/*
     This file is part of GNUnet.
     Copyright (C) 2009-2013 GNUnet e.V.

     GNUnet 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 3, or (at your
     option) any later version.

     GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     Boston, MA 02110-1301, USA.
*/

/**
 * @file util/socks.c
 * @brief  SOCKS5 connection support
 * @author Jeffrey Burdges
 *
 * These routines should be called only on newly active connections.
 */
#include "platform.h"
#include "gnunet_util_lib.h"


#define LOG(kind,...) GNUNET_log_from (kind, "util-socks", __VA_ARGS__)

#define LOG_STRERROR(kind,syscall) GNUNET_log_from_strerror (kind, "util-socks", syscall)


/* SOCKS5 authentication methods */
#define SOCKS5_AUTH_REJECT 0xFF /* No acceptable auth method */
#define SOCKS5_AUTH_NOAUTH 0x00 /* without authentication */
#define SOCKS5_AUTH_GSSAPI 0x01 /* GSSAPI */
#define SOCKS5_AUTH_USERPASS 0x02 /* User/Password */
#define SOCKS5_AUTH_CHAP 0x03 /* Challenge-Handshake Auth Proto. */
#define SOCKS5_AUTH_EAP 0x05 /* Extensible Authentication Proto. */
#define SOCKS5_AUTH_MAF 0x08 /* Multi-Authentication Framework */


/* SOCKS5 connection responces */
#define SOCKS5_REP_SUCCEEDED 0x00 /* succeeded */
#define SOCKS5_REP_FAIL 0x01 /* general SOCKS serer failure */
#define SOCKS5_REP_NALLOWED 0x02 /* connection not allowed by ruleset */
#define SOCKS5_REP_NUNREACH 0x03 /* Network unreachable */
#define SOCKS5_REP_HUNREACH 0x04 /* Host unreachable */
#define SOCKS5_REP_REFUSED 0x05 /* connection refused */
#define SOCKS5_REP_EXPIRED 0x06 /* TTL expired */
#define SOCKS5_REP_CNOTSUP 0x07 /* Command not supported */
#define SOCKS5_REP_ANOTSUP 0x08 /* Address not supported */
#define SOCKS5_REP_INVADDR 0x09 /* Inalid address */

const char * SOCKS5_REP_names(int rep)
{
  switch (rep) {
    case SOCKS5_REP_SUCCEEDED: return "succeeded";
    case SOCKS5_REP_FAIL: return "general SOCKS server failure";
    case SOCKS5_REP_NALLOWED: return "connection not allowed by ruleset";
    case SOCKS5_REP_NUNREACH: return "Network unreachable";
    case SOCKS5_REP_HUNREACH: return "Host unreachable";
    case SOCKS5_REP_REFUSED: return "connection refused";
    case SOCKS5_REP_EXPIRED: return "TTL expired";
    case SOCKS5_REP_CNOTSUP: return "Command not supported";
    case SOCKS5_REP_ANOTSUP: return "Address not supported";
    case SOCKS5_REP_INVADDR: return "Invalid address";
    default: return NULL;
  }
};


/**
 * Encode a string for the SOCKS5 protocol by prefixing it a byte stating its
 * length and stipping the trailing zero byte.  Truncates any string longer
 * than 255 bytes.
 *
 * @param b buffer to contain the encoded string
 * @param s string to encode
 * @return pointer to the end of the encoded string in the buffer
 */
unsigned char *
SOCK5_proto_string(unsigned char * b,
                   const char * s)
{
  size_t l = strlen(s);

  if (l > 255)
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         "SOCKS5 cannot handle hostnames, usernames, or passwords over 255 bytes, truncating.\n");
    l=255;
  }
  *(b++) = (unsigned char) l;
  strncpy ((char *)b, s, l);
  return b+l;
}


#define SOCKS5_step_greet 0
#define SOCKS5_step_auth  1
#define SOCKS5_step_cmd   2
#define SOCKS5_step_done  3

/**
 * State of the SOCKS5 handshake.
 */
struct GNUNET_SOCKS_Handshake
{

  /**
   * Connection handle used for SOCKS5
   */
  struct GNUNET_CONNECTION_Handle *socks5_connection;

  /**
   * Connection handle initially returned to client
   */
  struct GNUNET_CONNECTION_Handle *target_connection;

