/* terrisproxy.c
 *
 * Terris Proxy
 */

const char *remote_host = "217.29.193.122";
const int  remote_port = 31000;
/* the wizard header */
char *header = "\x1b\x57\x4d\x34\x0a";

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>

/* Ansi characters */

#define BEEP_CHAR     '\a'
#define ESC_CHAR      '\x1B'

#define ANSI_BEGIN   "\x1B["

#define ANSI_NORMAL   "\x1B[0m"

#define ANSI_HILITE   "\x1B[1m"
#define ANSI_INVERSE  "\x1B[7m"
#define ANSI_BLINK    "\x1B[5m"
#define ANSI_UNDERSCORE "\x1B[4m"

#define ANSI_INV_BLINK         "\x1B[7;5m"
#define ANSI_INV_HILITE        "\x1B[1;7m"
#define ANSI_BLINK_HILITE      "\x1B[1;5m"
#define ANSI_INV_BLINK_HILITE  "\x1B[1;5;7m"

/* Foreground colors */

#define ANSI_BLACK      "\x1B[30m"
#define ANSI_RED        "\x1B[31m"
#define ANSI_GREEN      "\x1B[32m"
#define ANSI_YELLOW     "\x1B[33m"
#define ANSI_BLUE       "\x1B[34m"
#define ANSI_MAGENTA    "\x1B[35m"
#define ANSI_CYAN       "\x1B[36m"
#define ANSI_WHITE      "\x1B[37m"

/* Background colors */

#define ANSI_BBLACK     "\x1B[40m"
#define ANSI_BRED       "\x1B[41m"
#define ANSI_BGREEN     "\x1B[42m"
#define ANSI_BYELLOW    "\x1B[43m"
#define ANSI_BBLUE      "\x1B[44m"
#define ANSI_BMAGENTA   "\x1B[45m"
#define ANSI_BCYAN      "\x1B[46m"
#define ANSI_BWHITE     "\x1B[47m"

#define BUFFER_SIZE 4096

int sendall(int s,char *buf,int len);
void process_loop(int ifd);
char *channels[256];
char *ansi_table[256];

void init_ansi() {
  int i;
  static char *blank = "";
  for (i=0;i<256;i++)
    ansi_table[i] = blank;
  ansi_table['r'] = ANSI_RED; /* Red */
  ansi_table['R'] = ANSI_RED ANSI_HILITE;
  ansi_table['b'] = ANSI_BLUE; /* Blue */
  ansi_table['B'] = ANSI_BLUE ANSI_HILITE;
  ansi_table['c'] = ANSI_CYAN; /* Cyan */
  ansi_table['C'] = ANSI_CYAN ANSI_HILITE;
  ansi_table['g'] = ANSI_GREEN; /* Green */
  ansi_table['G'] = ANSI_GREEN ANSI_HILITE;
  ansi_table['o'] = ANSI_RED ANSI_HILITE; /* Orange, but we don't have */
  ansi_table['O'] = ANSI_RED ANSI_HILITE; /* that for ANSI */
  ansi_table['v'] = ANSI_MAGENTA; /* Violet. Close enough */
  ansi_table['V'] = ANSI_MAGENTA;
  ansi_table['y'] = ANSI_YELLOW; /* Yellow */
  ansi_table['Y'] = ANSI_YELLOW ANSI_HILITE;
  ansi_table['s'] = ANSI_YELLOW; /* Sienna is brown, as is nohilite yellow */
  ansi_table['S'] = ANSI_YELLOW ANSI_HILITE;
  ansi_table['p'] = ANSI_MAGENTA ANSI_HILITE; /* Pink */
  ansi_table['P'] = ANSI_MAGENTA ANSI_HILITE; /* Pink */
  ansi_table['N'] = ANSI_NORMAL;
  ansi_table['K'] = ANSI_BWHITE ANSI_RED; /* I refuse to incorporate */
                      /* Blink - it's annoying! */
  ansi_table['n'] = ANSI_NORMAL;
  ansi_table['I'] = ANSI_UNDERSCORE;
  ansi_table['i'] = ANSI_NORMAL;
  ansi_table['E'] = ANSI_UNDERSCORE;
  ansi_table['e'] = ANSI_NORMAL;
}

void init_channels() {
  int i;
  for (i=0;i<256;i++)
    channels[i] = NULL;
  channels['M'] = ANSI_YELLOW ANSI_HILITE "<Shout>" ANSI_NORMAL;
  channels['C'] = ANSI_YELLOW ANSI_HILITE "<Chat>" ANSI_NORMAL;
  channels['Q'] = ANSI_YELLOW ANSI_HILITE "<Quest>" ANSI_NORMAL;
  channels['E'] = ANSI_YELLOW ANSI_HILITE "<Event>" ANSI_NORMAL;
  channels['W'] = ANSI_YELLOW ANSI_HILITE "<Wail>" ANSI_NORMAL;
  channels['t'] = ANSI_YELLOW ANSI_HILITE "<Tell>" ANSI_NORMAL;
  channels['G'] = ANSI_YELLOW ANSI_HILITE "<GuildShout>" ANSI_NORMAL;
  channels['T'] = ANSI_YELLOW ANSI_HILITE "<TempleShout>" ANSI_NORMAL;
  channels['H'] = ANSI_YELLOW ANSI_HILITE "<HHShout>" ANSI_NORMAL;
  channels['L'] = ANSI_YELLOW ANSI_HILITE "<Login>" ANSI_NORMAL;
}

