/
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

clicomm.c			The server side file for RoAClient<->
				RoAServer communications.
				Text messages/files/binary messages/
				files (sound, graphics, etc) is all
				prepared and sent from this file.
				
		******** 100% Completely Original Code ********
		*** BE AWARE OF ALL RIGHTS AND RESERVATIONS ***
		******** 100% Completely Original Code ********
		        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 ***

V1 - original design...
V2 - improved design, prevents single client from locking up main server
     at times, but too much overhead.  Also forced recursive memory
     management between processes which dmalloc seemed to have a difficult
     time with.  Basically fork()'d for every send but still blocked on
     read from client.

V3 - Spawn a new process on bootup called cli_router to monitor the inet 
     client port.
     Create a unique unix socket local to this machine (AF_UNIX) to allow
     this new router to communicate with the main mud server.
     Router is a mini server that waits for client connection attempts.  When
     it receives one, it'll fork off another process to handle that individual
     client connection.  This way, client lag only blocks each indiviual client
     as the main mud's unix socket never hears from the router until a full
     data block has been read from a client.
     cli_route.c is the entire routing process and is much more appropriate
     to fork off rather than forking off the entire mud for every write to 
     a client.

*************************************************************************/

#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 "comm.h"
#include "interpreter.h"
#include "acmd.h"
#include "handler.h"
#include "db.h"
#include "screen.h"
#include "clicomm.h"
#include "global.h"
#include "objsave.h"
#include "darkenelf.h"

/* external functions */
extern 	void nonblock(int s);
extern 	void block(int s);
extern  void who_to_buf(char *whobuf);
extern  char *winkillr(char *str);

/* external vars */
extern	dsdata *descriptor_list;
extern	dsdata *cdesc_list;
extern  cldesc *prelim_list;
extern  char *wv_bits[];
extern struct str_app_type str_app[];

// protos
void update_client_who(void);
void update_client_eq(dsdata *d);
void update_client_inv(dsdata *d);
void update_allclient_chan(void);
void update_client_chan(dsdata *d);
void update_client_stats(BOOL combat);
void send_client_help_file(dsdata *d, char *name);
void send_client_help_index(dsdata *d);
void send_client_hitmanamove(dsdata *d);

// internal to this file
static int localport;

// set up some client options...
ACMD(do_client_options)
{
  if (IS_NPC(ch)) return;

  one_argument(argument, arg);
  if (!*arg)
  {
    send_to_char("Usage: clientopt < sound | who | eq | inv | chan | stats >.\n\r",ch);
    return;
  }

  if (is_abbrev(arg, "stats"))
  {
    TOGGLE_BIT(PLR2_FLAGS(ch), PLR2_CLIENTSTAT);
    if (PLR2_FLAGGED(ch, PLR2_CLIENTSTAT))
      send_to_char("Client will be sent character stats.\n\r",ch);
    else
      send_to_char("Client will not be sent character stats.\n\r",ch);
  }
  else
  if (is_abbrev(arg, "sound"))
  {
    TOGGLE_BIT(PLR2_FLAGS(ch), PLR2_CLIENTMUTE);
    if (PLR2_FLAGGED(ch, PLR2_CLIENTMUTE))
      send_to_char("Client will not be sent sound data.\n\r",ch);
    else
      send_to_char("Client will be sent sound data.\n\r",ch);
  }
  else
  if (is_abbrev(arg, "who"))
  {
    TOGGLE_BIT(PLR2_FLAGS(ch), PLR2_CLIENTWHO);
    if (PLR2_FLAGGED(ch, PLR2_CLIENTWHO))
      send_to_char("Client will be sent who data.\n\r",ch);
    else
      send_to_char("Client will not be sent who data.\n\r",ch);
  }
  else
  if (is_abbrev(arg, "eq"))
  {
    TOGGLE_BIT(PLR2_FLAGS(ch), PLR2_CLIENTEQ);
    if (PLR2_FLAGGED(ch, PLR2_CLIENTEQ))
      send_to_char("Client will be sent eq data.\n\r",ch);
    else
      send_to_char("Client will not be sent eq data.\n\r",ch);
  }
  else
  if (is_abbrev(arg, "inv"))
  {
    TOGGLE_BIT(PLR2_FLAGS(ch), PLR2_CLIENTINV);
    if (PLR2_FLAGGED(ch, PLR2_CLIENTINV))
      send_to_char("Client will be sent inv data.\n\r",ch);
    else
      send_to_char("Client will not be sent inv data.\n\r",ch);
  }
  else
  if (is_abbrev(arg, "chan"))
  {
    TOGGLE_BIT(PLR2_FLAGS(ch), PLR2_CLIENTCHAN);
    if (PLR2_FLAGGED(ch, PLR2_CLIENTCHAN))
      send_to_char("Client will be sent channel data.\n\r",ch);
    else
      send_to_char("Client will not be sent channel data.\n\r",ch);
  }
  else
  {
    send_to_char("Usage: clientopt < sound | who | eq | inv | chan | stats >.\n\r",ch);
    return;
  }
}

// for global client PID list, add and remove  12/5/97 -jtrhone
void insert_clpid_entry(int pid, int bitvector, dsdata *d, char *stream)
{
  clpid_info *cl;

  CREATE(cl, clpid_info, 1);
  cl->pid       = pid;
  cl->bitvector = bitvector;
  cl->d         = d;
  cl->ch        = d->character;
  cl->stream    = stream;
  
  cl->next = cl_pids;
  cl_pids = cl;
}