  /**
   * Transmission handle on socks5_connection.
   */
  struct GNUNET_CONNECTION_TransmitHandle *th;

  /**
   * Our stage in the SOCKS5 handshake
   */
  int step;

  /**
   * Precomputed SOCKS5 handshake ouput buffer
   */
  unsigned char outbuf[1024];

  /**
   * Pointers delineating protoocol steps in the outbut buffer
   */
  unsigned char * (outstep[4]);

  /**
   * SOCKS5 handshake input buffer
   */
  unsigned char inbuf[1024];

  /**
   * Pointers delimiting the current step in the input buffer
   */
  unsigned char * instart;
  unsigned char * inend;
};


/* Regitering prototypes */

void
register_reciever (struct GNUNET_SOCKS_Handshake *ih, int want);

  /* In fact, the client sends first rule in GNUNet suggests one could take
   * large mac read sizes without fear of screwing up the proxied protocol,
   * but we make a proper SOCKS5 client. */
#define register_reciever_wants(ih) ((SOCKS5_step_cmd == ih->step) ? 10 : 2)


struct GNUNET_CONNECTION_TransmitHandle *
register_sender (struct GNUNET_SOCKS_Handshake *ih);


/**
 * Conclude the SOCKS5 handshake successfully.
 *
 * @param ih SOCKS5 handshake, consumed here.
 * @param c open unused connection, consumed here.
 * @return Connection handle that becomes usable when the handshake completes.
 */
void
SOCKS5_handshake_done(struct GNUNET_SOCKS_Handshake *ih)
{
  GNUNET_CONNECTION_acivate_proxied (ih->target_connection);
}


/**
 * Read one step in the SOCKS5 handshake.
 *
 * @param ih SOCKS5 Handshake
 */
void
SOCKS5_handshake_step (struct GNUNET_SOCKS_Handshake *ih)
{
  unsigned char * b = ih->instart;
  size_t available = ih->inend - b;

  int want = register_reciever_wants(ih);
  if (available < want) {
    register_reciever (ih, want - available);
    return;
  }
  GNUNET_assert (SOCKS5_step_done > ih->step && ih->step >= 0);
  switch (ih->step) {
    case SOCKS5_step_greet:  /* SOCKS5 server's greeting */
      if (b[0] != 5)
      {
        LOG (GNUNET_ERROR_TYPE_ERROR,
             "Not a SOCKS5 server\n");
        GNUNET_assert (0);
      }
      switch (b[1]) {
        case SOCKS5_AUTH_NOAUTH:
          ih->step=SOCKS5_step_cmd;  /* no authentication to do */
          break;
        case SOCKS5_AUTH_USERPASS:
          ih->step=SOCKS5_step_auth;
          break;
        case SOCKS5_AUTH_REJECT:
          LOG (GNUNET_ERROR_TYPE_ERROR,
               "No authentication method accepted\n");
          return;
        default:
          LOG (GNUNET_ERROR_TYPE_ERROR,
               "Not a SOCKS5 server / Nonsensical authentication\n");
          return;
      }
      b += 2;
      break;
    case SOCKS5_step_auth:  /* SOCKS5 server's responce to authentication */
      if (b[1] != 0)
      {
        LOG (GNUNET_ERROR_TYPE_ERROR,
             "SOCKS5 authentication failed\n");
        GNUNET_assert (0);
      }
      ih->step=SOCKS5_step_cmd;
      b += 2;
      break;
    case SOCKS5_step_cmd:  /* SOCKS5 server's responce to command */
      if (b[0] != 5)
      {
        LOG (GNUNET_ERROR_TYPE_ERROR,
             "SOCKS5 protocol error\n");
        GNUNET_assert (0);
      }
      if (0 != b[1]) {
        LOG (GNUNET_ERROR_TYPE_ERROR,
             "SOCKS5 connection error : %s\n",
             SOCKS5_REP_names(b[1]));
        return;
      }
      b += 3;
      /* There is no reason to verify host and port afaik. */
      switch (*(b++)) {
        case 1: /* IPv4 */
          b += sizeof(struct in_addr);  /* 4 */
          break;
        case 4: /* IPv6 */
          b += sizeof(struct in6_addr);  /* 16 */
          break;
        case 3: /* hostname */
          b += *b;
          break;
      }
      b += 2;  /* port */
      if (b > ih->inend) {
        register_reciever (ih, b - ih->inend);
        return;
      }
      ih->step = SOCKS5_step_done;
      LOG (GNUNET_ERROR_TYPE_DEBUG,
           "SOCKS5 server : %s\n",
           SOCKS5_REP_names(b[1]));
      ih->instart = b;
      SOCKS5_handshake_done (ih);
      return;
    case SOCKS5_step_done:
      GNUNET_assert (0);
  }
  ih->instart = b;
  /* Do not reschedule the sender unless we're done reading.
   * I imagine this lets us avoid ever cancelling the transmit handle. */
  register_sender (ih);
}


