/*
 * IRC - Internet Relay Chat, tools/Bounce/Bounce.cpp
 * Copyright (C) 1990 Jarkko Oikarinen and
 *                    University of Oulu, Computing Center
 *
 * See file AUTHORS in IRC package for additional names of
 * the programmers.
 *
 * This program 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 1, or (at your option)
 * any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Port Bouncer.
 *
 * This tool is designed to set up a number of local listening ports, and
 * then forward any data recived on those ports, to another host/port combo.
 * Each listening port can bounce to a different host/port defined in the
 * config file. --Gte 
 *
 * $Id: Bounce.cpp,v 1.3 2002/03/07 22:52:57 ghostwolf Exp $ 
 *
 */

#include "Bounce.h"
 
int main() {
  Bounce* application = new Bounce();

  /*
   *  Ignore SIGPIPE.
   */

  struct sigaction act; 
  act.sa_handler = SIG_IGN;
  act.sa_flags = 0;
  sigemptyset(&act.sa_mask);
  sigaction(SIGPIPE, &act, 0);
 
#ifndef DEBUG
  /*
   *  If we aren't debugging, we might as well
   *  detach from the console.
   */

  pid_t forkResult = fork() ;
  if(forkResult < 0)
  { 
    printf("Unable to fork new process.\n");
    return -1 ;
  } 
  else if(forkResult != 0)
  {
    printf("Successfully Forked, New process ID is %i.\n", forkResult);
    return 0;
  } 
#endif

  /*
   *  Create new application object, bind listeners and begin
   *  polling them.
   */
  application->bindListeners();

  while (1) {
    application->checkSockets();
  } 
}

/*
 ****************************************
 *                                      *
 *     Bounce class implementation.     *
 *                                      *
 ****************************************
 */
 
void Bounce::bindListeners() { 
/*
 *  bindListeners.
 *  Inputs: Nothing.
 *  Outputs: Nothing.
 *  Process: 1. Reads the config file, and..
 *           2. Creates a new listener for each 'P' line found.
 *
 */

  FILE* configFd;
  char tempBuf[256];
  int localPort = 0;
  int remotePort = 0;
  char* remoteServer;
  char* vHost; 
 
  /*
   *  Open config File.
   */
  
  if(!(configFd = fopen("bounce.conf", "r")))
  {
    printf("Error, unable to open config file!\n");
    exit(0);
  } 

  while (fgets(tempBuf, 256, configFd) != NULL) { 
    if((tempBuf[0] != '#') && (tempBuf[0] != '\r')) {
    switch(tempBuf[0])
    {
      case 'P': { /* Add new port listener */ 
        strtok(tempBuf, ":");
        vHost = strtok(NULL, ":");
        localPort = atoi(strtok(NULL, ":"));
        remoteServer = strtok(NULL, ":");
        remotePort = atoi(strtok(NULL, ":")); 

        Listener* newListener = new Listener();
        strcpy(newListener->myVhost, vHost); 
        strcpy(newListener->remoteServer, remoteServer);
        newListener->remotePort = remotePort;
        newListener->localPort = localPort;
#ifdef DEBUG
        printf("Adding new Listener: Local: %s:%i, Remote: %s:%i\n", vHost, localPort, remoteServer, remotePort);
#endif

        newListener->beginListening();
        listenerList.insert(listenerList.begin(), newListener); 
        break;
      }
    }
    } 
  } 
}