// when it's done downloading, reaper will call this to remove it
// yank entry from list, free memory
void delete_clpid_entry(clpid_info *cl)
{
  clpid_info *temp = NULL;

#ifdef DEBUG_MAX
  sprintf(buf, "SYSUPD: Child client PID %d removed and freed.", cl->pid);
  mudlog(buf, BUG, LEV_IMM, TRUE);
#endif

  REMOVE_FROM_LIST(cl, cl_pids, next);
  FREENULL(cl->stream);
  FREENULL(cl);
}

// when a descriptor gets extracted, wax all clpids associated with it
// 3/1/98 -jtrhone
void wax_descriptor_clpids(dsdata *d)
{
  clpid_info *cl, *clnext;

  for (cl=cl_pids; cl; cl=clnext)
  {
    clnext = cl->next;
    if (cl->d == d)
      kill(SIGTERM, cl->pid);  // the reaper will handle removing it from the list...
  }
}

// when a char gets extracted, null out all clpids associated with him/her
// 3/1/98 -jtrhone
void null_char_clpids(chdata *ch)
{
  clpid_info *cl;

  for (cl=cl_pids; cl; cl=cl->next)
    if (cl->ch == ch)
      cl->ch = NULL;
}

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;
}

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

// after V3 update, no longer forked here  5/21/98 -jtrhone
int send_dblock(dblock *blk, dsdata *d)
{
  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)
  {
    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(d->cdesc, sndbuf, bufsize);
  if (nbytes != bufsize)
  {
    FREENULL(sndbuf);
    sprintf(buf, "SYSERR: %d bytes out of %d wrote on d->cdesc: %d\n", nbytes, bufsize, d->cdesc);
    log(buf);
    perror("send_dblock:");
    return -1;
  }
  else
    retval = size;

  FREENULL(sndbuf);

  signal(SIGPIPE, func);
  return retval;
}

// send a TEXT_MESG to client... V2 2/13/98 -jtrhone
int send_msg_to_client(char *msg, dsdata *d)
{
  dblock blk;

  // assemble the block...
  blk.type      = TEXT_MESG;
  blk.version   = 2;
  strcpy(blk.str1, msg);
  strcpy(blk.str2, "");
  blk.streamlen = 0;
  blk.stream    = NULL;
  return send_dblock(&blk, d);
}

// fork off a client router... 8/4/98 -jtrhone
void start_cli_router(void)
{
  char sport[64];
  dsdata *d;
  extern  int MASTER_DESCRIPTOR;
  extern  int CLIENT_DESCRIPTOR;

  // flush everything before fork
  fflush(NULL);

  // before we do anything, fork off a client router...
  switch ((cli_router_pid = fork())) {
    case -1:
      log("Error forking off cli_router...");
      exit(-1);
   
    case 0: // child
      close(MASTER_DESCRIPTOR);
      close(CLIENT_DESCRIPTOR);

      // close every descriptor in this child..
      Descriptors(d)
        close(d->descriptor);

      sprintf(sport, "%d", localport);
      execl("../bin/cli_router", "cli_router", sport, NULL);
      log("Error in execl() for cli_router!");
      exit(-1);

    default:
      sprintf(buf, "SYSUPD: cli_router %d started.", cli_router_pid);
      mudlog(buf, BRF, LEV_IMM, TRUE);
      break;
  }
}

// sets up the client descriptor - creates the socket, binds it, and listens.
int init_client_socket(int port)
{
  int s, opt;
  struct sockaddr_un sa;
 
  // remember this port this runtime...
  localport = port;

  // fork off a router first...
  start_cli_router();

  // create local socket name based on pid...
  sprintf(local_sockname, "/tmp/rcli_sock_%d", getpid());

  if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 
  {
    perror("Create client socket");
    exit(1);
  }

#if defined(SO_SNDBUF)
  opt = LARGE_BUFSIZE + GARBAGE_SPACE;
  if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *) &opt, sizeof(opt)) < 0) {
    perror("setsockopt SNDBUF");
    exit(1);
  }
#endif

#if defined(SO_REUSEADDR)
  opt = 1;
  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0) {
    perror("setsockopt REUSEADDR");
    exit(1);
  }
#endif

#if defined(SO_LINGER)
  {
    struct linger ld;

    ld.l_onoff = 0;
    ld.l_linger = 0;
    if (setsockopt(s, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld)) < 0) {
      perror("setsockopt LINGER");
      exit(1);
    }
  }
#endif

  sa.sun_family = AF_UNIX;
  strcpy(sa.sun_path, local_sockname);

  if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
    perror("client bind");
    close(s);
    exit(1);
  }
  nonblock(s);
  listen(s, 5);
  return s;
}

// 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;
}

// given a pword and name, locate descriptor in game...
// if cant find one... send message, then return NULL...
dsdata *get_char_desc(char *name, char *pword, char *res)
{
  chdata *ch;
  BOOL check_password(dsdata *d, char *arg, BOOL main_server);

  // first get the character with that name...
  if (!(ch = get_char(name)))
  {
    sprintf(res, "Unable to locate %s on main server...", name);
    return NULL;
  }

  if (!ch->desc)
  {
    sprintf(res, "%s is linkless on main server...", name);
    return NULL;
  }

  if (!check_password(ch->desc, pword, FALSE))
  {
    sprintf(res, "Incorrect password for %s...", name);
    return NULL;
  } 

  return ch->desc;
}

