/
roa/
roa/lib/boards/
roa/lib/config/
roa/lib/edits/
roa/lib/help/
roa/lib/misc/
roa/lib/plrobjs/
roa/lib/quests/
roa/lib/socials/
roa/lib/www/
roa/lib/www/LEDSign/
roa/lib/www/LEDSign/fonts/
roa/lib/www/LEDSign/scripts/
roa/src/s_inc/
roa/src/sclient/
roa/src/sclient/binary/
roa/src/sclient/text/
roa/src/util/
/************************************************************************
	Realms of Aurealis 		James Rhone aka Vall of RoA

cli_route.c				This file represents a small prog
					which runs along with the mud to
					gather client connection requests
					and route them to the mud via a 
					local socket, each client connection
					will have a seperate process assoc.
					with it to eliminate stress on the 
					main mud process.

					The mud will spawn off the master
					cli_router process during bootup
					passing it the port to monitor.
				
					The router will then monitor that
					port and upon connection attempt, 
					it will fork off a child router and
					establish a unix socket connection
					to the main mud and begin routing
					data between the main mud and the
					client connection.  The parent
					will continue to monitor the public
					client port (usually mud port+1).

		This design was adopted after a couple of other designs
	ended up not working out very well under heavier loads.  This design
	allows the mud to assume that there will be hardly any delays between
	itself and clients.  The lag will be felt at the router's end, if any
	exists.  Previous designs called for the main mud to fork itself off
	everytime a client stream needed to be sent so the rest of the mud
	wouldnt be lagged.  Forking a 20 meg process 8 or 9 times simultaneously
	while tracking memory with dmalloc is not a healthy thing to do and
	often confused dmallco to the point that it would give up.  Thus, this
	the latest design I have come up with...

		******** 100% Completely Original Code ********
		*** BE AWARE OF ALL RIGHTS AND RESERVATIONS ***
		******** Heavily modified and expanded ********
		        All rights reserved henceforth. 

    Please note that no guarantees are associated with any code from
Realms of Aurealis.  All code which has been released to the general
public has been done so with an 'as is' pretense.  RoA is based on both
Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well
as the RoA license.   *** Read, Learn, Understand, Improve ***
*************************************************************************/
#include "sysdep.h"
#include "conf.h"

#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <arpa/telnet.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>

#include "structures.h"
#include "utils.h"
#include "clicomm.h"

// prototypes
void	watch(int port);
int	init_socket(int port);
void	nonblock(int s);
int     reaper();
int     goodnight();

// globals...
extern int errno;
extern int MASTER_DESCRIPTOR;
int	mother; 	/* global mother descriptor */
BOOL done = FALSE;
BOOL child = FALSE;
char local_sockname[64];
char buf[MAX_STRING_LENGTH];

int main(int argc, char **argv)
{
  int	port;

  if (argc != 2)
  {
    fprintf(stderr, "%s: Usage: %s <port#>\n",*argv, *argv);
    exit(-1);
  }

  if ((port = atoi(argv[1])) <= 1024) 
  {
    fprintf(stderr, "%s: Illegal port: %d\n", *argv, port);
    exit(-1);
  }

  // set up our af_unix socket name based on parent's pid...
  sprintf(local_sockname, "/tmp/rcli_sock_%d", getppid());
  fprintf(stderr, "cli_router: Local socketname set to %s.\n", local_sockname);

  watch(port);

  return 1;
}

int reaper()
{
  int status;
  int pid;

  log("SIGCHLD received.\n");

  while ((pid = wait3(&status, WNOHANG, (struct rusage *)0)) > 0)
    fprintf(stderr, "cli_router: Child %d exited.\n", pid);

  // reset handler...
  signal (SIGCHLD, (void *) reaper);

  return 1;
}

int goodnight()
{
  log("Signal received.  Closing master descriptor and exiting.\n");
  close(mother);

  if (child)
  {
    done = TRUE;
    return 1;
  }

  // kill all close all...
  exit(3);
}