void Bounce::checkSockets() { 
/*
 *  checkSockets.
 *  Inputs: Nothing.
 *  Outputs: Nothing.
 *  Process: 1. Builds up a FD_SET of all sockets we wish to check.
 *              (Including all listeners & all open connections).
 *           2. SELECT(2) the set, and forward/accept as needed.
 *
 */ 
  typedef std::list<Listener*> listenerContainer;
  typedef listenerContainer::iterator listIter;

  typedef std::list<Connection*> connectionContainer;
  typedef connectionContainer::iterator connIter; 

  struct timeval tv;
  fd_set readfds; 
  tv.tv_sec = 0;
  tv.tv_usec = 1000;
  int tempFd = 0;
  int tempFd2 = 0;
  int highestFd = 0;
  int delCheck = 0;
  char* tempBuf;

  FD_ZERO(&readfds);
 
  /*
   *  Add all Listeners to the set.
   */

  listIter a = listenerList.begin();
  while(a != listenerList.end())
  { 
    tempFd = (*a)->fd; 
    FD_SET(tempFd, &readfds);
    if (highestFd < tempFd) highestFd = tempFd;
    a++;
  }

  /*
   *  Add Local & Remote connections from each
   *  connection object to the set.
   */

  connIter b = connectionsList.begin();
  while(b != connectionsList.end())
  { 
    tempFd = (*b)->localSocket->fd;
    tempFd2 = (*b)->remoteSocket->fd;
    FD_SET(tempFd, &readfds);
    if (highestFd < tempFd) highestFd = tempFd;
    FD_SET(tempFd2, &readfds);
    if (highestFd < tempFd2) highestFd = tempFd2;
    b++;
  }

  select(highestFd+1, &readfds, NULL, NULL, &tv); 

  /*
   *  Check all connections for readability.
   *  First check Local FD's.
   *  If the connection is closed on either side,
   *  shutdown both sockets, and clean up.
   *  Otherwise, send the data from local->remote, or
   *  remote->local.
   */

  b = connectionsList.begin();
  while(b != connectionsList.end())
  { 
    tempFd = (*b)->localSocket->fd;
 
    if (FD_ISSET(tempFd, &readfds))
    { 
      tempBuf = (*b)->localSocket->read();
      if ((tempBuf[0] == 0)) // Connection closed.
      {
        close((*b)->localSocket->fd);
        close((*b)->remoteSocket->fd); 
#ifdef DEBUG
        printf("Closing FD: %i\n", (*b)->localSocket->fd);
        printf("Closing FD: %i\n", (*b)->remoteSocket->fd); 
#endif
        delete(*b);
        delCheck = 1;
        b = connectionsList.erase(b); 
      } else {
        (*b)->remoteSocket->write(tempBuf, (*b)->localSocket->lastReadSize); 
      }
    } 
 
  if (!delCheck) b++;
  delCheck = 0;
  } 

  /*
   *  Now check Remote FD's..
   */
  b = connectionsList.begin();
  while(b != connectionsList.end())
  { 
    tempFd = (*b)->remoteSocket->fd;
    if (FD_ISSET(tempFd, &readfds))
    {
      tempBuf = (*b)->remoteSocket->read();
      if ((tempBuf[0] == 0)) // Connection closed.
      {
        close((*b)->localSocket->fd);
        close((*b)->remoteSocket->fd); 
#ifdef DEBUG
        printf("Closing FD: %i\n", (*b)->localSocket->fd);
        printf("Closing FD: %i\n", (*b)->remoteSocket->fd);
#endif
        delete(*b);
        delCheck = 1;
        b = connectionsList.erase(b); 
      } else {
        (*b)->localSocket->write(tempBuf, (*b)->remoteSocket->lastReadSize);
      }
    }
  if (!delCheck) b++;
  delCheck = 0;
  } 
 
  /*
   *  Check all listeners for new connections.
   */

  a = listenerList.begin();
  while(a != listenerList.end())
  { 
    tempFd = (*a)->fd; 
    if (FD_ISSET(tempFd, &readfds))
    { 
      recieveNewConnection(*a);
    }
    a++;
  } 

}

void Bounce::recieveNewConnection(Listener* listener) {
/*
 *  recieveNewConnection.
 *  Inputs: A Listener Object.
 *  Outputs: Nothing.
 *  Process: 1. Recieves a new connection on a local port,
 *              and creates a connection object for it.
 *           2. Accepts the incomming connection.
 *           3. Creates a new Socket object for the remote
 *              end of the connection.
 *           4. Connects up the remote Socket.
 *           5. Adds the new Connection object to the
 *              connections list.
 *
 */

  Connection* newConnection = new Connection(); 
  newConnection->localSocket = listener->handleAccept();

  Socket* remoteSocket = new Socket();
  newConnection->remoteSocket = remoteSocket; 
  if(remoteSocket->connectTo(listener->remoteServer, listener->remotePort)) { 
    connectionsList.insert(connectionsList.begin(), newConnection);
  } else {
#ifdef DEBUG
    newConnection->localSocket->write("Unable to connect to remote host.\n");
#endif
    close(newConnection->localSocket->fd);
    delete(newConnection);
    delete(remoteSocket);
  } 
}
 

/*
 ****************************************
 *                                      *
 *    Listener class implementation.    *
 *                                      *
 ****************************************
 */

 
