/*
 * fwg.c - fakewingate v0.4  by ajax(ajax@mobis.com) and rsh
 * 
 * accept connections on specified port (default port 23) and display
 * generic WinGate> prompt.  Telnet to specified hostname and port number
 * and log everything to logfile (default stdout).
 *
 * Handles telnet negotiation and line buffers telnet sessions, which
 * are character buffered.
 *
 * compile: gcc -O2 -o fwg fwg.c 
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <netdb.h>

extern char *optarg;
extern int   optind;

/*
 * function declarations
 */
void signal_handler(int sig);
void negotiate_telnet(int netfd, unsigned char *buf, unsigned int size);
void appendbuf(unsigned char *dst, unsigned char *src, int size);
void cleanup(int sig);

/*
 * global variables
 */
int hflag=0,oflag=0,pflag=0,l,i,bv,listen_port=0,test,connection_start=1;
int soc_client,s,user_fd,soc_len=sizeof(struct sockaddr_in);
int dest_port=0, start_of_connection, telnet_negotiated=0; 
int debug=0; char *filename=NULL; unsigned char line[1024], enter_pressed = 0;
int clear_line=0, on=1;
FILE *outfile;

int main(int argc, char *argv[]) {
  register int op;
  u_long timeout;
  fd_set rl;
  struct sockaddr_in serv_addr, client_addr;
  char *opop,host[60];
  unsigned char rbuf[1024];
  struct hostent *he;
  while ( (op = getopt(argc, argv, "o:p:h")) != EOF ) {
     switch (op) {
        case 'h': ++hflag;
                  printf ("FakeWingate v0.02 (c) 1998 by ajax\n");
                  printf ("usage: %s [-o logfile] [-p port] [-h]\n",argv[0]);
                  exit(0);
                  break;
        case 'p': ++pflag;
                  listen_port=atoi(optarg);
                  fprintf(stderr,"[%s]: Using port %d\n",argv[0],listen_port);
                  break;                                     /* port */
        case 'o': ++oflag;
                  filename=optarg;
                  if ((outfile = fopen(optarg, "a")) == NULL) {
                     fprintf(stderr,
                             "[%s]: error opening %s, using stdout\n",
                             argv[0], optarg);
                     outfile = stdout;
                  } else {
                      fprintf(stderr,"[%s]: Using output file: %s\n",
                              argv[0],optarg);
                  }

                  break;                                    /*outfile */
     }
  }
  if (!filename) { 
     fprintf(stderr,"[%s]: Using output file: stdout\n",argv[0]);
     outfile=stdout;
  } 
  if (!pflag) { /* no output file specified, use stdout */
     listen_port = 23;
     fprintf(stderr,"[%s]: Using port %d\n",argv[0],listen_port);
  }


  signal(SIGCHLD, signal_handler);
  signal(SIGINT,  cleanup);
  signal(SIGTERM, cleanup);
  signal(SIGQUIT, cleanup);

  if((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1) { /* open tcp socket */
    fprintf(stderr,"error opening socket.\n");
    exit(1);
  }
  /*
   * Set the socket option for local address reuse.
   * If we do not set this option, further attempts to reuse this port
   * will fail until it times out.  annoying.
   */
  (void) setsockopt(s,SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
  client_addr.sin_family=AF_INET;
  client_addr.sin_port=htons(listen_port);
  client_addr.sin_addr.s_addr=htonl(INADDR_ANY);

  if(bind(s,(struct sockaddr *)&client_addr,sizeof(client_addr)) == -1) {
    fprintf(stderr,"error bind() local prot addr.\n");
    exit(1);
  }
  
  if(listen(s,5) == -1) {
    fprintf(stderr,"error setting backlog.\n");
    exit(1);
  }
  
  for(;;) {
    if((soc_client=accept(s,NULL,&soc_len)) == -1) {
      fprintf(stderr,"error calling accept()\n");
      exit(1);
    }
    if(fork()==0) {
      close(s); 
      again:
      write(soc_client,"\nWinGate> ",10);
      bv=read(soc_client,rbuf,1024);
      rbuf[bv]='\0';    /* add null byte to end */
      if (strstr(rbuf,"quit")) { shutdown(soc_client,2);close(s); exit(0); }
      sscanf(rbuf,"%s %d",host,&dest_port); 
      if (dest_port==0) { dest_port = 23; }
      fprintf(outfile,"\n---[ host:%s, port:%d\n",host,dest_port);
      if((serv_addr.sin_addr.s_addr=inet_addr(host)) == -1) {
        if((he=gethostbyname(host)) == NULL) {
          write(soc_client,"error resolving hostname",24);
          goto again;
        }
        memcpy(&serv_addr.sin_addr.s_addr,he->h_addr,
               sizeof(serv_addr.sin_addr.s_addr));
      }
  
      serv_addr.sin_family=AF_INET;
      serv_addr.sin_port=htons(dest_port);

      if((user_fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1) {
        fprintf(stderr,"error allocating socket.\n");
        goto again;
      }

      if(connect(user_fd,(struct sockaddr *)&serv_addr,
         sizeof(serv_addr)) == -1) 
      {
        write(soc_client,"invalid address\n",16);
        goto again;
      }

      // We dont need to display to caller what host and port they are going to
      // snprintf(rbuf,sizeof(rbuf),"\n---[ port: %d host: %s]\n\n",(port==0) ? 23 : port,host);
      // write(n,rbuf,strlen(rbuf));

      FD_ZERO(&rl);
      start_of_connection = 1;
      telnet_negotiated = 0;    /* reset this flag */
      memset(line, '\0',1024);  /* zero out buffer used by appendbuf() */

      for(;;) { 
        FD_SET(user_fd,&rl);
        FD_SET(soc_client,&rl);       
 
        if(select((user_fd>soc_client) ? user_fd+1 : soc_client+1,&rl,NULL,NULL,NULL) == -1) {
          fprintf(stderr,"select() error\n");
          exit(1);
        }
  
        if(FD_ISSET(soc_client,&rl)) {
          if((bv=read(soc_client,rbuf,1024)) <= 0) {
            write(soc_client,"\r\nConnection closed.\r\n",22);
            shutdown(user_fd,2); shutdown(soc_client,2);
            exit(1);
          }
          rbuf[bv]='\0';
          if(debug) printf ("debug: bv=%d, rbuf=%s\n",bv,rbuf);
          /*
           * due to the fact that telnetd may be running on any port,
           * we should check all connections at the start_of_connection.
           */
          if (start_of_connection) {
             negotiate_telnet(soc_client, rbuf, bv);
             start_of_connection = 0;  /* reset this flag */
          }
          /*if (strchr(rbuf,'^') {
           *  write(soc_client,"Connection terminated.\n",25);
           *  shutdown(user_fd,2); shutdown(soc_client,2);
           *  exit(1);
           * }
           */

          /*
           * log user input
           */

          /*
           * If the telnet_negotiated flag is set, this means
           * the current connection we are in is a telnet session.
           * Each character needs to be buffered up to a ^M (0x0d)
           * and then logged after a ^M is recieved.  Without this,
           * we would have each character logged on an individual line.
           */
          if (telnet_negotiated) {
             if (clear_line) { memset(line,'\0',1024); clear_line=0; } 
             appendbuf(line, rbuf, strlen(rbuf));
             if (enter_pressed) {
                fprintf(outfile,"\n%s:%s",host,line);
                enter_pressed = 0;
             }
          } else {
	     fprintf(outfile,"\n%s:%s",host,rbuf);
          }
          write(user_fd,rbuf,strlen(rbuf));
        }
       
        if(FD_ISSET(user_fd,&rl)) {
          if((bv=read(user_fd,rbuf,1024)) <= 0) {
              write(soc_client,"\n\rConnection closed.\n",20);
              shutdown(user_fd,2); shutdown(soc_client,2);
              exit(1);
          }
          rbuf[bv]='\0';
          // fprintf(outfile,"\nsoc_client:%s:%s",host,rbuf);
          write(soc_client,rbuf,strlen(rbuf));
        }
      }
  } } 
  fclose(outfile);
  exit(0);
}       


void signal_handler(int sig) {
  while(waitpid(-1,NULL,WNOHANG) > 0)
    ; 
}

void cleanup (int sig) {
  printf("Caught signal %d, exiting.\n",sig);
  shutdown(user_fd,2); shutdown(soc_client,2);
  close(user_fd); close(soc_client);
  exit(0); 
}



void appendbuf(unsigned char *dst, unsigned char *src, int size)
{

    while(*dst) *dst++; /* seek to end of string */
    while(*src && *src!=0x0d && size--) *dst++ = *src++;
    if (*src==0x0d) { enter_pressed=1; clear_line=1; }

}


void negotiate_telnet (int netfd, unsigned char *buf, unsigned int size)
{
  static unsigned char obuf [4];  /* tiny thing to build responses into */
  register int x;
  register unsigned char y;
  register unsigned char * p;

  y = 0;
  p = buf;
  x = size;
  while (x > 0) {
    if (*p != 255)                      /* IAC? */
      goto notiac;
    obuf[0] = 255;
    p++; x--;
    if ((*p == 251) || (*p == 252))     /* WILL or WONT */
      y = 254;                          /* -> DONT */
    if ((*p == 253) || (*p == 254))     /* DO or DONT */
      y = 252;                          /* -> WONT */
    if (y) {
      obuf[1] = y;
      p++; x--;
      obuf[2] = *p;                     /* copy actual option byte */
      telnet_negotiated = 1;            /* set the telnet_negotiation flag */
      (void) write (netfd, obuf, 3);
      y = 0;
    } /* if y */
notiac:
    p++; x--; 
  } /* while x */
} /* negotiate_telnet */