// set up client descriptor
// V2 changes	- 2/12/98 -jtrhone
//		- now, expect login block immediately... validate name and pword...
//		- if ok, send a text message saying welcome
// no longer request read of login_request immediately, now just insert this
// descriptor into our prelim client list to select on it 
// when select on this desc gets triggered, we'll read login  5/20/98 -jtrhone
int new_client_descriptor(dsdata *newd, int desc)
{
  if (newd->cdesc > 0)
    close_client_socket(newd);

  newd->cdesc = desc;

  /* prepend to our list of client descriptors */
  newd->next_client = cdesc_list;
  cdesc_list = newd;

  if (newd->character)
  {
    sprintf(buf, "SYSUPD: %s established RoAClient connection.", GET_NAME(newd->character));
    mudlog(buf, BUG, MAX(LEV_IMM, GET_INVIS_LEV(newd->character)), TRUE);
  }

  send_welcome_to_client(newd);
  send_policies_to_client(newd);
  send_soundfile_to_client(newd, "hero.mid");

  return 0;
}

// just insert this descriptor into our list of prelim clients then move on
// 5/20/98 -jtrhone
int new_prelim_socket(int s)
{
  int desc,i;
  struct sockaddr_un peer;
  cldesc *c;

  /* accept the new connection */
  i = sizeof(peer);
  if ((desc = accept(s, (struct sockaddr *) &peer, &i)) < 0) {
    perror("prelim client accept");
    return -1;
  }

  nonblock(desc);

  // now, allocate a prelim struct and insert it
  CREATE(c, cldesc, 1);
  c->desc = desc;

  /* prepend to our list of client descriptors */
  c->next = prelim_list;
  prelim_list = c;

#ifdef DEBUG
  mudlog("SYSUPD: Preliminary client connection accepted.", BUG, LEV_IMM, TRUE);
#endif
  return 0;
}

