/* --------------------------------------------------------------------------
 *    Name: tube.c
 * Purpose: TCP Tunnel
 *  Author: by David Thomas,  1997-9
 * Version: 0.92 (17 Jul 1999)
 * ----------------------------------------------------------------------- */

#define BSD_COMP

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sys/types.h"
#include "sys/socket.h"
#include "sys/ioctl.h"
#include "sys/time.h"
#include "netinet/in.h"
#include "errno.h"
#include "netdb.h"

#ifdef __riscos
#include "unixlib.h"
#include "socklib.h"
#include "inetlib.h"
#else
#define socketread(sk, bf, sz)	read(sk, bf, sz)
#define socketwrite(sk, bf, sz)	write(sk, bf, sz)
#define socketclose(sk)		close(sk)
#define socketioctl(sk, fl, pt)	ioctl(sk, fl, pt)
#endif

#define NOT_USED(x) {x = x;}

#include "tube.h"


static proxy_server server;	/* One server.  Note that it needs
				 * to be here for the atexit() to
				 * function. */

int main(int argc, char *argv[])
{
  char config_line[256],
       config_name[16],
       config_value[64],
       hostname[64],
      *p;
  FILE *config;
  string_substitution *csub = NULL,
                     *ssub = NULL;


  if (argc != 2)
  {
    fprintf(stderr, "Syntax: %s <configuration file>\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  fprintf(stdout, "Tube 0.92 - TCP Tunnel - by David Thomas, (C) 1997-9\n\n");

  config = fopen(argv[1], "r");
  if (config == NULL)
  {
    fprintf(stderr, "Error: Configuration file could not be opened.\n");
    exit(EXIT_FAILURE);
  }

  /* Set up the default choices (used if the respective choice is not
   * specified in the configuration file. */
  sprintf(server.remote_host, "news");
  server.remote_host_port = 119;
  server.port = 1190;
  server.buffer_size = 4096;
  server.max_connections = 1;

  server.first_clt_substitution = NULL;
  server.first_srv_substitution = NULL;

  while (fgets(config_line, sizeof(config_line), config) != NULL)
  {
    if (config_line[0] != '#' && config_line[0] >= ' ')
    {

      sscanf(config_line, "%s\t%s\n", config_name, config_value);

      if (strcmp("RemoteHost", config_name) == 0)
	strcpy(server.remote_host, config_value);

      else if (strcmp("RemoteHostPort", config_name) == 0)
	server.remote_host_port = atoi(config_value);

      else if (strcmp("ProxyPort", config_name) == 0)
	server.port = atoi(config_value);

      else if (strcmp("ProxyBufferSize", config_name) == 0)
	server.buffer_size = atoi(config_value);

      else if (strcmp("MaxConnections", config_name) == 0)
	server.max_connections = atoi(config_value);

      else if (strcmp("ClientSubst", config_name) == 0)
      {
	if (csub == NULL)
	{
	  /* Begin the linked list */
	  server.first_clt_substitution =
	      csub = malloc(sizeof(string_substitution));
	  /* ### if (csub == NULL) ... */
	}
	else
	{
	  /* Add a node */
	  if (csub->next == NULL)
	    csub->next = malloc(sizeof(string_substitution));
	  /* ### if (csub == NULL) ... */
	  csub = csub->next;
	}
	/* Insert data into the new node */
	p = strtok(config_value, ",");
	csub->from = malloc(strlen(p) + 1);
	strcpy(csub->from, p);
	p = strtok(NULL, ",");
	csub->to = malloc(strlen(p) + 1);
	strcpy(csub->to, p);
	csub->next = NULL;
      }

      else if (strcmp("ServerSubst", config_name) == 0)
      {
	if (ssub == NULL)
	{
	  /* Begin the linked list */
	  server.first_srv_substitution =
	      ssub = malloc(sizeof(string_substitution));
	  /* if (ssub == NULL) ... */
	}
	else
	{
	  /* Add a node */
	  if (ssub->next == NULL)
	    ssub->next = malloc(sizeof(string_substitution));
	  /* ### if (csub == NULL) ... */
	  ssub = ssub->next;
	}
	/* Insert data into the new node */
	p = strtok(config_value, ",");
	csub->from = malloc(strlen(p) + 1);
	strcpy(csub->from, p);
	p = strtok(NULL, ",");
	csub->to = malloc(strlen(p) + 1);
	strcpy(csub->to, p);
	ssub->next = NULL;
      }

    }
  }

  fclose(config);

  if (server_initialise(&server) == 0)
    exit(EXIT_FAILURE);

  /* There is no way to stop the server, so we use an atexit() function. */
  atexit(atexit_handler);

  /* Display the configuration. */
  gethostname(hostname, 64);
  fprintf(stdout, "Configuration:\n  <%s> tunnel server listening on port %d.\n  Remote host is <%s> port %d.\n  Up to %d connections will be allowed.\n\n", hostname, server.port, server.remote_host, server.remote_host_port, server.max_connections);

  for (;;)
    server_process(&server);
}

void atexit_handler(void)
{
  server_closedown(&server);
}

/* ----------------------------------------------------------------------- */

int server_initialise(proxy_server * server)
{
  int on = 1,
      sk;
  struct sockaddr_in sv;


  sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sk < 0)
  {
    fprintf(stderr, "Error: server_initialise: socket() failed.\n");
    return 0;
  }

#ifdef __riscos
  /* Make the TaskWindow 'sleep' we're blocked. */
  socketioctl(sk, 0x80046679 /* FIOSLEEPTW */ , &on);
#endif

  setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));

  sv.sin_family = AF_INET;
  sv.sin_addr.s_addr = INADDR_ANY;
  sv.sin_port = htons(server->port);
  if (bind(sk, (struct sockaddr *) &sv, sizeof(sv)))
  {
    fprintf(stderr, "Error: server_initialise: bind() failed.\n");
    socketclose(sk);
    return 0;			/* Failure. */
  }

  listen(sk, 5 /* Backlog. */ );

  FD_ZERO(&server->fds);
  FD_SET(sk, &server->fds);

  server->socket = sk;

  /* No connections yet. */
  server->first_connection = NULL;
  server->connections = 0;

  return 1;
}