Socket* Listener::handleAccept() {
/*
 *  handleAccept.
 *  Inputs: Nothing.
 *  Outputs: A Socket Object.
 *  Process: 1. Accept's an incomming connection,
 *              and returns a new socket object. 
 */

  int new_fd = 0;
  int sin_size = sizeof(struct sockaddr_in);

  Socket* newSocket = new Socket();
  new_fd = accept(fd, (struct sockaddr*)&newSocket->address, (socklen_t*)&sin_size);
  newSocket->fd = new_fd; 
  return newSocket;
}
 
void Listener::beginListening() {
/*
 *  beginListening.
 *  Inputs: Nothing.
 *  Outputs: Nothing.
 *  Process: 1. Binds the local ports for all the
 *              Listener objects.
 *
 */

  struct sockaddr_in my_addr;
  int bindRes;
  int optval;
  optval = 1;

  fd = socket(AF_INET, SOCK_STREAM, 0); /* Check for no FD's left?! */

  my_addr.sin_family = AF_INET;
  my_addr.sin_port = htons(localPort);
  my_addr.sin_addr.s_addr = inet_addr(myVhost);
  bzero(&(my_addr.sin_zero), 8);

  setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

  bindRes = bind(fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
  if(bindRes == 0)
  {
    listen(fd, 10);
  } else { 
     /*
      *  If we can't bind a listening port, we might aswell drop out.
      */
     printf("Unable to bind to %s:%i!\n", myVhost, localPort);
     exit(0);
   } 
}

/*
 ****************************************
 *                                      *
 *     Socket class implementation.     *
 *                                      *
 ****************************************
 */


Socket::Socket() {
/*
 *  Socket Constructor.
 *  Inputs: Nothing.
 *  Outputs: Nothing.
 *  Process: Initialises member variables.
 *
 */

  fd = -1;
  lastReadSize = 0;
}

int Socket::write(char *message, int len) { 
/*
 *  write.
 *  Inputs: Message string, and lenght.
 *  Outputs: Amount written, or 0 on error.
 *  Process: 1. Writes out 'len' amount of 'message'.
 *              to this socket.
 *
 */

   if (fd == -1) return 0; 
 
   int amount = ::write(fd, message, len); 
#ifdef DEBUG
   printf("Wrote %i Bytes.\n", amount);
#endif
   return amount; 
}

int Socket::write(char *message) { 
/*
 *  write(2).
 *  Inputs: Message string.
 *  Outputs: Amount writte, or 0 on error.
 *  Process: Writes out the whole of 'message'.
 *
 */

   if (fd == -1) return 0; 
 
   int amount = ::write(fd, message, strlen(message)); 
#ifdef DEBUG
   printf("Wrote %i Bytes.\n", amount);
#endif
   return amount; 
}


int Socket::connectTo(char *hostname, unsigned short portnum) { 
/*
 *  connectTo.
 *  Inputs: Hostname and port.
 *  Outputs: +ve on success, 0 on failure.
 *  Process: 1. Connects this socket to remote 'hostname' on
 *              port 'port'.
 *
 */

  struct hostent     *hp;
 
  if ((hp = gethostbyname(hostname)) == NULL) { 
     return 0; 
  }          

  memset(&address,0,sizeof(address));
  memcpy((char *)&address.sin_addr,hp->h_addr,hp->h_length);
  address.sin_family= hp->h_addrtype;
  address.sin_port= htons((u_short)portnum);

  if ((fd = socket(hp->h_addrtype,SOCK_STREAM,0)) < 0)
    return 0; 
 
  if (connect(fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
    close(fd);
    fd = -1; 
    return 0;
  } 
  return(1);
}

char* Socket::read() { 
/*
 *  read.
 *  Inputs: Nothing.
 *  Outputs: char* to static buffer containing data.
 *  Process: 1. Reads as much as possible from this socket, up to
 *              4k.
 *
 */

  int amountRead = 0;
  static char buffer[4096];

  amountRead = ::read(fd, &buffer, 4096);

  if ((amountRead == -1)) buffer[0] = '\0';
  buffer[amountRead] = '\0';

#ifdef DEBUG
  printf("Read %i Bytes.\n", amountRead);
#endif
 
  /* 
   * Record this just incase we're dealing with binary data with 0's in it.
   */
  lastReadSize = amountRead;
  return (char *)&buffer;
}