/* something happened, close the client descriptor and yank it */
void close_client_socket(dsdata *d)
{
  dsdata *temp;

  close(d->cdesc);
  d->cdesc = -1;

  // if they have pending clpids, kill em all... 3/1/98 -jtrhone
  wax_descriptor_clpids(d); 

  if (d->character) 
  {
    sprintf(buf, "SYSUPD: %s lost client connection.",GET_NAME(d->character));
    mudlog(buf, BUG, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
  } 
  else
    mudlog("Losing client connection without char.", CMP, LEV_IMM, TRUE);

  REMOVE_FROM_LIST(d, cdesc_list, next_client);
}

/* something happened, close the prelim client descriptor and yank it */
void close_prelim_socket(cldesc *c)
{
  cldesc *temp;

  close(c->desc);
  c->desc = -1;

  REMOVE_FROM_LIST(c, prelim_list, next);
  FREENULL(c);
}

// sit and wait for a socket to become available from another process
// this uses a file for a locking mechanism... 12/3/97 -jtrhone
void request_socket_lock(int sok)
{
  FILE *fp;
  struct stat s;
  char fname[MAX_INPUT_LENGTH];

  sprintf(fname, "misc/%d.soklock", sok);

  // keep trying until its not there
  while (!stat(fname, &s))
  {

#ifdef DEBUG_MAX
  sprintf(buf, "SYSUPD: Server %d waiting for socket unlock: %d.",getpid(), sok);
  log(buf);
#endif

    sleep(1); 
  }

  // OK, make our own lock...
  if (!(fp = fopen(fname, "w")))
    log("REQUEST SOCKET LOCK FAILED TO WRITE FILE.");
  else
    fclose(fp);

#ifdef DEBUG_MAX
  sprintf(buf, "SYSUPD: Server %d locked socket: %d.",getpid(), sok);
  log(buf);
#endif
}

// simply removes the file representing the socket lock
void unlock_socket(int sok)
{
  char fname[MAX_INPUT_LENGTH];

  sprintf(fname, "misc/%d.soklock", sok);
  unlink(fname);

#ifdef DEBUG_MAX
  sprintf(buf, "SYSUPD: Server %d unlocked socket: %d.",getpid(), sok);
  log(buf);
#endif
}

// nice little send package...  2/12/97 -jtrhone
int send_message(dblock *h, dsdata *d, BOOL block)
{
  int retval = 1;
  int sok = d->cdesc;

  // sleep until we can write to it...
  if (block)
    request_socket_lock(sok);

  // ok... our turn
  retval = send_dblock(h, d);

  // let em know it's free...
  if (block)
    unlock_socket(sok);

  return retval;
}

// a preliminary descriptor has tripped select and caused us to
// attempt a read, only those client which havent logged in yet
// will be allowed here, so we expect a LOGIN_REQUEST
// 5/20/98 -jtrhone
int process_prelim_client(cldesc *c)
{
  cldesc *temp;
  dsdata *newd, tmpd;
  char res[MAX_INPUT_LENGTH];
  dblock *blk;

  // ok, lets read and validate first...
  if (read_dblock((void*)&blk, c->desc) <= 0)
  {
    close_prelim_socket(c);
    return -1;
  }

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

  // ok, we have allocated and read a data block... now figure what to do
  if (blk->type != LOGIN_REQUEST)
  {
    sprintf(buf, "SYSERR: Unknown rclient connection request, disconnecting.");
    mudlog(buf, BRF, LEV_IMM, TRUE);
    close_prelim_socket(c);
    wax_dblock(blk);
    return -1;
  }

  // must validate username and password... player MUST be online on the main server
  if (!(newd = get_char_desc(blk->str1, blk->str2, res)))
  {
    sprintf(buf, "SYSUPD: Failed rclient login attempt (%s).", blk->str1);
    mudlog(buf, BUG, LEV_IMM, TRUE);
    wax_dblock(blk);

    // notify client that login failed...
    tmpd.cdesc = c->desc;
    send_msg_to_client(res, &tmpd);
    close_prelim_socket(c);
    return -1;
  }

  // free it baba...
  wax_dblock(blk);

  // now, append it to list of client connections
  new_client_descriptor(newd, c->desc);

  // now, since it's in the client list, remove it from prelim list and free
  // we dont wanna close_prelim_socket cause we don't want the desc closed
  REMOVE_FROM_LIST(c, prelim_list, next);
  FREENULL(c);

  // update everybody's who list  6/15/98 -jtrhone
  update_client_who();

  if (D_CHECK(newd) && HAS_CLIENT(newd) && PLR2_FLAGGED(newd->character, PLR2_CLIENTEQ))
    update_client_eq(newd);
  if (D_CHECK(newd) && HAS_CLIENT(newd) && PLR2_FLAGGED(newd->character, PLR2_CLIENTINV))
    update_client_inv(newd);
  if (D_CHECK(newd) && HAS_CLIENT(newd) && PLR2_FLAGGED(newd->character, PLR2_CLIENTCHAN))
    update_client_chan(newd);

  return 0;
}

// for all messages passing from clients to server... relatively short
// for now... mainly soung gos/nogos 2/20/98 -jtrhone
int process_client_input(dsdata *d)
{
  dblock *rblk = NULL;
  static char txt[85000];
  extern void     string_add(dsdata *d, char *str, BOOL term);

  // lock it until we finish reading...we should get immediate lock
  request_socket_lock(d->cdesc);

  // await the response...
  if (read_dblock((void*)&rblk, d->cdesc) < 0)
  {
    unlock_socket(d->cdesc);
    close_client_socket(d);
    wax_dblock(rblk);
    if (d->rerouted)
    {
      d->rerouted = FALSE;
      strcpy(txt, "SYSERR: Linkloss forced premature exit.");
      string_add(d, txt, TRUE);
    }
    return FALSE;
  }

  // we got what we want... unlock it
  unlock_socket(d->cdesc);

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

  switch (rblk->type) {
    case SOUND_GO:
      #ifdef DEBUG_MAX
        sprintf(buf, "SYSUPD: GO acknowledged. Sending %s.",rblk->str1);
        log(buf); 
      #endif

      send_sound_data(d, rblk->str1);
      break;

    case SOUND_NOGO:
      #ifdef DEBUG_MAX
          sprintf(buf, "SYSUPD: NOGO acknowledged.  Not sending %s.",rblk->str1);
          log(buf); 
      #endif
      break;
     
    case EDIT_REPLY:		// 2/25/98 -jtrhone can send us back stuff now
      #ifdef DEBUG_MAX
          log("SYSUPD: EDIT_RESPONSE acknowledged.");
      #endif
      
      // 3/1/98 -jtrhone, if we get a reply and we dont think they are out
      //                  ignore the reply
      if (!d->rerouted)
      {
        log("SYSUPD: Non rerouted EDIT_RESPONSE ignored...");
        break;
      }
      else // guess what... they back 2/26/98 -jtrhone
        d->rerouted = FALSE;

      if (rblk->stream)
        strcpy(txt, rblk->stream);
      else
        strcpy(txt, "");
      string_add(d, txt, TRUE);
      break;

    case COMMAND:		// 2/27/98 -jtrhone can send us commands
      #ifdef DEBUG_MAX
          log("SYSUPD: COMMAND acknowledged.");
      #endif

      // if everything jives, execute the command...
      if (*rblk->str1 && d->character)
      {
        strcpy(txt, rblk->str1);
        command_interpreter(d->character, txt);
        if (VT100(d->character))
          updatescr(d->character);
      }
      break;

    case HELPINDEX_REQUEST:
      #ifdef DEBUG_MAX
        log("SYSUPD: HELPINDEX_REQ acknowledged. Sending help index.");
      #endif

      send_client_help_index(d);
      break;

    case HELPFILE_REQUEST:
      #ifdef DEBUG_MAX
        sprintf(buf, "SYSUPD: HELPFILE_REQ acknowledged. Sending %s.",rblk->str1);
        log(buf); 
      #endif

      send_client_help_file(d, rblk->str1);
      break;

    default:
      mudlog("SYSERR: Unknown client request, ignoring.", BRF, LEV_IMM, TRUE);
      break;
  }

  wax_dblock(rblk);
  return TRUE;
}

// doesn't do much yet, client output isn't buffered in a coded queue ...
int process_client_output(dsdata *d)
{
  return 1;
}

// send latest stats to client (hit mana move)...
void send_client_hitmanamove(dsdata *d)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;
  chdata *ch = d->character;

  blk.type = HITMANAMOVE;
  blk.version = 2;

  // this string will have helpfilename length X max # of files
  sprintf(blk.str1, "%d/%d %d/%d %d/%d", GET_HIT(ch), GET_MAX_HIT(ch), GET_MANA(ch), GET_MAX_MANA(ch),
          GET_MOVE(ch), GET_MAX_MOVE(ch));

  strcpy(blk.str2, "");
  blk.stream    = NULL;
  blk.streamlen = 0;

  if (D_CHECK(d) && HAS_CLIENT(d))
  {
      // must FLUSH EVERYTHING!!
      fflush(NULL);

#ifdef CLIENT_FORK
      switch ((pid = fork())) {
        case -1:
          mudlog("SYSERR: Client helpfilesend fork() error.", BRF, LEV_IMM, TRUE);
          return;

        case 0:  // CHILD!!!
          default_sigs();
#endif

          send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
          exit(1);

        default: // parent inserts pid structure into list...
          SET_BIT(bitv, CL_TEXTMSG);
          insert_clpid_entry(pid, bitv, d, blk.stream); 
          return;
      }
#endif
    }
}