void server_process(proxy_server * server)
{
  memcpy(&server->read_ready_fds, &server->fds, sizeof(fd_set));
  memcpy(&server->write_ready_fds, &server->fds, sizeof(fd_set));

  if (select(FD_SETSIZE,
	     &server->read_ready_fds,
	     &server->write_ready_fds,
	     NULL,
	     NULL))
  {

    if (server->connections < server->max_connections)
      if (FD_ISSET(server->socket, &server->read_ready_fds))
	connection_accept(server);

    if (server->connections)
    {
      connection_process(server);
      connection_release(server);
    }

  }
}

void server_closedown(proxy_server * server)
{
  proxy_connection *cn,
                  *next;

  cn = server->first_connection;
  while (cn != NULL)
  {
    next = cn->next;
    connection_halfclose(server, cn);
    connection_close(server, cn);
    cn = next;
  }

  socketclose(server->socket);
  FD_CLR(server->socket, &server->fds);
}

/* ----------------------------------------------------------------------- */

void connection_accept(proxy_server * server)
{
  int insk,
      sz,
      outsk,
      on = 1;
  struct hostent *hp;
  struct sockaddr_in remote,
              sa;
  proxy_connection *newcn,
                  *cn;

  sz = sizeof(sa);
  insk = accept(server->socket, (struct sockaddr *) &sa, &sz);
  if (insk < 0)
  {
    /* Abort. */
    fprintf(stderr, "Error: connection_accept: %d on accept().\n", errno);
    FD_CLR(server->socket, &server->fds);	/* Disallow new connections. */
    /* Server socket will be closed on exit. */
    return;
  };

#ifdef __riscos
  /* Make the TaskWindow 'sleep' if blocked. */
  socketioctl(insk, 0x80046679 /* FIOSLEEPTW */ , &on);
#endif

  /* Get the clients's hostent. */
  hp = gethostbyaddr((char *) &sa.sin_addr.s_addr, 4, AF_INET);
  if (hp == NULL)
    fprintf(stdout, "[%03d] Connected to client, name unresolvable (%s)...\n", insk, inet_ntoa(sa.sin_addr));
  else
    fprintf(stdout, "[%03d] Connected to client (%s)...\n", insk, hp->h_name);

  /* Claim space for proxy connection block plus two buffers. */
  newcn = malloc(sizeof(proxy_connection) + server->buffer_size * 2);
  if (newcn == NULL)
  {
    /* abort */
    fprintf(stdout, "[%03d] Connection aborted: buffer memory claim failed.\n", insk);
    socketclose(insk);
    return;
  }
  newcn->clt.buffer = (char *) newcn + sizeof(proxy_connection);
  newcn->srv.buffer = newcn->clt.buffer + server->buffer_size;

  /* Create a socket for the proxy->server connection. */
  outsk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (outsk < 0)
  {
    /* Abort. */
    fprintf(stdout, "[%03d] Connection aborted: socket creation failed.\n", insk);
    socketclose(insk);
    free(newcn);
    return;
  }

#ifdef __riscos
  /* Make the TaskWindow 'sleep' if blocked. */
  socketioctl(outsk, 0x80046679 /* FIOSLEEPTW */ , &on);
#endif

  /* Get the server's hostent. */
  hp = gethostbyname(server->remote_host);
  if (hp == NULL)
  {
    /* abort */
    fprintf(stdout, "[%03d] Connection aborted: could not resolve '%s'.\n", insk, server->remote_host);
    socketclose(insk);
    free(newcn);
    socketclose(outsk);
    return;
  }

  /* Establish proxy->server connection. */
  bzero((char *) &remote, sizeof(remote));
  bcopy(hp->h_addr, (char *) &remote.sin_addr, hp->h_length);
  remote.sin_family = hp->h_addrtype;
  remote.sin_port = htons(server->remote_host_port);
  if (connect(outsk, (struct sockaddr *) &remote, sizeof(remote)) < 0)
  {
    /* Abort. */
    fprintf(stdout, "[%03d] Connection aborted: connect failed with error %d.\n", insk, errno);
    socketclose(insk);
    free(newcn);
    socketclose(outsk);
    return;
  }
  fprintf(stdout, "[%03d] Connected to server (%s)...\n", outsk, server->remote_host);

  FD_SET(insk, &server->fds);
  newcn->clt.index = 0;
  newcn->clt.socket = insk;
  newcn->clt.spare = server->buffer_size;
  newcn->clt.substitutions = server->first_clt_substitution;
  if (newcn->clt.substitutions == NULL)
  {
    /* Block mode/ */
    /* newcn->clt.line = NULL; */
    newcn->clt.state = STATE_READDATA;
  }
  else
  {
    /* Line mode (slower.) */
    newcn->clt.line = malloc(NNTPMAXLINELENGTH);
    if (newcn->clt.line == NULL)
    {
      /* Malloc failed. */
      fprintf(stdout, "[%03d] Connection aborted: line memory claim failed.\n", insk);
      socketclose(insk);
      free(newcn);
      socketclose(outsk);
      return;
    }
    newcn->clt.line[newcn->clt.index] = '\0';
    newcn->clt.state = STATE_READLINE;
  }

  FD_SET(outsk, &server->fds);
  newcn->srv.index = 0;
  newcn->srv.socket = outsk;
  newcn->srv.spare = server->buffer_size;
  newcn->srv.substitutions = server->first_srv_substitution;
  if (newcn->srv.substitutions == NULL)
  {
    /* Block mode. */
    /* newcn->srv.line = NULL; */
    newcn->srv.state = STATE_READDATA;
  }
  else
  {
    /* Line mode (slower.) */
    newcn->srv.line = malloc(NNTPMAXLINELENGTH);
    if (newcn->srv.line == NULL)
    {
      /* Malloc failed. */
      fprintf(stdout, "[%03d] Connection aborted: line memory claim failed.\n", outsk);
      socketclose(insk);
      free(newcn);
      socketclose(outsk);
      return;
    }
    newcn->srv.line[newcn->srv.index] = '\0';
    newcn->srv.state = STATE_READLINE;
  }

  /* Add a new connection */
  if (server->first_connection == NULL)
  {
    /* No previous connections */
    server->first_connection = newcn;
    newcn->last = NULL;
  }
  else
  {
    /* Add to end of existing linked list */
    cn = server->first_connection;
    while (cn->next != NULL)
      cn = cn->next;
    cn->next = newcn;
    newcn->last = cn;
  }
  newcn->next = NULL;

  server->connections++;
}