/**
 * Callback to read from the SOCKS5 proxy.
 *
 * @param client the service
 * @param handler function to call with the message
 * @param handler_cls closure for @a handler
 */
void
reciever (void *cls,
          const void *buf, size_t available,
          const struct sockaddr * addr,
          socklen_t addrlen, int errCode)
{
  struct GNUNET_SOCKS_Handshake * ih = cls;
  GNUNET_assert (&ih->inend[available] < &ih->inbuf[1024]);
  GNUNET_memcpy(ih->inend, buf, available);
  ih->inend += available;
  SOCKS5_handshake_step (ih);
}


/**
 * Register callback to read from the SOCKS5 proxy.
 *
 * @param client the service
 * @param handler function to call with the message
 * @param handler_cls closure for @a handler
 */
void
register_reciever (struct GNUNET_SOCKS_Handshake *ih, int want)
{
  GNUNET_CONNECTION_receive (ih->socks5_connection,
                             want,
                             GNUNET_TIME_relative_get_minute_ (),
                             &reciever,
                             ih);
}


/**
 * Register SOCKS5 handshake sender
 *
 * @param cls closure (SOCKS handshake)
 * @param size number of bytes available in @a buf
 * @param buf where the callee should write the message
 * @return number of bytes written to @a buf
 */
size_t
transmit_ready (void *cls,
                size_t size,
                void *buf)
{
  struct GNUNET_SOCKS_Handshake * ih = cls;

  /* connection.c has many routines that call us with buf == NULL :
   * signal_transmit_error() - DNS, etc. active
   *   connect_fail_continuation()
   *     connect_probe_continuation() - timeout
   *     try_connect_using_address() - DNS failure/timeout
   *     transmit_timeout() - retry failed?
   * GNUNET_CONNECTION_notify_transmit_ready() can schedule :
   *   transmit_timeout() - DNS still working
   *   connect_error() - DNS done but no socket?
   * transmit_ready() - scheduler shutdown or timeout, or signal_transmit_error()
   * We'd need to dig into the scheduler to guess at the reason, as
   * connection.c tells us nothing itself, but mostly its timouts.
   * Initially, we'll simply ignore this and leave massive timeouts, but
   * maybe that should change for error handling pruposes.  It appears that
   * successful operations, including DNS resolution, do not use this.  */
  if (NULL == buf)
  {
    if (0 == ih->step)
    {
      LOG (GNUNET_ERROR_TYPE_WARNING,
	   "Timeout contacting SOCKS server, retrying indefinitely, but probably hopeless.\n");
      register_sender (ih);
    }
    else
    {
      LOG (GNUNET_ERROR_TYPE_ERROR,
	   "Timeout during mid SOCKS handshake (step %u), probably not a SOCKS server.\n",
	   ih->step);
      GNUNET_break (0);
    }
    return 0;
  }

  GNUNET_assert ( (1024 >= size) && (size > 0) );
  GNUNET_assert ( (SOCKS5_step_done > ih->step) && (ih->step >= 0) );
  unsigned char * b = ih->outstep[ih->step];
  unsigned char * e = ih->outstep[ih->step+1];
  GNUNET_assert (e <= &ih->outbuf[1024]);
  unsigned int l = e - b;
  GNUNET_assert (size >= l);
  GNUNET_memcpy (buf,
                 b,
                 l);
  register_reciever (ih,
                     register_reciever_wants (ih));
  return l;
}


/**
 * Register SOCKS5 handshake sender
 *
 * @param ih handshake
 * @return non-NULL if the notify callback was queued,
 *         NULL if we are already going to notify someone else (busy)
 */