// send hitmanamove to any clients who are fighting and want it  8/3/98 -jtrhone
// and every tick regardless  8/4/98 -jtrhone
void update_client_stats(BOOL combat)
{
  dsdata *d;

  Descriptors(d)
    if (D_CHECK(d) && HAS_CLIENT(d) && (!combat || FIGHTING(d->character)) && 
        PLR2_FLAGGED(d->character, PLR2_CLIENTSTAT))
      send_client_hitmanamove(d);
}

// send help index to client...
void send_client_help_index(dsdata *d)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;

  blk.type = HELPINDEX_REPLY;
  blk.version = 2;

  // this string will have helpfilename length X max # of files
  sprintf(blk.str1, "%d %d", TITLE_LENGTH, MAX_HELPFILES);

  strcpy(blk.str2, "");
  blk.streamlen = sizeof(struct roa_help_data) * MAX_HELPFILES;
  CREATE(blk.stream, char, blk.streamlen);
  memcpy(blk.stream, roa_help, blk.streamlen);

  if (D_CHECK(d) && HAS_CLIENT(d))
  {
      // must FLUSH EVERYTHING!!
      fflush(NULL);

#ifdef CLIENT_FORK
      switch ((pid = fork())) {
        case -1:
          mudlog("SYSERR: Client helpfilesend fork() error.", BRF, LEV_IMM, TRUE);
          return;

        case 0:  // CHILD!!!
          default_sigs();
#endif

          send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
          exit(1);

        default: // parent inserts pid structure into list...
          SET_BIT(bitv, CL_TEXTMSG);
          insert_clpid_entry(pid, bitv, d, blk.stream); 
          return;
      }
#endif
    }

  // free that which we winkillrdmallocedthingie
  FREENULL(blk.stream);
}

// send a particular help file to client...
void send_client_help_file(dsdata *d, char *name)
{
  extern void add_underscores(char *str);

#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  char fname[512];
  dblock blk;

  add_underscores(name);
  sprintf(fname, "help/%s", name);
  if (file_to_string(fname, buf2) < 0)
  {
    mudlog("SYSERR: Client helpfilesend file_to_string() error.", BRF, LEV_IMM, TRUE);
    return;
  }
  killp(buf2);

  blk.type = HELPFILE_REPLY;
  blk.version = 2;
  strcpy(blk.str1, name);
  strcpy(blk.str2, "");
  blk.stream    = (void *)winkillr(buf2);
  blk.streamlen = strlen((char *)blk.stream)+1;

  if (D_CHECK(d) && HAS_CLIENT(d))
  {
      // must FLUSH EVERYTHING!!
      fflush(NULL);

#ifdef CLIENT_FORK
      switch ((pid = fork())) {
        case -1:
          mudlog("SYSERR: Client helpfilesend fork() error.", BRF, LEV_IMM, TRUE);
          return;

        case 0:  // CHILD!!!
          default_sigs();
#endif

          send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
          exit(1);

        default: // parent inserts pid structure into list...
          SET_BIT(bitv, CL_TEXTMSG);
          insert_clpid_entry(pid, bitv, d, blk.stream); 
          return;
      }
#endif
    }

  // free that which we winkillrdmallocedthingie
  FREENULL(blk.stream);
}
// send a welcome text stream to client upon connection
void send_welcome_to_client(dsdata *d)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;

  // must FLUSH EVERYTHING!!
  fflush(NULL);

  switch ((pid = fork())) {
    case -1:
      mudlog("SYSERR: Client welcome fork() error.", BRF, LEV_IMM, TRUE);
      return;

    case 0:  // CHILD!!!
      default_sigs();
#endif

      sprintf(buf, "Welcome, %s.  RoAServer (%s) acknowledges your connection.",
        	  d->character ? GET_NAME(d->character) : "<Unknown>", RoA_version);
      send_msg_to_client(buf, d);

#ifdef CLIENT_FORK
      exit(1);

    default: // parent, just to keep track of ALL forks... record this as well
      SET_BIT(bitv, CL_TEXTMSG);
      insert_clpid_entry(pid, bitv, d, NULL); 
      return ;
  }