void connection_process(proxy_server * server)
{
  proxy_connection *cn;


  cn = server->first_connection;
  while (cn != NULL)
  {


    if (cn->clt.state == STATE_READLINE)
      if (FD_ISSET(cn->clt.socket, &server->read_ready_fds))
	connection_readline(server, cn, &cn->clt);

    if (cn->clt.state == STATE_CHECKLINE)
      /* Needs no state; it sends and receives nothing. */
      connection_checkline(server, cn, &cn->clt);

    if (cn->clt.state == STATE_READDATA)
      if (FD_ISSET(cn->clt.socket, &server->read_ready_fds))
	connection_readdata(server, cn, &cn->clt);

    if (cn->clt.state == STATE_WRITE)
      if (FD_ISSET(cn->srv.socket, &server->write_ready_fds))
	connection_write(server, cn, &cn->clt, &cn->srv);


    if (cn->srv.state == STATE_READLINE)
      if (FD_ISSET(cn->srv.socket, &server->read_ready_fds))
	connection_readline(server, cn, &cn->srv);

    if (cn->srv.state == STATE_CHECKLINE)
      /* Needs no state; it sends and receives nothing. */
      connection_checkline(server, cn, &cn->srv);

    if (cn->srv.state == STATE_READDATA)
      if (FD_ISSET(cn->srv.socket, &server->read_ready_fds))
	connection_readdata(server, cn, &cn->srv);

    if (cn->srv.state == STATE_WRITE)
      if (FD_ISSET(cn->clt.socket, &server->write_ready_fds))
	connection_write(server, cn, &cn->srv, &cn->clt);


    cn = cn->next;
  }

}