struct GNUNET_CONNECTION_TransmitHandle *
register_sender (struct GNUNET_SOCKS_Handshake *ih)
{
  struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_MINUTES;

  GNUNET_assert (SOCKS5_step_done > ih->step);
  GNUNET_assert (ih->step >= 0);
  if (0 == ih->step)
    timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 3);
  unsigned char * b = ih->outstep[ih->step];
  unsigned char * e = ih->outstep[ih->step+1];
  GNUNET_assert (ih->outbuf <= b && b < e && e < &ih->outbuf[1024]);
  ih->th = GNUNET_CONNECTION_notify_transmit_ready (ih->socks5_connection,
                                                    e - b,
                                                    timeout,
                                                    &transmit_ready,
                                                    ih);
  return ih->th;
}


/**
 * Initialize a SOCKS5 handshake for authentication via username and
 * password.  Tor uses SOCKS username and password authentication to assign
 * programs unique circuits.
 *
 * @param user username for the proxy
 * @param pass password for the proxy
 * @return Valid SOCKS5 hanbdshake handle
 */
struct GNUNET_SOCKS_Handshake *
GNUNET_SOCKS_init_handshake (const char *user, const char *pass)
{
  struct GNUNET_SOCKS_Handshake *ih = GNUNET_new (struct GNUNET_SOCKS_Handshake);
  unsigned char * b = ih->outbuf;

  ih->outstep[SOCKS5_step_greet] = b;
  *(b++) = 5; /* SOCKS5 */
  unsigned char * n = b++;
  *n = 1; /* Number of authentication methods */
  /* We support no authentication even when requesting authentication,
   * but this appears harmless, given the way that Tor uses authentication.
   * And some SOCKS5 servers might require this.  */
  *(b++) = SOCKS5_AUTH_NOAUTH;
  if (NULL != user) {
    *(b++) = SOCKS5_AUTH_USERPASS;
    (*n)++;
  }
  /* There is no apperent reason to support authentication methods beyond
   * username and password since afaik Tor does not support them. */

  /* We authenticate with an empty username and password if the server demands
   * them but we do not have any. */
  if (user == NULL)
    user = "";
  if (pass == NULL)
    pass = "";

  ih->outstep[SOCKS5_step_auth] = b;
  *(b++) = 1; /* subnegotiation ver.: 1 */
  b = SOCK5_proto_string(b,user);
  b = SOCK5_proto_string(b,pass);

  ih->outstep[SOCKS5_step_cmd] = b;

  ih->inend = ih->instart = ih->inbuf;

  return ih;
}


/**
 * Initialize a SOCKS5 handshake without authentication, thereby possibly
 * sharing a Tor circuit with another process.
 *
 * @return Valid SOCKS5 hanbdshake handle
 */
struct GNUNET_SOCKS_Handshake *
GNUNET_SOCKS_init_handshake_noauth ()
{
  return GNUNET_SOCKS_init_handshake (NULL,NULL);
}


/**
 * Build request that the SOCKS5 proxy open a TCP/IP stream to the given host
 * and port.
 *
 * @param ih SOCKS5 handshake
 * @param hostname
 * @param port
 */
void
GNUNET_SOCKS_set_handshake_destination (struct GNUNET_SOCKS_Handshake *ih,
                                         const char *host, uint16_t port)
{
  union {
    struct in_addr in4;
    struct in6_addr in6;
  } ia;
  unsigned char * b = ih->outstep[SOCKS5_step_cmd];

  *(b++) = 5;  /* SOCKS5 */
  *(b++) = 1;  /* Establish a TCP/IP stream */
  *(b++) = 0;  /* reserved */

  /* Specify destination */
  if (1 == inet_pton(AF_INET,host,&ia.in4)) {
    *(b++)= 1;  /* IPv4 */
    GNUNET_memcpy (b, &ia.in4, sizeof(struct in_addr));
    b += sizeof(struct in_addr);  /* 4 */
  } else if (1 == inet_pton(AF_INET6,host,&ia.in6)) {
    *(b++)= 4;  /* IPv6 */
    GNUNET_memcpy (b, &ia.in6, sizeof(struct in6_addr));
    b += sizeof(struct in6_addr);  /* 16 */
  } else {
    *(b++)= 3;  /* hostname */
    b = SOCK5_proto_string (b, host);
  }

  /* Specify port */
  *(uint16_t*)b = htons (port);
  b += 2;

  ih->outstep[SOCKS5_step_done] = b;
}