int main(int argc,char *argv[]) {
  int portnum;
  int listener, newfd;
  int fdmax = 0;
  fd_set master,read_fds;
  struct sockaddr_in myaddr,remoteaddr;
  int sin_size;
  int yes=1;
  int addrlen;
  int i,j;
  struct timeval tv;

  if (argc != 2) {
    printf( "Usage: %s <port>\n", argv[0] );
    return 1;
  }
  portnum = atoi(argv[1]);
  if (portnum == 0) {
    printf( "Usage: %s <portnum>\n", argv[0] );
    return 1;
  }
  if (fork() > 0) {
    printf( "Forking as daemon\n" );
    return 0;
  }

  init_ansi();
  init_channels();

  FD_ZERO(&master);
  FD_ZERO(&read_fds);

  listener  = socket(AF_INET, SOCK_STREAM, 0);
  if (listener == -1) {
    perror("setsockopt");
    exit(1);
  }

  myaddr.sin_family = AF_INET;
  myaddr.sin_addr.s_addr = INADDR_ANY;
  myaddr.sin_port = htons(portnum);
  memset(&(myaddr.sin_zero), '\0', 8 );
  if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
    perror("bind");
    exit(1);
  }

  if (listen(listener,10) == -1) {
    perror("listen");
    exit(1);
  }

  FD_SET(listener,&master);
  fdmax = listener;
  while (1) {
    read_fds = master;
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    if (select(fdmax+1,&read_fds,NULL,NULL,&tv) == -1) {
      perror("select");
      exit(1);
    }
    wait3(NULL,WNOHANG,NULL);
    if (FD_ISSET(listener,&read_fds)) {
      addrlen = sizeof(remoteaddr);
      if ((newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen)) == -1) {
        perror("accept");
      } else {
    if (fork() == 0) {
      close(listener);
          process_loop(newfd);
      return 0;
    } else {
      close(newfd);
    }
      }
    }
  }
  return 0;
}