/* ----------------------------------------------------------------------- */

void connection_readline(proxy_server * server, proxy_connection * cn, proxy_side * cs)
{
  int bytesread;
  char byte;

  /* Read a byte. */
  /* ### This should really be buffered. */
  bytesread = socketread(cs->socket, &byte, 1);
  if (bytesread < 0)
  {
    /* Error. */
    fprintf(stdout, "[%03d] Error %d on read.\n", cs->socket, errno);
    connection_halfclose(server, cn);
  }
  else
  {
    /* No error. */
    if (bytesread)
    {
      /* ### 0's cause fugup. */
      if (byte == 10)
      {
	cs->line[cs->index] = '\0';
	cs->index = 0;
	cs->state = STATE_CHECKLINE;
      }
      else if (byte != 13)
      {
	cs->line[cs->index++] = byte;
	printf("%d (%c)\n", byte, byte);
      }
    }
    else
    {
      /* End of connection. */
      fprintf(stdout, "[%03d] Connection closed.\n", cs->socket);
      cs->state = STATE_CLOSED;
      FD_CLR(cs->socket, &server->fds);
      socketclose(cs->socket);
      cs->socket = -1;
    }
  }
}

void connection_checkline(proxy_server * server, proxy_connection * cn, proxy_side * cs)
{
  char *p;
  int l;
  string_substitution *curr_substitution;
  NOT_USED(cn);


  curr_substitution = cs->substitutions;
  while (curr_substitution != NULL)
  {
    replace(curr_substitution->from, curr_substitution->to, cs->line);
    curr_substitution = curr_substitution->next;
  }

  l = strlen(cs->line);
  memcpy(cs->buffer, cs->line, l);
  p = cs->buffer + l;
  *p++ = 13;
  *p++ = 10;

  cs->spare = server->buffer_size - (p - cs->buffer);

  cs->state = STATE_WRITE;
}