#endif
}

/* give client local copy of latest policies */
void send_policies_to_client(dsdata *d)
{
  extern char *policies;
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;

  strcpy(buf, policies);
  killr(buf);

  // memory must be allocated by parent  12/5/97 -jtrhone
  blk.type = TEXT_FILE;
  blk.version = 2;
  strcpy(blk.str1, "policies");
  strcpy(blk.str2, "");
  blk.streamlen = strlen(buf) + 1;
  blk.stream = (char *)STR_DUP(buf);

  // must FLUSH EVERYTHING!!
  fflush(NULL);

#ifdef CLIENT_FORK
  switch ((pid = fork())) {
    case -1:
      mudlog("SYSERR: Client policies fork() error.", BRF, LEV_IMM, TRUE);
      return;

    case 0:  // CHILD!!!
      default_sigs();
#endif

      send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
      exit(1);

    default: // parent inserts pid structure into list... 12/5/97 -jtrhone
      SET_BIT(bitv, CL_POLICIES);
      insert_clpid_entry(pid, bitv, d, blk.stream); 
      return;
  }
#else
      FREENULL(blk.stream);
#endif
}

void lgoss_to_buf(char *buf)
{
  int i;

  strcpy(buf, "Latest channel activity:\n\r");
  for (i=GOSSIP_PAGE_LENGTH-1; i >= 0; i--)
    if (*latest_gossip[i].name)
      sprintf(buf+strlen(buf), "%s %s\n\r", latest_gossip[i].name, latest_gossip[i].said);
}

// send channel update to one client...
void update_client_chan(dsdata *d)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;

  lgoss_to_buf(buf2);
  killp(buf2);

  blk.type = CHANNEL_TEXT;
  blk.version = 2;
  strcpy(blk.str1, "");
  strcpy(blk.str2, "");
  blk.stream    = (void *)winkillr(buf2);
  blk.streamlen = strlen((char *)blk.stream)+1;

  if (D_CHECK(d) && HAS_CLIENT(d) && PLR2_FLAGGED(d->character, PLR2_CLIENTCHAN))
  {
      // must FLUSH EVERYTHING!!
      fflush(NULL);

#ifdef CLIENT_FORK
      switch ((pid = fork())) {
        case -1:
          mudlog("SYSERR: Client eqsend fork() error.", BRF, LEV_IMM, TRUE);
          return;

        case 0:  // CHILD!!!
          default_sigs();
#endif

          send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
          exit(1);

        default: // parent inserts pid structure into list...
          SET_BIT(bitv, CL_TEXTMSG);
          insert_clpid_entry(pid, bitv, d, blk.stream); 
          return;
      }
#endif
    }

  // free that which we winkillrdmallocedthingie
  FREENULL(blk.stream);
}

// send channel update to all clients...
void update_allclient_chan(void)
{
  dsdata *d = descriptor_list;

  for ( ; d; d=d->next)
    if (D_CHECK(d) && HAS_CLIENT(d) && PLR2_FLAGGED(d->character, PLR2_CLIENTCHAN))
      update_client_chan(d);
}

// send latest who info to clients  6/15/98 -jtrhone
void update_client_who(void)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;
  dsdata *d;

  who_to_buf(buf2);
  killp(buf2);

  blk.type = WHO_LIST;
  blk.version = 2;
  strcpy(blk.str1, "");
  strcpy(blk.str2, "");
  blk.stream    = (void *)winkillr(buf2);
  blk.streamlen = strlen((char *)blk.stream)+1;

  for (d = descriptor_list; d; d=d->next)
    if (D_CHECK(d) && HAS_CLIENT(d) && PLR2_FLAGGED(d->character, PLR2_CLIENTWHO))
    {
      // must FLUSH EVERYTHING!!
      fflush(NULL);

#ifdef CLIENT_FORK
      switch ((pid = fork())) {
        case -1:
          mudlog("SYSERR: Client whosend fork() error.", BRF, LEV_IMM, TRUE);
          return;

        case 0:  // CHILD!!!
          default_sigs();
#endif

          send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
          exit(1);

        default: // parent inserts pid structure into list...
          SET_BIT(bitv, CL_TEXTMSG);
          insert_clpid_entry(pid, bitv, d, blk.stream); 
          return;
      }
#endif
    }

  // free that which we winkillrdmallocedthingie
  FREENULL(blk.stream);
}

void inv_to_buf(chdata *ch, char *buf)
{
  obdata *o;
  BOOL found = FALSE;

  strcpy(buf, "You are carrying:\n\r");
  for (o=ch->carrying; o; o=o->next_content)
    if (CAN_SEE_OBJ(ch, o))
    {
      sprintf(buf+strlen(buf), "  %s\n\r", o->shdesc);
      found = TRUE;
    }

  if (!found)
    strcat(buf, "  Nothing.\n\r");
  else
  {
    int ic = check_num(ch);
    sprintf(buf+strlen(buf),"Total of %d items (weight: %d, max: %d).\n\r", ic, IS_CARRYING_W(ch), CAN_CARRY_W(ch));
    if (ic > MAX_RENT_S)
      sprintf(buf+strlen(buf),"WARNING!  You have exceeded the MAX rent limit by %d items.\n\r", (ic - MAX_RENT_S));
  }
}