void process_loop(int ifd) {
  int in_fd,out_fd;
  int fdmax;
  fd_set fds,read_fds;
  struct sockaddr_in their_addr;
  struct hostent *he;
  char inbuffer[BUFFER_SIZE+5];
  char outbuffer[BUFFER_SIZE+5];
  char incline[BUFFER_SIZE+5];
  char outline[BUFFER_SIZE+5];
  int  ini,outi;
  int nbytes;
  int i,j,k;
  in_fd = ifd;

  FD_ZERO(&fds);
  FD_ZERO(&read_fds);
  FD_SET(in_fd,&fds);

  if ((he=(struct hostent *)gethostbyname(remote_host)) == NULL) {
    perror("gethostbyname");
    exit(1);
  }

  // Create new connection, assign to out_fd and out_fds
  out_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (out_fd == -1) {
    perror("socket");
    exit(1);
  }

  their_addr.sin_family = AF_INET;
  their_addr.sin_port = htons(remote_port);
  their_addr.sin_addr = *((struct in_addr *)he->h_addr);
  memset(&(their_addr.sin_zero),'\0', 8);

  if (connect(out_fd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
    perror("connect");
    exit(1);
  }

  FD_SET(out_fd,&fds);

  fdmax = (in_fd > out_fd) ? in_fd : out_fd;

  sendall(out_fd,header,strlen(header));

  ini,outi=0;
  while (1) {
    read_fds = fds;
    if (select(fdmax+1,&read_fds,NULL,NULL,NULL) == -1) {
      perror("select");
      exit(1);
    }
    if (FD_ISSET(in_fd,&read_fds)) {
      /* TODO: have some neat hacky code to break long lines into several
       * short ones that are accepted by terris.
       */
      do {
        if ((nbytes = recv(in_fd,inbuffer,BUFFER_SIZE, 0)) <= 0) {
      exit(0);
        }
        for (i=0,k=0;i<nbytes;i++) {
      switch (inbuffer[i]) {
      case '\r':
        break; // ignore \rs... they're ugggly.
      case '\n':
        incline[ini] = 0;

        k = 0;
        for (j=0;incline[j] == ' ';j++);
        for (;j<ini;j++) {
          switch (incline[j]) {
          case '[': strcpy(outbuffer+k,"&lsb."); k += 5; break;
          case '/': strcpy(outbuffer+k,"&fs."); k += 4; break;
          case '\\': strcpy(outbuffer+k,"&bs."); k += 4; break;
          default:
            outbuffer[k++] = incline[j];
          }
        }
        outbuffer[k++] = '\r';
        outbuffer[k++] = '\n';
        outbuffer[k] = 0;

        // if (ini < 120) {
        if (1) {
              if (sendall(out_fd,outbuffer,k) < k) {
                exit(0);
              }
          k=0;
          j=0;
        } else {
          /* Too long. This is a work in progress */
          /* Line wrap code */
        }
        ini=0;
        break;
          default:
        incline[ini++] = inbuffer[i];
        incline[ini] = 0;
        break;
      }
        }
      } while (nbytes == BUFFER_SIZE);
      /* END INPUT CODE FROM PLAYER HERE */
    }
    if (FD_ISSET(out_fd,&read_fds)) {
      do {
        if ((nbytes = recv(out_fd,inbuffer,BUFFER_SIZE, 0)) <= 0) {
      exit(0);
        }
        for (i=0;i<nbytes;i++) {
      switch (inbuffer[i]) {
      case '\r':
        break; // ignore \rs... they're ugggly.
      case '\n':
        outline[outi] = 0;
        k = 0;
        j=0;
        /* Strip out Wizard Status Control lines */
        if (outline[0] == 0x1B) {
          j=k=outi=0;
          outline[0]=0;
          break;
          /* Uncomment this to see the line in hex
          strcpy(outbuffer,"Nonprint:");
          k = 9;

          for (j=0;j<outi;j++) {
            sprintf(outbuffer+k,"%.2X ",outline[j]);
            k += 3;
          }
          sprintf(outbuffer+k," (%s)", outline);
          outbuffer[k++] = '\r';
          outbuffer[k++] = '\n';
          outbuffer[k] = 0;
          outline[0]=0;
              if (sendall(in_fd,outbuffer,k) < k) {
                exit(0);
              }
          j=k=outi=0;
          break; /* */
        }

        /* channels */
        if (outline[0] == '[') {
          if (channels[outline[1]]) {
            strcpy(outbuffer,channels[outline[1]]);
            j = 3;
            k = strlen(channels[outline[1]]);
            outbuffer[k++] = ' ';
            outbuffer[k] = 0;
          } else {
            sprintf(outbuffer,"<Unkown channel '%c'> ",outline[1]);
          }
        }

        /* Let's put our tells on <Tell> too */
        if (!strncmp(outline,"You tell",8)) {
          if (channels['t']) {
            strcpy(outbuffer,channels['t']);
            j = 0;
            k = strlen(channels['t']);
            outbuffer[k++] = ' ';
            outbuffer[k] = 0;
          } else {
            sprintf(outbuffer,"<Unkown channel '%c'> ",'t');
          }
        }

        /* ansi and &lsb,&rsb,&fs,&bs */
        for (;j<outi&&k<BUFFER_SIZE;j++) {
          if (outline[j] == '^') {
            j++;
                strcpy(outbuffer+k,ansi_table[outline[j]]);
            k += strlen(ansi_table[outline[j]]);
            outbuffer[k] = 0;
          } else if (outline[j] == '&') {
        if (!strncasecmp(outline+j,"&fs.",4)) {
          outbuffer[k++] = '/';
          j += 3;
        } else if (!strncasecmp(outline+j,"&bs.",4)) {
          outbuffer[k++] = '\\';
          j += 3;
        } else if (!strncasecmp(outline+j,"&lsb.",5)) {
          outbuffer[k++] = '[';
          j += 4;
        } else {
          outbuffer[k++] = '&';
        }
          } else {
            outbuffer[k++] = outline[j];
          }
        }
        strcpy(outbuffer+k,ansi_table['N']);
        k += strlen(ansi_table['N']);
        outbuffer[k++] = '\r';
        outbuffer[k++] = '\n';
        outbuffer[k] = 0;

            if (sendall(in_fd,outbuffer,k) < k) {
              exit(0);
            }
        outi=0;
        break;
          default:
        outline[outi++] = inbuffer[i];
        outline[outi] = 0;
        break;
      }
        }
      } while (nbytes == BUFFER_SIZE);
      if (!strncmp(outline,"Account",7)) {
      k = strlen(outline);
      outline[k++] = '\n';
      outline[k] = 0;
          if (sendall(in_fd,outline,k) < k) {
            exit(0);
          }
      outi=0;
      }
      if (!strncmp(outline,"Password",8)) {
      k = strlen(outline);
      outline[k++] = '\n';
      outline[k] = 0;
          if (sendall(in_fd,outline,k) < k) {
            exit(0);
          }
      outi=0;
      }
      if (!strncmp(outline,"Persona number",14)) {
      k = strlen(outline);
      outline[k++] = '\n';
      outline[k] = 0;
          if (sendall(in_fd,outline,k) < k) {
            exit(0);
          }
      outi=0;
      }
    }
  }

  return;
}

/*
 * Make sure the entire string is sent.
 */

int sendall(int s,char *buf,int len) {
  int total;
  int bytesleft = len;
  int n;

  total = 0;
  while (total < len) {
    n = send(s,buf+total,bytesleft,0);
    if (n == -1) { break; }
    total += n;
    bytesleft -= n;
  }

  return (n<0) ? -1 : total;
}