/**
 * Run a SOCKS5 handshake on an open but unused TCP connection.
 *
 * @param ih SOCKS5 handshake, consumed here.
 * @param c open unused connection, consumed here.
 * @return Connection handle that becomes usable when the SOCKS5 handshake completes.
 */
struct GNUNET_CONNECTION_Handle *
GNUNET_SOCKS_run_handshake(struct GNUNET_SOCKS_Handshake *ih,
                            struct GNUNET_CONNECTION_Handle *c)
{
  ih->socks5_connection=c;
  ih->target_connection = GNUNET_CONNECTION_create_proxied_from_handshake (c);
  register_sender (ih);

  return ih->target_connection;
}


/**
 * Check if a SOCKS proxy is required by a service.  Do not use local service
 * if a SOCKS proxy port is configured as this could deanonymize a user.
 *
 * @param service_name name of service to connect to
 * @param cfg configuration to use
 * @return GNUNET_YES if so, GNUNET_NO if not
 */
int
GNUNET_SOCKS_check_service (const char *service_name,
                            const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  return GNUNET_CONFIGURATION_have_value (cfg, service_name, "SOCKSPORT") ||
         GNUNET_CONFIGURATION_have_value (cfg, service_name, "SOCKSHOST");
}


/**
 * Try to connect to a service configured to use a SOCKS5 proxy.
 *
 * @param service_name name of service to connect to
 * @param cfg configuration to use
 * @return Connection handle that becomes usable when the handshake completes.
 *         NULL if SOCKS not configured or not configured properly
 */
struct GNUNET_CONNECTION_Handle *
GNUNET_SOCKS_do_connect (const char *service_name,
                         const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct GNUNET_SOCKS_Handshake *ih;
  struct GNUNET_CONNECTION_Handle *socks5; /* *proxied */
  char *host0;
  char *host1;
  char *user;
  char *pass;
  unsigned long long port0;
  unsigned long long port1;

  if (GNUNET_YES !=
      GNUNET_SOCKS_check_service (service_name, cfg))
    return NULL;
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_number (cfg,
                                             service_name,
                                             "SOCKSPORT",
                                             &port0))
    port0 = 9050;
  /* A typical Tor client should usually try port 9150 for the TBB too, but
   * GNUnet can probably assume a system Tor installation. */
  if (port0 > 65535 || port0 <= 0)
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
	 _("Attempting to use invalid port %d as SOCKS proxy for service `%s'.\n"),
	 port0,
         service_name);
    return NULL;
  }
  if ( (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_number (cfg,
                                               service_name,
                                               "PORT",
                                               &port1)) ||
       (port1 > 65535) ||
       (port1 <= 0) ||
       (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_string (cfg,
                                               service_name,
                                               "HOSTNAME",
                                               &host1)))
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
	 _("Attempting to proxy service `%s' to invalid port %d or hostname.\n"),
	 service_name,
         port1);
    return NULL;
  }
  /* Appeared to still work after host0 corrupted, so either test case is broken, or
     this whole routine is not being called. */
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (cfg, service_name, "SOCKSHOST", &host0))
    host0 = NULL;
  socks5 = GNUNET_CONNECTION_create_from_connect (cfg,
                                                  (host0 != NULL)
                                                  ? host0
                                                  :"127.0.0.1",
                                                  port0);
  GNUNET_free_non_null (host0);

  /* Sets to NULL if they do not exist */
  (void) GNUNET_CONFIGURATION_get_value_string (cfg,
                                                service_name,
                                                "SOCKSUSER",
                                                &user);
  (void) GNUNET_CONFIGURATION_get_value_string (cfg,
                                                service_name,
                                                "SOCKSPASS",
                                                &pass);
  ih = GNUNET_SOCKS_init_handshake(user,pass);
  GNUNET_free_non_null (user);
  GNUNET_free_non_null (pass);

  GNUNET_SOCKS_set_handshake_destination (ih,
                                          host1,
                                          port1);
  GNUNET_free (host1);
  return GNUNET_SOCKS_run_handshake (ih,
                                     socks5);
}

/* socks.c */