// generic internet socket creation function...
int passivesock(char *service, char *protocol, int qlen)
{
  struct servent *pse;
  struct protoent *ppe;
  struct sockaddr_in sin;
  int s, type;
  u_short portbase = 0;

  bzero((char *)&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;

  if ((pse = getservbyname(service, protocol)))
    sin.sin_port = htons(ntohs((u_short)pse->s_port) + portbase);
  else
  if ((sin.sin_port = htons((u_short)atoi(service))) == 0)
  {
    fprintf(stderr, "cli_router: Can't get %s service entry.\n",service);
    exit(-1);
  }

  if ((ppe = getprotobyname(protocol)) == 0)
  {
    fprintf(stderr, "cli_router: Can't get %s proto entry.\n",protocol);
    exit(-1); 
  }

  if (!strcmp(protocol, "udp"))
    type = SOCK_DGRAM;
  else
    type = SOCK_STREAM;

  s = socket(PF_INET, type, ppe->p_proto);
  if (s < 0)
  {
    fprintf(stderr, "cli_router: Can't create socket.\n");
    exit (-1);
  }

  if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
  {
    fprintf(stderr, "cli_router: Can't bind socket.\n");
    exit (-1);
  }

  if (type == SOCK_STREAM && listen(s, qlen) < 0)
  {
    fprintf(stderr, "cli_router: Can't listen on stream socket.\n");
    exit (-1);
  }

  return s;
}

int passiveTCP(char *service, int qlen)
{
  return passivesock(service, "tcp", qlen);
}

void log(char *buf)
{
  fprintf(stderr, "cli_router: %s\n", buf);
  fflush(stderr);
}

int myread(int sok, char *trans, int length)
{
 int cnt, sofar;

  sofar = 0;
  while (sofar < length)
  {
    cnt = read(sok, trans + sofar, length - sofar);
    if (cnt < 0)
    {
      if (errno == EAGAIN)
      {
#ifdef DEBUG_MAX
        sprintf(buf, "%d out of %d bytes read...", sofar, length);
        log(buf);
#endif
        continue;
      }
      else
        return cnt;
    }
    else
    if (!cnt)
      return cnt;

    sofar += cnt;

#ifdef DEBUG_MAX
    sprintf(buf, "%d out of %d bytes read...", sofar, length);
    log(buf);
#endif
  }

  return sofar;
}

int mywrite(int sok, char *trans, int length)
{
  int cnt, sofar;

  sofar = 0;
  while (sofar < length)
  {
    cnt = write(sok, trans + sofar, length - sofar);
    if (cnt < 0)
    {
      if (errno == EAGAIN)
      {
#ifdef DEBUG_MAX
        sprintf(buf, "%d out of %d bytes wrote...", sofar, length);
        log(buf);
#endif
        continue;
      }
      else
        return cnt;
    }
    else
    if (!cnt)
      return cnt;

    sofar += cnt;

#ifdef DEBUG_MAX
    sprintf(buf, "%d out of %d bytes wrote...", sofar, length);
    log(buf);
#endif
  }

  return sofar;
}

// read in a data block from this descriptor
// this allocs memory for the data block, BTW
int read_dblock(void **blk_ptr, int desc)
{
 int size;
 int nbytes;
 char *msg;
 void (*func)();

 func = signal(SIGPIPE, SIG_IGN);

 errno = 0;

  // error check here...
  nbytes = myread(desc, (char *)&size, sizeof(int));
  if (nbytes != sizeof(int))
  {
#ifdef DEBUG_MAX
    printf("ERROR: %d bytes out of %d read on desc: %d\n", nbytes, sizeof(int), desc);
    perror("hmm:");
#endif
    return -1;
  }

#ifdef DEBUG_MAX
  sprintf(buf, "Allocating for %d size...", size);
  log(buf);
#endif

  if (!size)
  {
    log("SYSERR: read_dblock, 0 bytes read.");
    return -1;
  }

  msg = (char *)malloc(size);
  if (!msg)
  {
    log("SYSERR: Read error in read_dblock...out of memory.");
    return -1;
  }

  nbytes = myread(desc, msg, size);
  if (nbytes != size)
  {
#ifdef DEBUG_MAX
    printf("ERROR: %d bytes out of %d read on desc: %d\n", nbytes, size, desc);
    perror("hmm:");
#endif
    return -1;
  }

  // else... we read the right size...
  *blk_ptr = msg;

  signal(SIGPIPE, func);
  return nbytes;
}

// send a dblock over this descriptor
int write_dblock(dblock *blk, int desc)
{
  int size, bufsize, retval = -1, nbytes = 0;
  char *sndbuf;
  void (*func)();

  func = signal(SIGPIPE, SIG_IGN);

  size    = sizeof(dblock) + blk->streamlen;
  bufsize = size + sizeof(int);

  CREATE(sndbuf, char, bufsize);
  if (!sndbuf)
  {
    log("write_dblock out of memory...?");
    return -1;
  }

  // assemble the three pieces... header, block, stream
  memcpy(sndbuf, (void *) &size, sizeof(int));
  memcpy(&(sndbuf[sizeof(int)]), blk, sizeof(dblock));
  memcpy(&(sndbuf[sizeof(int)+sizeof(dblock)]), blk->stream, blk->streamlen);

  nbytes = mywrite(desc, sndbuf, bufsize);
  if (nbytes != bufsize)
  {
    FREENULL(sndbuf);
    sprintf(buf, "SYSERR: %d bytes out of %d wrote on desc: %d\n", nbytes, bufsize, desc);
    log(buf);
    perror("write_dblock:");
    return -1;
  }
  else
    retval = size;

  FREENULL(sndbuf);
  signal(SIGPIPE, func);
  return retval;
}

// have to deep free this one
void wax_dblock(dblock *blk)
{
  if (blk)
  {
    FREENULL(blk->stream);
    FREENULL(blk);
  }
}

// create an AF_UNIX socket connection to main mud listening to
// socket file /tmp/roa_cli_socket (LOCAL_SOCKNAME)
void interface_to_roa(int sok)
{
  struct sockaddr_un myadr_un;
  struct hostent *sp;
  int addrlen, mud, cnt, opt, nread, nwrite;
  fd_set iset, eset;
  struct timeval time;
  int connected = FALSE;
  dblock *dblk;

  addrlen = sizeof(struct sockaddr_un);
  memset((char *)&myadr_un, 0, addrlen);
  myadr_un.sun_family = AF_UNIX;
  strcpy(myadr_un.sun_path, local_sockname);

  mud = socket(AF_UNIX, SOCK_STREAM, 0);
  if (mud < 0) 
  {
    fprintf(stderr, "cli_router: Can't create AF_UNIX socket to main mud...\n");
    exit(-1);
  }

  nonblock(sok);

  // basically, sit in a select until activity...
  time.tv_usec = 0;
  time.tv_sec = 0;

  // now select on both sockets... first one in the gate gets privs...
  while (!done)
  {
    FD_ZERO(&iset);
    FD_ZERO(&eset);
    FD_SET(mud, &iset);
    FD_SET(sok, &iset);

    if (select((mud > sok ? mud : sok) + 1, &iset, NULL, &eset, NULL) < 0)
    {
      fprintf(stderr, "cli_router: Error in select...\n");
      exit(-1);
    }

    if (FD_ISSET(mud, &eset) || FD_ISSET(sok, &eset))
    {
      fprintf(stderr, "cli_router: Socket in exception set, exiting.\n");
      exit(-1);
    }

    // if the client has sent us something...
    if (FD_ISSET(sok, &iset))
    {
      // read a chunk from the client cok
      if (read_dblock((void*)&dblk, sok) <= 0)
      {
        fprintf(stderr, "cli_router: Error reading from client sok, exiting.\n");
        wax_dblock(dblk);
        exit(-1);
      }

      // if we have a stream, offset the pointer correctly...
      if (dblk->streamlen > 0)
        dblk->stream = (void *) &dblk[1];

      if (!connected)
      {
        if (connect(mud, (struct sockaddr *)&myadr_un, addrlen) < 0)
        {
          fprintf(stderr, "cli_router: Can't connect to main mud unix socket...\n");
          exit(-1);
        }

        nonblock(mud);
        connected = TRUE;
        fprintf(stderr, "cli_router: Child %d connected to main mud.\n", getpid());
      }

      // now write that chunk to the mud...
      if (write_dblock(dblk, mud) <= 0)
      {
        fprintf(stderr, "cli_router: Error writing to mud, exiting.\n");
        wax_dblock(dblk);
        exit(-1);
      }

      // ok, we send it on over, now destroy it
      wax_dblock(dblk);
    }

    // if the mud has sent us something...
    if (FD_ISSET(mud, &iset) && connected)
    {
      // read a chunk from the mud
      if (read_dblock((void*)&dblk, mud) <= 0)
      {
        fprintf(stderr, "cli_router: Error reading from mud, exiting.\n");
        wax_dblock(dblk);
        exit(-1);
      }

      // if we have a stream, offset the pointer correctly...
      if (dblk->streamlen > 0)
        dblk->stream = (void *) &dblk[1];

      // now write that dblock to the client...
      if (write_dblock(dblk, sok) <= 0)
      {
        log("Error writing to client sok, exiting.");
        wax_dblock(dblk);
        exit(-1);
      }

      // ok, we send it on over, now destroy it
      wax_dblock(dblk);
    }
  }

  // ok we must be done...
  close(sok);
  close(mud);
  fprintf(fprintf, "cli_router: Child %d connection closed.\n", getpid());
  exit(3);
}

void	watch(int port)
{
   int con;

   signal (SIGCHLD, (void *) reaper);
   signal (SIGINT,  (void *) goodnight);
   signal (SIGTERM, (void *) goodnight);
   signal (SIGHUP, (void *) goodnight);
   signal (SIGABRT, (void *) goodnight);

   mother = init_socket(port);
   fprintf(stderr, "cli_router: Master socket opened on port %d.\n",port);

   // sleep on accept... accept and fork off
   for (;;) 
   {
     con = accept(mother, (struct sockaddr *) 0, 0); 
     if (con < 0) continue;

     // if we're the child...
     if (!fork())
     {
       fprintf(stderr, "cli_router: Connection detected, child %d forked.\n",getpid());
       child = TRUE;

       // child ignores sig child
       signal (SIGCHLD, SIG_IGN);

       // no need for mother descriptor in child
       close(mother);

       // start gabbin with mud and client...
       interface_to_roa(con);

       close(con);
       exit(1);
     }
     else  // parent just close the socket...
       close(con);
   }
}

// initialize the socket we're gonna listen to for client connections...
// uses our own hostname...
int	init_socket(int port)
{
   int	s;
   char	*opt;
   char	hostname[1024];
   struct sockaddr_in sa;
   struct hostent *hp;

   bzero(&sa, sizeof(struct sockaddr_in ));
   gethostname(hostname, 1023);

   hp = gethostbyname(hostname);
   if (!hp)
   {
      perror("gethostbyname");
      exit(-1);
   }

   sa.sin_family = hp->h_addrtype;
   sa.sin_port	 = htons(port);

   s = socket(AF_INET, SOCK_STREAM, 0);
   if (s < 0) 
   {
      perror("Init-socket");
      exit(-1);
   }

   if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) 
   {
      perror("bind");
      close(s);
      exit(-1);
   }

   if (listen(s, 5) < 0)
   {
      perror("listen");
      close(s);
      exit(-1);
   }

   return(s);
}

// stop a socket from blocking on read/write...
void	nonblock(int s)
{
   int	flags;
   flags = fcntl(s, F_GETFL);
   flags |= O_NONBLOCK;
   if (fcntl(s, F_SETFL, flags) < 0) {
      perror("Fatal error executing nonblock (comm.c)");
      exit(1);
   }
}