// stick chars eq list into a buffer
void eq_to_buf(chdata *ch, char *buf)
{
  int   j;
  BOOL found;
  char tbuf[MAX_INPUT_LENGTH];
  char cstr[MAX_INPUT_LENGTH];

  strcpy(buf, "You are using:\n\r");
  for (found = j = 0; j < MAX_WEAR; j++)
  {
     if (EQ(ch, j))
     {
       if (WV_FLAGS(EQ(ch, j)))
         sprintbit(WV_FLAGS(EQ(ch,j)), wv_bits, tbuf);
       else
         strcpy(tbuf, "");

       sprintf(cstr, "%s %s%s%s", wv_bits[j], *tbuf ? "(" : "" , tbuf, *tbuf ? ")" : "" );
       if (CAN_SEE_OBJ(ch, EQ(ch, j)))
         sprintf(buf+strlen(buf), "%%B%-35s%%0: %s\n\r", cstr,  EQ(ch, j)->shdesc);
       else
         sprintf(buf+strlen(buf), "%%B%-35s%%0: %%4Something...%%0\n\r", cstr);
       found = TRUE;
     }
  }
  if (!found)
    strcat(buf, " Nothing.\n\r");
}

// send updated eq listing to client
void update_client_eq(dsdata *d)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;

  eq_to_buf(d->character, buf2);
  killp(buf2);

  blk.type = EQ_LIST;
  blk.version = 2;
  strcpy(blk.str1, "");
  strcpy(blk.str2, "");
  blk.stream    = (void *)winkillr(buf2);
  blk.streamlen = strlen((char *)blk.stream)+1;

  if (D_CHECK(d) && HAS_CLIENT(d) && PLR2_FLAGGED(d->character, PLR2_CLIENTEQ))
  {
      // must FLUSH EVERYTHING!!
      fflush(NULL);

#ifdef CLIENT_FORK
      switch ((pid = fork())) {
        case -1:
          mudlog("SYSERR: Client eqsend fork() error.", BRF, LEV_IMM, TRUE);
          return;

        case 0:  // CHILD!!!
          default_sigs();
#endif

          send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
          exit(1);

        default: // parent inserts pid structure into list...
          SET_BIT(bitv, CL_TEXTMSG);
          insert_clpid_entry(pid, bitv, d, blk.stream); 
          return;
      }
#endif
    }

  // free that which we winkillrdmallocedthingie
  FREENULL(blk.stream);
}

void update_client_inv(dsdata *d)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;

  inv_to_buf(d->character, buf2);
  killp(buf2);

  blk.type = INV_LIST;
  blk.version = 2;
  strcpy(blk.str1, "");
  strcpy(blk.str2, "");
  blk.stream    = (void *)winkillr(buf2);
  blk.streamlen = strlen((char *)blk.stream)+1;

  if (D_CHECK(d) && HAS_CLIENT(d) && PLR2_FLAGGED(d->character, PLR2_CLIENTEQ))
  {
      // must FLUSH EVERYTHING!!
      fflush(NULL);

#ifdef CLIENT_FORK
      switch ((pid = fork())) {
        case -1:
          mudlog("SYSERR: Client eqsend fork() error.", BRF, LEV_IMM, TRUE);
          return;

        case 0:  // CHILD!!!
          default_sigs();
#endif

          send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
          exit(1);

        default: // parent inserts pid structure into list...
          SET_BIT(bitv, CL_TEXTMSG);
          insert_clpid_entry(pid, bitv, d, blk.stream); 
          return;
      }
#endif
    }

  // free that which we winkillrdmallocedthingie
  FREENULL(blk.stream);
}

// send date and time over to client (every tick)
void send_time_to_client(dsdata *d, char *str1, char *str2)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;

  blk.type = MUDTERM_INFO_MSG;
  blk.version = 2;
  strcpy(blk.str1, str1);
  strcpy(blk.str2, str2);
  blk.streamlen = 0;
  blk.stream = NULL;

  // must FLUSH EVERYTHING!!
  fflush(NULL);

#ifdef CLIENT_FORK
  switch ((pid = fork())) {
    case -1:
      mudlog("SYSERR: Client timesend fork() error.", BRF, LEV_IMM, TRUE);
      return;

    case 0:  // CHILD!!!
      default_sigs();
#endif

      send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
      exit(1);

    default: // parent inserts pid structure into list...
      SET_BIT(bitv, CL_TEXTMSG);
      insert_clpid_entry(pid, bitv, d, blk.stream); 
      return;
  }
#else
      FREENULL(blk.stream);
#endif
}

// go thru descriptors and update the times of clients...
void update_client_times(void)
{
  extern void fill_time_buf(char *txt1, char *txt2);
  dsdata *d;
  char str1[BLOCKSTRLENGTH];
  char str2[BLOCKSTRLENGTH];
  
  fill_time_buf(str1, str2);
  killp(str1);
  killp(str2);

  for (d = descriptor_list; d; d=d->next)
    if (D_CHECK(d) && HAS_CLIENT(d))
      send_time_to_client(d, str1, str2);
}

// no longer await the response here...
// client will send us a response, we receive in process_client_input()
void client_sound_query(dsdata *d, char *sname)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;

  blk.version = 2;
  blk.type    = SOUND_QUERY;
  strcpy(blk.str1, sname);
  strcpy(blk.str2, "");
  blk.streamlen  = 0;
  blk.stream  = NULL;