void connection_readdata(proxy_server * server, proxy_connection * cn, proxy_side * cs)
{
  int bytesread;


  if (cs->spare == 0)
    /* Buffer is full. */
    return;

  /* Try to fill the buffer. */
  bytesread = socketread(cs->socket, cs->buffer + server->buffer_size - cs->spare, cs->spare);
  if (bytesread < 0)
  {
    /* Error. */
    fprintf(stdout, "[%03d] Error %d on read.\n", cs->socket, errno);
    connection_halfclose(server, cn);
  }
  else
  {
    /* No error. */
    if (bytesread)
    {
      cs->spare -= bytesread;
      cs->state = STATE_WRITE;
    }
    else
    {
      /* End of connection. */
      fprintf(stdout, "[%03d] Connection closed.\n", cs->socket);
      cs->state = STATE_CLOSED;
      FD_CLR(cs->socket, &server->fds);
      socketclose(cs->socket);
      cs->socket = -1;
    }
  }
}

void connection_write(proxy_server * server, proxy_connection * cn, proxy_side * from, proxy_side * to)
{
  int wanttowrite,
      wrote;


  wanttowrite = server->buffer_size - from->spare;
  if (wanttowrite)
  {
    /* There is data to write. */
    /* Try to write the entire buffer. */
    wrote = socketwrite(to->socket, from->buffer, wanttowrite);
    if (wrote < 0)
    {
      /* Error. */
      fprintf(stdout, "[%03d] Error %d on write.\n", to->socket, errno);
      connection_halfclose(server, cn);
    }
    else
    {
      /* No error. */
      /* If we couldn't write the entire buffer then blat the buffer contents
       * back. */
      if (wrote < wanttowrite)
      {
	memmove(from->buffer, from->buffer + wrote, wanttowrite - wrote);
      }
      else
      {
	if (from->substitutions == NULL)
	  from->state = STATE_READDATA;
	else
	  from->state = STATE_READLINE;
      }
      from->spare += wrote;
    }
  }
}


void connection_release(proxy_server * server)
{
  proxy_connection *cn,
                  *next;


  cn = server->first_connection;
  while (cn != NULL)
  {
    next = cn->next;

    /* Is either end closed and out of data? */
    if (cn->clt.state == STATE_CLOSED && cn->srv.spare == server->buffer_size || cn->srv.state == STATE_CLOSED && cn->clt.spare == server->buffer_size)
      connection_halfclose(server, cn);

    /* Is the connection half closed?  If so, remove it. */
    if (cn->clt.state == STATE_CLOSED && cn->srv.state == STATE_CLOSED)
      connection_close(server, cn);

    cn = next;
  }
}

void connection_halfclose(proxy_server * server, proxy_connection * cn)
{
  /* Close the associated sockets, but don't remove the block. */

  if (cn->clt.socket != -1)
  {
    fprintf(stdout, "[%03d] Client connection closed.\n", cn->clt.socket);
    cn->clt.state = STATE_CLOSED;
    FD_CLR(cn->clt.socket, &server->fds);
    socketclose(cn->clt.socket);
    cn->clt.socket = -1;
  }

  if (cn->srv.socket != -1)
  {
    fprintf(stdout, "[%03d] Server connection closed.\n", cn->srv.socket);
    cn->srv.state = STATE_CLOSED;
    FD_CLR(cn->srv.socket, &server->fds);
    socketclose(cn->srv.socket);
    cn->srv.socket = -1;
  }
}

void connection_close(proxy_server * server, proxy_connection * cn)
{
  /* Really close. */

  /* Adjust the linked list. */
  /* Anything that points forward to this connection. */
  if (cn->last != NULL)
    cn->last->next = cn->next;
  else
    /* Start of chain. */
    server->first_connection = cn->next;	/* Update the fixed ptr. */
  /* Anything that points back to this connection. */
  if (cn->next != NULL)
    cn->next->last = cn->last;

  free(cn);
  cn = NULL;

  server->connections--;
}



void replace(char *replace, char *with, char *string)
{
  char *p;
  int slen,
      rlen,
      wlen;


  /* ### Need verification checks. */
  p = strstr(string, replace);
  if (p != NULL)
  {
    rlen = strlen(replace);
    wlen = strlen(with);
    if (rlen != wlen)
    {
      slen = strlen(string);
      memmove(p + wlen, p + rlen, string + slen - p - rlen + 1);
    }
    memmove(p, with, wlen);
  }
}
