/*
 * nantarad client... obtains information from nantarad, and returns 
 * the value obtained to stdout and exit value.
 * on error, returns EXIT_FAILURE on normal failure, and -1 on 
 * more obscure failures.
 *
 * originally a Telnet clone which does little input/output cooking.
 * 2001 Feb 28 Junichi Uekawa <dancer@debian.org>
 * 2001 Mar 4 Junichi Uekawa <dancer@debian.org> reuse for nantaraclient
 * copyright 2001 Junichi Uekawa
 * See GPL version 2 or later for license.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pthread.h>

#define BACKLOG 10
#define MAXREAD 1024

struct handle_incoming_params
{
  int t ;			/* socket */
  struct sockaddr_in *tmpisap;	/* address of caller */
} handle_incoming_params ;

struct handle_outgoing_params
{
  int dest;			/* socket one. */
  int from;			/* socket two. */
  pthread_cond_t cond;  
} handle_outgoing_params;

int the_destination_port = 0;
char * the_destination_addr = 0;

static void write_to_tcp_thread(struct handle_outgoing_params * data)
{
  char buf [MAXREAD+2];
  int readbytes=0;
  while ((readbytes = read (data->dest, buf, MAXREAD)) > 0)
    {
      write ( data->from, buf, readbytes );
      //      buf[readbytes+1]=0;      
      //      printf("IN: %s\n", buf);
    }
  pthread_cond_broadcast(&(data->cond));
}

static void read_to_tcp_thread(struct handle_outgoing_params * data)
{
  char buf [MAXREAD+2];
  int readbytes=0;
  while ((readbytes = read (data->from, buf, MAXREAD)) > 0)
    { 
      write( data->dest, buf, readbytes );
      //      buf[readbytes+1]=0;      
      //      printf("OUT:%s\n", buf);
    }
  pthread_cond_broadcast(&(data->cond));
}

static void read_from_tcp(int * sockptr, int fromsock)
{
  pthread_t writetask,readtask;
  pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER ;
    
  struct handle_outgoing_params data;
  pthread_cond_init(&data.cond, NULL);
  data.dest = *sockptr;
  data.from = fromsock;
  
  pthread_mutex_lock (&mut);
  
  pthread_create( &writetask, NULL, (void*)write_to_tcp_thread, &data );
  pthread_create( &readtask, NULL, (void*)read_to_tcp_thread, &data );
  
  pthread_cond_wait( &data.cond, &mut );
  pthread_mutex_unlock( &mut );

  pthread_cancel ( readtask );
  pthread_cancel ( writetask);  
}

static int connect_to_host(char * stringaddress, int portnumber )
{
  struct sockaddr_in inet_address;
  struct hostent * hostinfo ;
  int sock;
    
  sock = socket (PF_INET, SOCK_STREAM, 0);
  
  inet_address.sin_family = AF_INET;
  inet_address.sin_port = htons(portnumber);
  hostinfo = gethostbyname(stringaddress);
  if (hostinfo == NULL)
    {
      fprintf(stderr, "Unknown host %s\n", stringaddress);
      return -1 ;
    }
  inet_address.sin_addr = *(struct in_addr * )hostinfo->h_addr ;

  if (connect (sock,
	       (struct sockaddr *)&inet_address,
	       sizeof (inet_address))<0)
    {
      perror("connect");
      fprintf(stderr, "Connect to %s [%i]\n", the_destination_addr, the_destination_port);      
      return -1;
    }
  return sock;  
}

				/* the routine to connect. */
static void connect_routine(struct handle_incoming_params * parameter)
{
  int socket;   
  if (-1 == (socket = connect_to_host (the_destination_addr, the_destination_port)))
    {
      fprintf (stderr, "Connection failed \n");
      return ;      
    }
  read_from_tcp(&socket, parameter->t);
  close (socket);
  return ;  
}

static void handle_incoming(struct handle_incoming_params * parameter)
{
  printf("Incoming [%i]\n", parameter->t);
  connect_routine(parameter);
  printf("Closed [%i]\n", parameter->t);  
  /* cleanup routine */
  close(parameter->t);  
  free (parameter->tmpisap);  
  free (parameter);
}

static char * gethostname_versatile(void)
{
  const int unithostnamelen = 256;
  int unit = 1;
  char * localhostname = malloc (unithostnamelen + 1) ;
  if (!localhostname) return NULL;
  
  while (gethostname (localhostname, unithostnamelen * unit) == -1)
    {
      char * newlocalhostname = realloc(localhostname, unithostnamelen * (++ unit) + 1);
      if (newlocalhostname == NULL)
	{
	  free(localhostname);
	  return NULL;
	}
      else 
	localhostname = newlocalhostname ;
    }
  return localhostname;
}

#define malloc_with_error(A,BYTES) if (NULL==((A)=malloc(BYTES)))\
{fprintf(stderr, "out of memory in allocating %i bytes\n", BYTES);return (1);}

				/* return 1 on error */
int 
tcp_host_setup( const char * programname, const char * port_name, void * handle_incoming_function)
{
  char *localhostname ;
  struct sockaddr_in sa;
  struct handle_incoming_params * params;
    
  int incoming_socket;		/* the main waiting socket */
  struct hostent *hp;
  int i;
  pthread_t newthread_id ;
  pthread_attr_t pthread_attrib ;
  
				/* creating a pthread attribute */
  pthread_attr_init(&pthread_attrib);
  pthread_attr_setdetachstate(&pthread_attrib,1);
  
  if (!(localhostname = gethostname_versatile ()))
    {
      fprintf(stderr, "%s: Cannot allocate enough memory to gethostname\n", programname);
      return 1;      
    }
  if ((hp = gethostbyname(localhostname)) == NULL)
    {
      fprintf(stderr, "%s: cannot get localhost info\n", programname);
      return 1;      
    }
  if ((incoming_socket = socket (hp->h_addrtype, SOCK_STREAM, 0 )) == -1 )
    {
      perror  ("socket");
      return 1;
    }

  sa.sin_port = htons(atoi(port_name));	/* this should hopefully be portable */
  sa.sin_addr.s_addr= htonl(INADDR_ANY); /* should it be like this? it's 0 in linux */
  sa.sin_family = AF_INET;
  
  if (bind(incoming_socket, (struct sockaddr*)&sa, sizeof (sa) ) < 0  ) 
    {
      perror ("bind");
      return 1;      
    }

  listen (incoming_socket, BACKLOG);
  /* the main routine getting incoming connections */
  while (1)
    {
      i = sizeof (struct sockaddr_in);
      malloc_with_error(params, sizeof(struct handle_incoming_params));
      malloc_with_error(params->tmpisap, i);
      if ((params->t = accept (incoming_socket, (struct sockaddr*)params->tmpisap, &i )) == -1)
	{
	  perror ("accept");
	  return 1;	  
	}      
      pthread_create (&newthread_id, &pthread_attrib, (void*)handle_incoming_function, params);
    }
}

int main (int ac, char** av)
{  
  puts ("tcpproxy daemon version 0.0.1\n"
	"copyright 2001 Junichi Uekawa\n");
  if (ac != 4)
    {
      fprintf(stderr, "Please give me [waitport] [destaddr] [destport]\n");
      exit(EXIT_FAILURE);
    }
  the_destination_port = atoi (av[3]);
  the_destination_addr = av[2];
 
  
  exit (tcp_host_setup(av[0],av[1],handle_incoming));
}