#ifdef CLIENT_FORK
  // must FLUSH EVERYTHING!!
  fflush(NULL);

  switch ((pid = fork())) {
    case -1:
      mudlog("SYSERR: Client sound query fork() error.", BRF, LEV_IMM, TRUE);
      return;

    case 0:  // CHILD!!!
      default_sigs();
#endif

#ifdef DEBUG_MAX
      log("Sending soundquery...");
#endif
      // lock it, send query, unlock...
      send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
      exit(1);

    default: // parent
      SET_BIT(bitv, CL_SOUNDQUERY);
      insert_clpid_entry(pid, bitv, d, blk.stream); 
      return ;
  }
#endif
}

/* given a sound filename, send it after reading it */
void send_soundfile_to_client(dsdata *d, char *sname)
{
  char fname[MAX_INPUT_LENGTH];
  struct stat stats;

  // add check for mute flag on char  6/15/98 -jtrhone
  // add check for ppl in OLC (cause cliedit blocks on client side)  6/30/98 -jtrhone
  if (!D_CHECK(d) || PLR2_FLAGGED(d->character, PLR2_CLIENTMUTE) || 
      PLR_FLAGGED(d->character, PLR_BUILDING))
    return;

  /* first check if it's there... */
  sprintf(fname, "sound/%s", sname);

  if (stat(fname, &stats) < 0)
  {
    sprintf(buf, "SYSERR: %s not found for client send.",fname);
    mudlog(buf, BRF, LEV_IMM, TRUE);
    return;
  }

  // now... just send the query to client...
  client_sound_query(d, sname);
}

// this ACTUALLY sends the large sound file...checking again if it's there...
void send_sound_data(dsdata *d, char *sname)
{
  dblock blk;
  int length; 
  char fname[MAX_INPUT_LENGTH];
  struct stat stats;
  FILE *fp;
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif

  /* first check if it's there...again */
  sprintf(fname, "sound/%s", sname);

  if (stat(fname, &stats) < 0)
  {
    sprintf(buf, "SYSERR: %s not found for client send.",fname);
    mudlog(buf, BRF, LEV_IMM, TRUE);
    return;
  }
  else
    length = stats.st_size;

  // setup structure and allocate memory in parent first 12/5/97 -jtrhone
  CREATE(blk.stream, char, length);

  /* read blk.stream */
  if (!(fp = fopen(fname, "rb")))
  {
    sprintf(buf, "SYSERR: Unable to open %s for read.",fname);
    mudlog(buf, BRF, LEV_IMM, TRUE);
    return;
  }
  fread(blk.stream, 1, length, fp);
  fclose(fp);

  blk.type = SOUND_FILE;
  blk.version = 2;
  strcpy(blk.str1, sname);
  strcpy(blk.str2, "");
  blk.streamlen = length;

#ifdef CLIENT_FORK
  // must FLUSH EVERYTHING!!
  fflush(NULL);

  switch ((pid = fork())) {
    case -1:
      mudlog("SYSERR: Client soundfile fork() error.", BRF, LEV_IMM, TRUE);
      return;

    case 0:  // CHILD!!!
      default_sigs();
#endif

#ifdef DEBUG_MAX
      log("Sending soundfile...");
#endif
      send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
      exit(1);

    default: // parent inserts pid structure into list... 12/5/97 -jtrhone
      SET_BIT(bitv, CL_SOUNDFILE);
      insert_clpid_entry(pid, bitv, d, blk.stream); 
      break;
  }
#else
      FREENULL(blk.stream);
#endif
}

// send an edit request block out to client...
void client_edit(dsdata *d, char *prompt, char *txt)
{
#ifdef CLIENT_FORK
  int pid, bitv = 0;
#endif
  dblock blk;
  static char bigbuf[MAX_STRING_LENGTH*2];

  blk.version    = 2;
  blk.type       = EDIT_REQUEST;
  strcpy(blk.str1, prompt);
  strcpy(blk.str2, "");

  if (txt && *txt)
  {
    // first, convert it...
    strcpy(bigbuf, txt);
    blk.stream    = (void *)winkillr(bigbuf);
    blk.streamlen = strlen((char *)blk.stream)+1;
  }
  else
  {
    blk.streamlen  = 0;
    blk.stream     = NULL;
  }

#ifdef CLIENT_FORK
  // must FLUSH EVERYTHING!!
  fflush(NULL);

  switch ((pid = fork())) {
    case -1:
      mudlog("SYSERR: Client edit request fork() error.", BRF, LEV_IMM, TRUE);
      return;

    case 0:  // CHILD!!!
      default_sigs();
#endif

#ifdef DEBUG_MAX
      log("Sending edit request...");
#endif
      // lock it, send query, unlock...
      send_message(&blk, d, TRUE);

#ifdef CLIENT_FORK
      exit(1);

    default: // parent
      if (blk.stream)
      {
        SET_BIT(bitv, CL_EDITREQUEST);
        insert_clpid_entry(pid, bitv, d, blk.stream); 
      }
#endif

      // so we don't take input...
      d->rerouted = TRUE;
      return;

#ifdef CLIENT_FORK
  }
#endif
}