/
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

comm.c				Heavily based on comm.c from CircleMUD3.0.
				Additions include external editor code
				along with all the functions associated
				with it.  RoAOLC also needs some mods to
				this file with roaolc_menu checks thru-
				out.

		5/1/97 - Heavy mods to allow communication with first
			 generation RoAClients. -jtrhone
		5/19/98 - more mods have been added and mostly moved to
			  clicomm.c  -jtrhone

		******** Heavily modified and expanded ********
		*** 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 ***
*************************************************************************/
#define __COMM_C__

#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 <unistd.h>

#include "structures.h"
#include "utils.h"
#include "comm.h"
#include "clicomm.h"
#include "interpreter.h"
#include "acmd.h"
#include "handler.h"
#include "db.h"
#include "house.h"
#include "identd/ident.h"
#include "screen.h"
#include "roaolc.h"
#include "lists.h"
#include "global.h"
#include "plshop.h"
#include "darkenelf.h"
#include "descmenu.h"

/* externs */
extern int 	restrict;
extern int 	mini_mud;
extern FILE 	*player_fl;
extern int 	DFLT_PORT;
extern char 	*DFLT_DIR;

/* local globals */
dsdata *descriptor_list = NULL;	/* master desc list of normal descriptors */
dsdata *cdesc_list = NULL;	/* master desc list of client descriptors */
cldesc *prelim_list = NULL;     /* global list of preliminary clients... */

struct txt_block *bufpool = 0;	/* pool of large output buffers */
int buf_largecount = 0;		/* # of large buffers which exist */
int buf_overflows = 0;		/* # of overflows of output */
int buf_switches = 0;		/* # of switches from small to large buf */
int circle_shutdown = 0;	/* clean shutdown */
int circle_reboot = 0;		/* reboot the game after a shutdown */
int max_players = 0;		/* max descriptors available */
int tics = 0;			/* for extern checkpointing */
struct timeval null_time;	/* zero-valued time structure */
int MASTER_DESCRIPTOR;		/* assigned to mother_desc for exshell.c */
int CLIENT_DESCRIPTOR;		/* assigned to client_desc for exshell.c */

/* internal functions */
int 	get_from_q(struct txt_q *queue, char *dest, int *aliased);
void 	init_game(int port);
void    setup_checkpoint(void);
void 	signal_setup(void);
void 	game_loop(int mother_desc, int client_desc);
void 	respond_to_input(void);
int 	init_socket(int port);
int 	new_descriptor(int s);
int 	get_max_players(void);
int 	process_output(dsdata *t);
int 	process_input(dsdata *t);
void 	close_socket(dsdata *d);
void 	flush_queues(dsdata *d);
void 	nonblock(int s);
void 	block(int s);
int 	perform_subst(dsdata *t, char *orig, char *subst);
int 	perform_alias(dsdata *d, char *orig);
void 	record_usage(void);
void 	make_prompt(dsdata *point);
void 	check_idle_passwords(void);
void 	send_auction(char *messg);
struct timeval timediff(struct timeval *a, struct timeval *b);
void	update_vt100_bars(void);
void 	handle_heartbeat_stuff(int *real_pulse);

/* extern fcnts */
void 	boot_db(void);
void 	boot_world(void);
void 	zone_update(void);
void 	affect_update(void);	/* In spells.c */
void 	point_update(void);	/* In limits.c */
void 	mobile_activity(BOOL doubletime);
void 	magical_activity(void);
void 	perform_violence(void);
void 	update_client_stats(BOOL combat);
void 	show_string(dsdata *d, char *input);
int 	isbanned(char *hostname);
void 	another_hour(void);
void 	string_add(dsdata *d, char *str, BOOL term);
extern void 	jump_to_ritual(chdata *ch);
extern void	rand_rite_messg(chdata *p);
extern int 	update_who_html(void);
extern int 	update_stats_html(void);
extern void 	update_goss_html(void);
extern void	do_auction_update(void);
extern void 	room_activity(BOOL doubletime);
extern void	zone_activity(void);
extern void	info_broadcast(void);
extern void	check_arena_state(BOOL award);
extern void 	free_the_mud(void);
extern void 	do_arena_update(void);
extern void 	mortal_save(void);
extern void 	save_board_config(void);

// MUST be linked up to the libdesc library to use this -roa
ACMD(do_identify)
{
  chdata *vict;
  IDENT *idinfo;
  char *argu = argument;

  skip_spaces(&argu);

  if (!(vict = get_char_vis(ch, argu)))
  {
    send_to_char("Nobody by that name around.\n\r",ch);
    return;
  }

  if (!vict->desc)
  {
    send_to_char("There is no link to identify.\n\r",ch);
    return;
  }

  idinfo = ident_lookup(vict->desc->descriptor, 5);

  sprintf(buf, "%%6Identity information on %%B%s%%0:\n\r", GET_NAME(vict));
  S2C();

  if (!idinfo)
  {
    send_to_char("Remote host not running identd or connection timed out.\n\r",ch);
    return;
  }

  sprintf(buf, "  %%5Username%%0: %s\n\r", idinfo->identifier);
  S2C();
  sprintf(buf, "  %%5Host%%0: %s\n\r", vict->desc->host);
  S2C();
  sprintf(buf, "  %%5Remote OS%%0: %s\n\r", idinfo->opsys);
  S2C();
  sprintf(buf, "  %%5Remote CharSet%%0: %s\n\r", idinfo->charset);
  S2C();
  sprintf(buf, "  %%5Lport%%0: %d\n\r", idinfo->lport);
  S2C();
  sprintf(buf, "  %%5Fport%%0: %d\n\r", idinfo->fport);
  S2C();

  // fixed mem leak here...7/21/98 -jtrhone
  ident_free(idinfo);
}

/* *********************************************************************
*  main game loop and related stuff                                    *
********************************************************************* */

int main(int argc, char **argv)
{
  int port;
  char buf[512];
  int pos = 1;
  char *dir;

  port = DFLT_PORT;
  dir  = DFLT_DIR;

  while ((pos < argc) && (*(argv[pos]) == '-')) 
  {
    switch (*(argv[pos] + 1)) 
    {
    case 'd':
      if (*(argv[pos] + 2))
	dir = argv[pos] + 2;
      else if (++pos < argc)
	dir = argv[pos];
      else {
	log("Directory arg expected after option -d.");
	exit(1);
      }
      break;
    case 'm':
      mini_mud = TRUE;
      log("Running in minimized mode.");
      break;
    case 'r':
      restrict = 1;
      log("Restricting mud -- no new players allowed (wizlock 1).");
      break;
    case 'w':
      restrict = 71;
      log("Restricting mud -- no mortals allowed (wizlock 71).");
      break;
    default:
      sprintf(buf, "SYSERR: Unknown option -%c in argument string.", *(argv[pos] + 1));
      log(buf);
      break;
    }
    pos++;
  }

  if (pos < argc) {
    if (!isdigit(*argv[pos])) {
      fprintf(stderr, "Usage: %s [-c] [-m] [-q] [-r] [-d pathname] [port #]\n", argv[0]);
      exit(1);
    } else if ((port = atoi(argv[pos])) <= 1024) {
      fprintf(stderr, "Illegal port number.\n");
      exit(1);
    }
  }
  if (chdir(dir) < 0) {
    perror("Fatal error changing to data directory");
    exit(1);
  }
  sprintf(buf, "Using %s as data directory.", dir);
  log(buf);
  sprintf(buf, "Main Server on port %d.", port);
  log(buf);

  init_game(port);

  return 0;
}

/* Init sockets, run game, and cleanup sockets */
void init_game(int port)
{
  int mother_desc;
  int client_desc;

  srandom(time(0));

  // make sure we run with these ENV vars set right... 2/12/98 -jtrhone
  log("Initializing environment.");
  system("rm -f misc/*.soklock >& /dev/null");
  putenv("TERM=vt100");

  log("Opening mother connection.");
  mother_desc = init_socket(port);

  // this will also now fork off a cli_router... for client connections...
  client_desc = init_client_socket(port + 1);

  /* testing jtrhone -roa*/
  MASTER_DESCRIPTOR = mother_desc;
  CLIENT_DESCRIPTOR = client_desc;

  max_players = get_max_players();

  boot_db();

  log("Signal trapping.");
  signal_setup();

  log("Entering game loop.");

  game_loop(mother_desc, client_desc);

  log("Closing all mother connections.");
  while (descriptor_list)
    close_socket(descriptor_list);

  close(mother_desc);
  close(client_desc);

  fclose(player_fl);

  // ignore SIGCHILD on shutdown
  signal(SIGCHLD, SIG_IGN);

  // if we brought one up, kill it...
  if (cli_router_pid > 0)
    kill(cli_router_pid, SIGTERM);

  // now remove local socket 5/19/98 -jtrhone
  sprintf(buf, "Removing %s.", local_sockname);
  log(buf);
  unlink(local_sockname);
  
  // cleanup...
  system("rm -f misc/*.soklock >& /dev/null");

  if (circle_reboot) {
    log("Rebooting.");
    exit(52);			/* what's so great about HHGTTG, anyhow? */
  }

  log("Normal termination of game.");
}

/*
 * init_socket sets up the mother descriptor - creates the socket, sets
 * its options up, binds it, and listens.
 */
int init_socket(int port)
{
  int s, opt;
  struct sockaddr_in sa;

  /*
   * Should the first argument to socket() be AF_INET or PF_INET?  I don't
   * know, take your pick.  PF_INET seems to be more widely adopted, and
   * Comer (_Internetworking with TCP/IP_) even makes a point to say that
   * people erroneously use AF_INET with socket() when they should be using
   * PF_INET.  However, the man pages of some systems indicate that AF_INET
   * is correct; some such as ConvexOS even say that you can use either one.
   * All implementations I've seen define AF_INET and PF_INET to be the same
   * number anyway, so ths point is (hopefully) moot.
   */

  if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("Create 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.sin_family = AF_INET;
  sa.sin_port = htons(port);
  sa.sin_addr.s_addr = htonl(INADDR_ANY);

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


int get_max_players(void)
{
  int max_descs = 0;
  char *method;

#ifdef OS2
  return MAX_PLAYERS;
#else

/*
 * First, we'll try using getrlimit/setrlimit.  This will probably work
 * on most systems.
 */
#if defined (RLIMIT_NOFILE) || defined (RLIMIT_OFILE)
#if !defined(RLIMIT_NOFILE)
#define RLIMIT_NOFILE RLIMIT_OFILE
#endif
  {
    struct rlimit limit;

    /* find the limit of file descs */
    method = "rlimit";
    if (getrlimit(RLIMIT_NOFILE, &limit) < 0) {
      perror("calling getrlimit");
      exit(1);
    }

    /* set the current to the maximum */
    limit.rlim_cur = limit.rlim_max;
    if (setrlimit(RLIMIT_NOFILE, &limit) < 0) {
      perror("calling setrlimit");
      exit(1);
    }

#ifdef RLIM_INFINITY
    if (limit.rlim_max == RLIM_INFINITY)
      max_descs = MAX_PLAYERS + NUM_RESERVED_DESCS;
    else
      max_descs = MIN(MAX_PLAYERS + NUM_RESERVED_DESCS, limit.rlim_max);
#else
      max_descs = MIN(MAX_PLAYERS + NUM_RESERVED_DESCS, limit.rlim_max);
#endif
  }

#elif defined (OPEN_MAX) || defined(FOPEN_MAX)
#if !defined(OPEN_MAX)
#define OPEN_MAX FOPEN_MAX
#endif
  method = "OPEN_MAX";
  max_descs = OPEN_MAX;		/* Uh oh.. rlimit didn't work, but we have
				 * OPEN_MAX */
#elif defined (POSIX)
  /*
   * Okay, you don't have getrlimit() and you don't have OPEN_MAX.  Time to
   * use the POSIX sysconf() function.  (See Stevens' _Advanced Programming
   * in the UNIX Environment_).
   */
  method = "POSIX sysconf";
  errno = 0;
  if ((max_descs = sysconf(_SC_OPEN_MAX)) < 0) {
    if (errno == 0)
      max_descs = MAX_PLAYERS + NUM_RESERVED_DESCS;
    else {
      perror("Error calling sysconf");
      exit(1);
    }
  }
#else
  /* if everything has failed, we'll just take a guess */
  max_descs = MAX_PLAYERS + NUM_RESERVED_DESCS;
#endif

  /* now calculate max _players_ based on max descs */
  max_descs = MIN(MAX_PLAYERS, max_descs - NUM_RESERVED_DESCS);

  if (max_descs <= 0) {
    sprintf(buf, "Non-positive max player limit!  (Set at %d using %s).", max_descs, method);
    log(buf);
    exit(1);
  }

  sprintf(buf, "Setting player limit to %d using %s.", max_descs, method);
  log(buf);
  return max_descs;
#endif /* OS2 */
}

// Now handle second list of connections, from client descriptor
// use the same select, accept new client connections as well now
// as well as poll thru available client connections and
// parsing thru client headers   -RoA
// now select on list of cldescs (preliminary clients before login) as well 5/20/98 -jtrhone
void game_loop(int mother_desc, int client_desc)
{
  fd_set input_set, output_set, exc_set;
  struct timeval last_time, now, timespent, timeout, opt_time;
  dsdata *d, *next_d;
  cldesc *c, *next_c;
  int pulse = 0, maxdesc;

  /* initialize various time values */
  null_time.tv_sec 	= 0;
  null_time.tv_usec 	= 0;
  opt_time.tv_usec 	= OPT_USEC;
  opt_time.tv_sec 	= 0;
  gettimeofday(&last_time, (struct timezone *) 0);

  while (!circle_shutdown) 
  {
    /* Sleep if we don't have any connections */
    if (!descriptor_list && !cdesc_list && !prelim_list) 
    {
      log("No connections.  Going to sleep.");

      // null out entire input set (read_set) -roa
      FD_ZERO(&input_set);

      // stick our mother into the input set -roa
      FD_SET(mother_desc, &input_set);

      // stick client in there too, but mother should always be trigged
      // first, just a safety check i guess if mother is plugged
      FD_SET(client_desc, &input_set);

      if (select(MAX(mother_desc, client_desc)+1, &input_set, (fd_set *) 0, (fd_set *) 0, NULL) < 0) 
      {
	if (errno == EINTR)
	  log("Waking up to process signal.");
	else
	  perror("Select coma");
      } else
	log("New connection.  Waking up.");
      gettimeofday(&last_time, (struct timezone *) 0);
    }
  
    /* Set up the input, output, and exception sets for select(). */
    // clear out every set, init them to NULL -roa
    FD_ZERO(&input_set);
    FD_ZERO(&output_set);
    FD_ZERO(&exc_set);

    // put our mother, client_desc into the input set
    FD_SET(mother_desc, &input_set);
    FD_SET(client_desc, &input_set);

    // high end of the descriptors we scan starts at mother_desc -roa
    // in theory, client is higher, but it could have rolled
    maxdesc = MAX(mother_desc, client_desc);

    // now, go thru list, find the high end of the range of descriptors
    // and stick each of them into our read,write,and exception sets -roa

    Descriptors(d)
    {
      if (d->descriptor > maxdesc)
	maxdesc = d->descriptor;
      FD_SET(d->descriptor, &input_set);
      FD_SET(d->descriptor, &output_set);
      FD_SET(d->descriptor, &exc_set);
    }

    // now scan RoA client descriptor list and insert
    for (d = cdesc_list; d; d = d->next_client) 
    {
      if (d->cdesc > maxdesc)
	maxdesc = d->cdesc;
      FD_SET(d->cdesc, &input_set);
      FD_SET(d->cdesc, &output_set);
      FD_SET(d->cdesc, &exc_set);
    }

    // now, go thru cldesc list as well, lookin for max... 5/20/98 -jtrhone
    for (c = prelim_list; c; c = c->next) 
    {
      if (c->desc > maxdesc)
	maxdesc = c->desc;
      FD_SET(c->desc, &input_set);
      FD_SET(c->desc, &output_set);
      FD_SET(c->desc, &exc_set);
    }

    do {
      errno = 0;		/* clear error condition */

      /* figure out for how long we have to sleep */
      gettimeofday(&now, (struct timezone *) 0);
      timespent = timediff(&now, &last_time);
      timeout = timediff(&opt_time, &timespent);

      /* sleep (regardless!) until the next 0.1 second tick */
      if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &timeout) < 0)
	if (errno != EINTR) {
	  perror("Select sleep");
	  exit(1);
	}
    } while (errno);

    /* record the time for the next pass */
    gettimeofday(&last_time, (struct timezone *) 0);

    /* poll (without blocking) for new input, output, and exceptions */
    // we are scanning the i,o,e sets for changes in their state
    // those sets contain every player descriptor in the game including mother
    if (select(maxdesc + 1, &input_set, &output_set, &exc_set, &null_time) < 0) 
    {
      perror("Select poll");
      return;
    }

    /* If there are new connections waiting, accept them. */
    // ok, mama is in the input_set, someone is knockin at her door -roa
    if (FD_ISSET(mother_desc, &input_set))
      new_descriptor(mother_desc);

    // client monitor has a new connection, go getem one
    // no longer insert into client list, insert now into prelim list
    // upon successful process_prelim_input and login, this desc will be
    // put into client list
    if (FD_ISSET(client_desc, &input_set))
      new_prelim_socket(client_desc);

    // anybody in the exception set, we wanna rip from the game
    // and remove them from the input and output sets -roa
    for (d = descriptor_list; d; d = next_d) 
    {
      next_d = d->next;
      if (FD_ISSET(d->descriptor, &exc_set)) 
      {
	FD_CLR(d->descriptor, &input_set);
	FD_CLR(d->descriptor, &output_set);
	close_socket(d);
      }
    }

    // kick client exceptions out
    for (d = cdesc_list; d; d = next_d) 
    {
      next_d = d->next_client;
      if (FD_ISSET(d->cdesc, &exc_set)) 
      {
	FD_CLR(d->cdesc, &input_set);
	FD_CLR(d->cdesc, &output_set);
	close_client_socket(d);
      }
    }

    // kick preliminary client exceptions out 5/20/98 -jtrhone
    for (c = prelim_list; c; c = next_c) 
    {
      next_c = c->next;
      if (FD_ISSET(c->desc, &exc_set)) 
      {
	FD_CLR(c->desc, &input_set);
	FD_CLR(c->desc, &output_set);
	close_prelim_socket(c);
      }
    }

    // process all descriptors which are in our input_set
    for (d = descriptor_list; d; d = next_d) 
    {
      next_d = d->next;
      if (FD_ISSET(d->descriptor, &input_set))
	if (process_input(d) < 0)
	  close_socket(d);
    }

    // take care of client input
    for (d = cdesc_list; d; d = next_d) 
    {
      next_d = d->next_client;
      if (FD_ISSET(d->cdesc, &input_set))
	if (process_client_input(d) < 0)
	  close_client_socket(d);
    }

    // take care of prelim client input
    for (c = prelim_list; c; c = next_c) 
    {
      next_c = c->next;
      if (FD_ISSET(c->desc, &input_set))
	if (process_prelim_client(c) < 0)
	  close_prelim_socket(c);
    }

    // now each descriptor has had its input stuck onto a queue
    // lets go thru and yank a chunk of each players queue and
    // decipher it and process it
    respond_to_input();

    /* send queued output out to the operating system (ultimately to user) */
    // ok, now take care of those descriptors in our output_set -roa
    for (d = descriptor_list; d; d = next_d) 
    {
      next_d = d->next;
      if (FD_ISSET(d->descriptor, &output_set) && *(d->output))
	if (process_output(d) < 0)
	  close_socket(d);
	else
	  d->prompt_mode = 1;
    }

    // now process output from mud to client
    // doesn't do much since client output isn't buffered (yet)...
    for (d = cdesc_list; d; d = next_d) 
    {
      next_d = d->next_client;
      if (FD_ISSET(d->cdesc, &output_set))
	if (process_client_output(d) < 0)
	  close_client_socket(d);
    }

    // wax CON_CLOSEd folks, this will also wax client connections
    for (d = descriptor_list; d; d = next_d) {
      next_d = d->next;
      if (STATE(d) == CON_CLOSE)
	close_socket(d);
    }

    /* give each descriptor an appropriate prompt */
    Descriptors(d)
      if (d->prompt_mode) 
      {
	make_prompt(d);
	d->prompt_mode = 0;
      }

    // Note: pulse only updates every 0.10 seconds because we force
    // the mud to sleep for that amount of time with the select statement
    // above as we send it NULL i,o,e sets and a timeval of 0.10 secs -roa

    pulse++;
    handle_heartbeat_stuff(&pulse);

    tics++;	/* tics since last checkpoint signal */
  }

  // if we have logged, close the file ptr 5/13/98 -jtrhone
  if (memfp)
    fclose(memfp);

  // ok, were out of the main loop, ready to shutdown
  // lets free a ton of stuff so we can track down memory leaks
  // with the dmalloc libraries easier -roa
  free_the_mud();

#ifndef DMALLOC_DISABLE
  #ifdef DEBUG_MAX
    dmalloc_log_stats();
    dmalloc_log_unfreed(); 
  #endif
#endif
}

// no more nanny call or STATE() macros..., if they in a descriptor menu, call it 7/14/98 -jtrhone
void respond_to_input(void)
{
  dsdata *d, *next_d;
  chdata *player;
  char comm[MAX_INPUT_LENGTH];
  char combuf[256];
  int aliased;

    for (d = descriptor_list; d; d = next_d) 
    {
      next_d = d->next;
      player = d->character;

      // check for arena and ritualistic things first
      if (D_CHECK(d) && IS_PC(player))
      {
	if (IN_ARENA(player) && !ZONE_FLAGGED(world[player->in_room].zone, Z_ARENA))
	{
	  REMOVE_BIT(PLR_FLAGS(player), PLR_ARENA);
          check_arena_state(TRUE); 
	}

	if (IN_A_RITUAL(player) && DELAY_TYPE(player))
	{
          if (!(d->wait % 60))  /* every 60 pulses show messg */
            rand_rite_messg(player);

          if ((--d->wait) <= 0)  /* ready to finish ritual? */
	  {
	    d->wait = 1;
            jump_to_ritual(player);
	  }

	  continue;  // dont take input from this guy yet
	}
      }

      aliased = FALSE;
      if ((--(d->wait) <= 0) && get_from_q(&d->input, comm, &aliased)) 
      {
        // Record time of last entered command... 03/21/98 -callahan
        d->last_time = time(0);

	if (d->character) 
	{
	  /* pull char back from void if necessary */
          d->character->specials.timer = 0;
	  if (!d->connected && GET_WAS_IN(d->character) != NOWHERE) 
	  {
	    if (!IN_NOWHERE(d->character))
	      char_from_room(d->character);
	    char_to_room(d->character, GET_WAS_IN(d->character));
	    GET_WAS_IN(d->character) = NOWHERE;
	    act("$n has returned.", TRUE, d->character, 0, 0, TO_ROOM);
	  }
	}
	d->wait = 1;
	d->prompt_mode = 1;

        // if they are rerouted... we basically ignore them...
        if (d->rerouted)
	{
 	  if (d->character)
	    sprintf(combuf, "SYSUPD: %s rerouted input in comm.c", GET_NAME(d->character));
	  else
	    sprintf(combuf, "SYSUPD: Unknown char rerouted in comm.c.");
	  mudlog(combuf, BUG, LEV_IMM, TRUE);
	  continue;
	}
        else
	if (d->str)		/* writing boards, mail, etc.     */
	  string_add(d, comm, FALSE);
	else 
	if (d->showstr_count)	/* reading something w/ pager     */
	  show_string(d, comm);
        else 
	if (d->descmenu)  /* in descmenus */
	{
          d->idle_tics = 0;
          (*d->descmenu)(d, comm);
        }
        else
	if (d->roaolc_menu)  /* in RoA OLC */
	{
	  if (d->character)
            (*d->roaolc_menu)(d->character, comm);
	  else
	    mudlog("SYSERR: NonCharacter menu access in comm.c", BRF,LEV_IMM,TRUE);
	}
	else 
	{			/* else: we're playing normally */
	  if (aliased)		/* to prevent recursive aliases */
	    d->prompt_mode = 0;
	  else 
	  {
	    /* run it through aliasing system */
	    if (d->character)
	    {
	      if (perform_alias(d, comm))		
	        get_from_q(&d->input, comm, &aliased);
	    }
	    else
	    {
	      mudlog("SYSERR: NonCharacter perform_alias attempt in comm.c", BRF,LEV_IMM,TRUE);
	    }
	  }

	  /* send it to interpreter */
	  command_interpreter(d->character, comm);	

	  if (VT100(d->character))
	    updatescr(d->character);
	}
      }
    }
}

// updated room_activity for rproc dtime  6/6/98 -jtrhone
void handle_heartbeat_stuff(int *real_pulse)
{
  int pulse = *real_pulse;
  static int mins_since_crashsave = 0;
  static BOOL dtime = FALSE;
  static BOOL rdtime = FALSE;
  extern void room_save_all(void);

  if (!(pulse % PULSE_PASSWORD))
    check_idle_passwords();

  if (!(pulse % PULSE_ZONE))
  {
    zone_update();
    zone_activity();
  }

  if (!(pulse % PULSE_MAGIC))
    magical_activity();	// in mobact.c for now -roa

  if (!(pulse % PULSE_ROOM))
  {
    room_activity(rdtime);
    rdtime = !rdtime;
  }

  if (!(pulse % PULSE_MOBILE)) 
  {
    mobile_activity(dtime);
    dtime = !dtime;
  }

  // do the normal violence thing
  if (!(pulse % PULSE_VIOLENCE))
  {
    perform_violence();

    // for all those players who are vt100 and need an update
    update_vt100_bars(); 

    // for all those peeps who WANT stat data every 2 seconds... 8/3/98 -jtrhone
    update_client_stats(TRUE);
  }

  /* do per tick i guess? per hour */
  if (!(pulse % (SECS_PER_MUD_HOUR * PASSES_PER_SEC))) 
  {
    another_hour();
    affect_update();
    point_update();
    fflush(player_fl);
    max_on = MAX(max_on, update_who_html()); 
    update_stats_html();
    update_goss_html();
    do_auction_update();
    do_arena_update();
    info_broadcast();

    // update these every tick, regardless if they fighting  8/4/98 -jtrhone
    update_client_stats(FALSE);
  }

  /* 1 minute */
  if (auto_save)
    if (!(pulse % (60 * PASSES_PER_SEC)))	
      if (++mins_since_crashsave >= autosave_time) 
      {
	mins_since_crashsave = 0;
	mortal_save();
	House_save_all();
	room_save_all();
      }

  /* 5 minutes */
  if (!(pulse % (300 * PASSES_PER_SEC)))	
    record_usage();

  /* 30 minutes */
  if (pulse >= (30 * 60 * PASSES_PER_SEC)) 
  {
    *real_pulse = 0;

    // save config files every half hour... -jtrhone 11/25/97
    save_configuration();
    save_board_config();
    save_plshops();
  }
}

/* ******************************************************************
*  general utility stuff (for local use)                            *
****************************************************************** */
/*
 *  new code to calculate time differences, which works on systems
 *  for which tv_usec is unsigned (and thus comparisons for something
 *  being < 0 fail).  Based on code submitted by ss@sirocco.cup.hp.com.
 */

/*
 * code to return the time difference between a and b (a-b).
 * always returns a nonnegative value (floors at 0).
 */
struct timeval timediff(struct timeval *a, struct timeval *b)
{
  struct timeval rslt;

  if (a->tv_sec < b->tv_sec)
    return null_time;
  else if (a->tv_sec == b->tv_sec) {
    if (a->tv_usec < b->tv_usec)
      return null_time;
    else {
      rslt.tv_sec = 0;
      rslt.tv_usec = a->tv_usec - b->tv_usec;
      return rslt;
    }
  } else {			/* a->tv_sec > b->tv_sec */
    rslt.tv_sec = a->tv_sec - b->tv_sec;
    if (a->tv_usec < b->tv_usec) {
      rslt.tv_usec = a->tv_usec + 1000000 - b->tv_usec;
      rslt.tv_sec--;
    } else
      rslt.tv_usec = a->tv_usec - b->tv_usec;
    return rslt;
  }
}

void record_usage(void)
{
  int sockets_connected = 0, sockets_playing = 0;
  dsdata *d;
  char buf[256];

  Descriptors(d) {
    sockets_connected++;
    if (!d->connected)
      sockets_playing++;
  }

  sprintf(buf, "nusage: %-3d sockets connected, %-3d sockets playing",
	  sockets_connected, sockets_playing);
  log(buf);

  if (0)
  {
    struct rusage ru;

    getrusage(0, &ru);
    sprintf(buf, "rusage: user time: %d sec, system time: %d sec, max res size: %ld",
	    ru.ru_utime.tv_sec, ru.ru_stime.tv_sec, ru.ru_maxrss);
    log(buf);
  }
}

/*
 * Turn off echoing (specific to telnet client)
 */
void echo_off(dsdata *d)
{
  char off_string[] =
  {
    (char) IAC,
    (char) WILL,
    (char) TELOPT_ECHO,
    (char) 0,
  };
  send_to_q(off_string, d);
}

/*
 * Turn on echoing (specific to telnet client)
 */
void echo_on(dsdata *d)
{
  char on_string[] =
  {
    (char) IAC,
    (char) WONT,
    (char) TELOPT_ECHO,
    (char) TELOPT_NAOFFD,
    (char) TELOPT_NAOCRD,
    (char) 0,
  };

  send_to_q(on_string, d);
}

// at predetermined interval, go thru descriptor list and updatescr
void update_vt100_bars(void)
{
  dsdata *d = descriptor_list;
  dsdata *next_d;

  for ( ; d; d = next_d)
  {
    next_d = d->next;
    if (D_CHECK(d) && IS_PC(d->character) && VT100(d->character) && 
	d->character->pc_specials->needs_update)
      updatescr(d->character);
  }
}

// give our peeps a prompt if they need one
void make_prompt(dsdata *d)
{
  char prompt[MAX_INPUT_LENGTH+1];

  // dont give em no prompt if they shelled out -roa
  if (d->rerouted)
    return;
  else
  if (d->character && INCHAT(d->character))
  {
    write_to_descriptor(d->descriptor, ">");
    return;
  }
  else 
  if (d->showstr_count) // they using the pager type code?
  {
    sprintf(prompt,
"\r[ Return to continue, (q)uit, (r)efresh, (b)ack, or page number (%d/%d) ]",
            d->showstr_page, d->showstr_count);
    write_to_descriptor(d->descriptor, prompt);
    if (d->character && IS_PC(d->character))
      d->character->pc_specials->needs_update = FALSE;
  }
  else
  if (d->descmenu_prompt) // they have descmenu?
  {
    write_to_descriptor(d->descriptor, d->descmenu_prompt);
    if (d->character && IS_PC(d->character))
      d->character->pc_specials->needs_update = FALSE;
  }
  else
  if (d->menu_prompt) // they have an RoAOLC menu?
  {
    write_to_descriptor(d->descriptor, d->menu_prompt);
    if (d->character && IS_PC(d->character))
      d->character->pc_specials->needs_update = FALSE;
  }
  else
  if (d->str)  // they in the old fashioned editor?
  {
    write_to_descriptor(d->descriptor, "] ");
    if (d->character && IS_PC(d->character))
      d->character->pc_specials->needs_update = FALSE;
  }
  else 
  if (D_CHECK(d) && !VT100(d->character))
  {
    display_prompt(d->character, prompt);
    write_to_descriptor(d->descriptor, prompt); 
    if (d->character && IS_PC(d->character))
      d->character->pc_specials->needs_update = FALSE;
  }
}

// throw a chunk of text on a queue
void write_to_q(char *txt, struct txt_q *queue, int aliased)
{
  struct txt_block *neww;

  CREATE(neww, struct txt_block, 1);
  CREATE(neww->text, char, strlen(txt) + 1);
  strcpy(neww->text, txt);
  neww->aliased = aliased;

  /* queue empty? */
  if (!queue->head) {
    neww->next = NULL;
    queue->head = queue->tail = neww;
  } else {
    queue->tail->next = neww;
    queue->tail = neww;
    neww->next = NULL;
  }
}

/*
  used to be a macro, BLAH, i had to throw a thing in here, easier as a
  function now -jtrhone
*/
void send_to_q(char *messg, dsdata *d)
{
  if (!messg || !*messg) return;

  if (d->character && IS_PC(d->character) && VT100(d->character))
    d->character->pc_specials->needs_update = TRUE;
  write_to_output(messg, d);
}

// yank a chunk off the queue
int get_from_q(struct txt_q *queue, char *dest, int *aliased)
{
  struct txt_block *tmp;

  /* queue empty? */
  if (!queue->head)
    return 0;

  tmp = queue->head;
  strcpy(dest, queue->head->text);
  *aliased = queue->head->aliased;
  queue->head = queue->head->next;

  free(tmp->text);
  free(tmp);

  return 1;
}

/* Empty the queues before closing connection */
void flush_queues(dsdata *d)
{
  int dummy;

  if (d->large_outbuf) {
    d->large_outbuf->next = bufpool;
    bufpool = d->large_outbuf;
  }
  while (get_from_q(&d->input, buf2, &dummy));
}

/* Add a new string to a players output queue */
void write_to_output(const char *txt, dsdata *t)
{
  int size;

  size = strlen(txt);

  /* if were in the overflow state already, ignore this new output */
  if (t->bufptr < 0)
    return;

  /* if we have enough space, just write to buffer and thats it! */
  if (t->bufspace >= size) {
    strcpy(t->output + t->bufptr, txt);
    t->bufspace -= size;
    t->bufptr += size;
    return;
  }

  /*
   * If were already using the large buffer, or if even the large buffer
   * is too small to handle this new text, chuck the text and switch to the
   * overflow state.
   */
  if (t->large_outbuf || ((size + strlen(t->output)) > LARGE_BUFSIZE)) {
    t->bufptr = -1;
    buf_overflows++;
    mudlog("SYSUPD: Buf overflow noted.",BUG,LEV_IMM,TRUE);
    return;
  }

  buf_switches++;

  /* if the pool has a buffer in it, grab it */
  if (bufpool != NULL) {
    t->large_outbuf = bufpool;
    bufpool = bufpool->next;
  } else {			/* else create a new one */
    CREATE(t->large_outbuf, struct txt_block, 1);
    CREATE(t->large_outbuf->text, char, LARGE_BUFSIZE);
    buf_largecount++;
  }

  strcpy(t->large_outbuf->text, t->output);	/* copy to big buffer */
  t->output = t->large_outbuf->text;	/* make big buffer primary */
  strcat(t->output, txt);	/* now add new text */

  /* calculate how much space is left in the buffer */
  t->bufspace = LARGE_BUFSIZE - 1 - strlen(t->output);

  /* set the pointer for the next write */
  t->bufptr = strlen(t->output);
}

/* ******************************************************************
*  socket handling                                                  *
****************************************************************** */

// set up descriptor, and descriptor struct
int new_descriptor(int s)
{
  int desc, sockets_connected = 0, i;
  unsigned long addr;
  static int last_desc = 0;
  dsdata *newd;
  struct sockaddr_in peer;
  struct hostent *from;

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

  /* make sure we have room for it */
  Descriptors(newd)
    sockets_connected++;

  if (sockets_connected >= max_players) 
  {
    sprintf(buf, "Sorry, %s is full right now... please try again later!\n\r", shortmudname);
    write_to_descriptor(desc, buf);
    close(desc);
    return 0;
  }

  /* create a new descriptor */
  CREATE(newd, dsdata, 1);
  memset((char *) newd, 0, sizeof(dsdata));

  /* find the sitename */
  if (nameserver_is_slow || !(from = gethostbyaddr((char *) &peer.sin_addr, sizeof(peer.sin_addr), AF_INET))) 
  {
    /* resolution failed */
    if (!nameserver_is_slow)
      perror("gethostbyaddr");

    /* find the numeric site address */
    addr = ntohl(peer.sin_addr.s_addr);
    sprintf(newd->host, "%03u.%03u.%03u.%03u", (int) ((addr & 0xFF000000) >> 24),
	    (int) ((addr & 0x00FF0000) >> 16), (int) ((addr & 0x0000FF00) >> 8),
	    (int) ((addr & 0x000000FF)));
  } else {
    strncpy(newd->host, from->h_name, 30);
    *(newd->host + 30) = '\0';
  }

  /* determine if the site is banned */
  if (isbanned(newd->host) == BAN_ALL) {
    close(desc);
    sprintf(buf2, "Connection attempt denied from [%s]", newd->host);
    mudlog(buf2, CMP, LEV_GOD, TRUE);
    free(newd);
    return 0;
  }

  /* initialize descriptor data */
  newd->descriptor = desc;
  newd->pos = -1;
  newd->wait = 1;
  newd->output = newd->small_outbuf;
  newd->bufspace = SMALL_BUFSIZE - 1;
  newd->login_time = time(0);
  newd->last_time = time(0);	// For idle-monitoring... 03/21/98 -callahan
  newd->zone_stimer = -1;
  newd->room_stimer = -1;
  newd->mob_stimer = -1;
  STATE(newd) = CON_DESCMENU;

  // we need a number to reference this descriptor for wiz commands
  // this has nothing to do with the socket or descriptor stuff -roa
  if (++last_desc == 1000)
  {
    last_desc = 1;
    mudlog("SYSUPD: 1000 connections reached.  Reset to 1.", BUG, LEV_IMM, TRUE);
  }
  newd->desc_num = last_desc;

  /* prepend to our list of descriptors list */
  newd->next = descriptor_list;
  descriptor_list = newd;

  // jump to getname menu in descmenu.c
  descmenu_jump(newd, getname);
  return 0;
}

// each character has a buffer of output waiting to be processed
// (normally), lets process a chunk and send it out
int process_output(dsdata *t)
{
  static char i[LARGE_BUFSIZE + GARBAGE_SPACE];
  static int result;

  /* we may need this \r\n for later -- see below */
  strcpy(i, "\r\n");

  /* now, append the 'real' output */
  str_cpy(i + 2, t->output, LARGE_BUFSIZE + GARBAGE_SPACE -2, "process_output");

  /* if we're in the overflow state, notify the user */
  if (t->bufptr < 0)
    strcat(i, "**OVERFLOW**");

  /* add the extra CRLF if the person isn't in compact mode */
  if (D_CHECK(t) && !PRF_FLAGGED(t->character, PRF_COMPACT))
    strcat(i + 2, "\r\n");

  /*
   * now, send the output.  If this is an 'interruption', use the prepended
   * CRLF, otherwise send the straight output sans CRLF.
   */
  if (!t->prompt_mode)		
    result = write_to_descriptor(t->descriptor, i);
  else
    result = write_to_descriptor(t->descriptor, i + 2);

  if (t->character && IS_PC(t->character))
    t->character->pc_specials->needs_update = FALSE;

  /* handle snooping: prepend "% " and send to snooper */
  if (t->snoop_by) {
    send_to_q("% ", t->snoop_by);
    send_to_q(t->output, t->snoop_by);
    send_to_q("%%", t->snoop_by);
  }
  /*
   * if we were using a large buffer, put the large buffer on the buffer pool
   * and switch back to the small one
   */
  if (t->large_outbuf) {
    t->large_outbuf->next = bufpool;
    bufpool = t->large_outbuf;
    t->large_outbuf = NULL;
    t->output = t->small_outbuf;
  }
  /* reset total bufspace back to that of a small buffer */
  t->bufspace = SMALL_BUFSIZE - 1;
  t->bufptr = 0;
  *(t->output) = '\0';

  return result;
}

int	write_to_descriptor(int desc, char *txt)
{
   int	sofar, thisround, total;

   total = strlen(txt);
   sofar = 0;

   do {
      thisround = write(desc, txt + sofar, total - sofar);
      if (thisround < 0) {
	 perror("Write to socket");
	 return(-1);
      }
      sofar += thisround;
   } while (sofar < total);

   return(0);
}

int	process_input(dsdata *t)
{
   int	sofar, thisround, begin, squelch, i, k, flag, failed_subst = 0;
   char	tmp[MAX_INPUT_LENGTH+2], buffer[MAX_INPUT_LENGTH + 60];
   char somebuf[MAX_STRING_LENGTH];

   // if they is rerouted, dump the input off into cyberspacia -roa
   if (t->rerouted)
   {
     read(t->descriptor, somebuf, MAX_STRING_LENGTH);
     return 0;
   }

   sofar = 0;
   flag = 0;
   begin = strlen(t->buf);

   /* Read in some stuff */
   do 
   {
      if ((thisround = read(t->descriptor, t->buf + begin + sofar, 
          MAX_STRING_LENGTH - (begin + sofar) - 1)) > 0)
	 sofar += thisround;
      else 
      if (thisround < 0)
	 if (errno != EWOULDBLOCK) 
	 {
	    perror("Read1 - ERROR");
	    return(-1);
	 } 
	 else
	    break;
      else 
      {
	 log("EOF encountered on socket read. (connection broken by peer)");
	 return(-1);
      }
   } while (!ISNEWL(*(t->buf + begin + sofar - 1))); 

   *(t->buf + begin + sofar) = 0;

   /* if no newline is contained in input, return without proc'ing */
   for (i = begin; !ISNEWL(*(t->buf + i)); i++)
      if (!*(t->buf + i))
	 return(0);

   /* input contains 1 or more newlines; process the stuff */
   // this is normal case
   for (i = 0, k = 0; *(t->buf + i); ) {
      if (!ISNEWL(*(t->buf + i)) && !(flag = (k >= (MAX_INPUT_LENGTH - 2))))
	 if (*(t->buf + i) == '\b')	 /* backspace */
	    if (k) 
	    {  /* more than one char ? */
	       if (*(tmp + --k) == '$')
		  k--;
	       i++;
	    } 
	    else
	       i++;  /* no or just one char.. Skip backsp */
	 else 
         if (isascii(*(t->buf + i)) && isprint(*(t->buf + i))) 
	 {
	    /* trans char, double for '$' (printf)	*/
	    if ((*(tmp + k) = *(t->buf + i)) == '$')
	       *(tmp + ++k) = '$';
	    k++;
	    i++;
	 } 
	 else
	    i++;
      else 
      {
	 *(tmp + k) = 0;
	 if (*tmp == '!')
	    strcpy(tmp, t->last_input);
	 else 
	 if (*tmp == '^') 
	 {
	    if (!(failed_subst = perform_subst(t, t->last_input, tmp)))
	       strcpy(t->last_input, tmp);
	 } 
	 else
	    strcpy(t->last_input, tmp);

	 if (!failed_subst)
	    write_to_q(tmp, &t->input, 0);

	 if (t->snoop_by) {
	    send_to_q("% ", t->snoop_by);
	    send_to_q(tmp, t->snoop_by);
	    send_to_q("\n\r", t->snoop_by);
	 }

	 if (flag) {
	    sprintf(buffer, "Line too long.  Truncated to:\n\r%s\n\r", tmp);
	    if (write_to_descriptor(t->descriptor, buffer) < 0)
	       return(-1);

	    /* skip the rest of the line */
	    for (; !ISNEWL(*(t->buf + i)); i++)
	       ;
	 }

	 /* find end of entry */
	 for (; ISNEWL(*(t->buf + i)); i++)
	    ;

	 /* squelch the entry from the buffer */
	 for (squelch = 0; ; squelch++)
	    if ((*(t->buf + squelch) = *(t->buf + i + squelch)) == '\0')
	       break;
	 k = 0;
	 i = 0;
      }
   }
   return(1);
}


/*
 * perform substitution for the '^..^' csh-esque syntax
 * orig is the orig string (i.e. the one being modified.
 * subst contains the substition string, i.e. "^telm^tell"
 */
int perform_subst(dsdata *t, char *orig, char *subst)
{
  char neww[MAX_INPUT_LENGTH + 5];

  char *first, *second, *strpos;

  /*
   * first is the position of the beginning of the first string (the one
   * to be replaced
   */
  first = subst + 1;

  /* now find the second '^' */
  if (!(second = strchr(first, '^'))) {
    send_to_q("Invalid substitution.\r\n", t);
    return 1;
  }
  /* terminate "first" at the position of the '^' and make 'second' point
   * to the beginning of the second string */
  *(second++) = '\0';

  /* now, see if the contents of the first string appear in the original */
  if (!(strpos = strstr(orig, first))) {
    send_to_q("Invalid substitution.\r\n", t);
    return 1;
  }
  /* now, we construct the new string for output. */

  /* first, everything in the original, up to the string to be replaced */
  strncpy(neww, orig, (strpos - orig));
  neww[(strpos - orig)] = '\0';

  /* now, the replacement string */
  strncat(neww, second, (MAX_INPUT_LENGTH - strlen(neww) - 1));

  /* now, if there's anything left in the original after the string to
   * replaced, copy that too. */
  if (((strpos - orig) + strlen(first)) < strlen(orig))
    strncat(neww, strpos + strlen(first), (MAX_INPUT_LENGTH - strlen(neww) - 1));

  /* terminate the string in case of an overflow from strncat */
  neww[MAX_INPUT_LENGTH - 1] = '\0';
  strcpy(subst, neww);

  return 0;
}

void cleanup_after_linkloss(dsdata *d)
{
  extern void wax_room(rmdata *r);
  extern void wax_object(obdata *o);
  extern void wax_mobile(chdata *ch);

  if (OBJ_EDITTED(d->character))
  {
    wax_object(OBJ_EDITTED(d->character));
    FREENULL(OBJ_EDITTED(d->character));
    sprintf(buf, "SYSUPD: Freeing editted object on %s.", GET_NAME(d->character));
    mudlog(buf, NRM, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
  }

  if (ROOM_EDITTED(d->character))
  {
    wax_room(ROOM_EDITTED(d->character));
    // free it, since it's stuck on a character
    FREENULL(ROOM_EDITTED(d->character));
    sprintf(buf, "SYSUPD: Freeing editted room on %s.", GET_NAME(d->character));
    mudlog(buf, NRM, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
  }

  if (MOB_EDITTED(d->character))
  {
    wax_mobile(MOB_EDITTED(d->character));
    FREENULL(MOB_EDITTED(d->character));
    sprintf(buf, "SYSUPD: Freeing editted mobile on %s.", GET_NAME(d->character));
    mudlog(buf, NRM, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
  }

  if (PSTR1(d->character))
  {
    FREENULL(PSTR1(d->character));
    sprintf(buf, "SYSUPD: Freeing PSTR1 on %s.", GET_NAME(d->character));
    mudlog(buf, NRM, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
  }

  if (PSTR2(d->character))
  {
    FREENULL(PSTR2(d->character));
	sprintf(buf, "SYSUPD: Freeing PSTR2 on %s.", GET_NAME(d->character));
    mudlog(buf, NRM, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
  }

  FREENULL(d->character->tmp_skills);
}

void close_socket(dsdata *d)
{
  dsdata *temp;

  if (d->cdesc)
    close_client_socket(d);

  close(d->descriptor);
  flush_queues(d);

  /* Forget snooping */
  if (d->snooping)
    d->snooping->snoop_by = NULL;

  if (d->snoop_by) {
    send_to_q("Your victim is no longer among us.\r\n", d->snoop_by);
    d->snoop_by->snooping = NULL;
  }

  if (d->room_snooping)
    remove_room_snooper(&world[d->room_snooping], d);

  if (d->character) 
  {
    if (STATE(d) == CON_PLAYING) 
    {
      // MUST check to see if this guy had spawned process off in some editor
      // if so, kill its child

      if (d->child_pid > 0)
      {
      	sprintf(buf, "Closing child shell: %s.", GET_NAME(d->character));
      	mudlog(buf, BRF, MAX(LEV_IMM, GET_INVIS_LEV(d->character)),TRUE);
	if ( kill(d->child_pid, SIGKILL) < 0 )
	{
	  perror("shell kill:");
	  sprintf(buf, "%%1%%BSYSWAR%%0: Error in child kill, recommend immediate shutdown.");
      	  mudlog(buf, BRF, LEV_IMM,TRUE);
	}
	d->child_pid = -1;
      }

      REMOVE_BIT(PLR_FLAGS(d->character), PLR_BUILDING | PLR_WRITING | PLR_MAILING);
      save_char(d->character, NOWHERE);
      act("$n has lost $s soul.", TRUE, d->character, 0, 0, TO_ROOM);
      sprintf(buf, "Closing link to: %s.", GET_NAME(d->character));
      mudlog(buf, NRM, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
     
      // unallocate any unneeded memory
      cleanup_after_linkloss(d);

      d->character->desc = NULL;
    } 
    else 
    {
      sprintf(buf, "Losing player: %s.", GET_NAME(d->character) ? GET_NAME(d->character) : "<null>");
      mudlog(buf, CMP, MAX(LEV_IMM, GET_INVIS_LEV(d->character)), TRUE);
      free_char(d->character);
    }
  } else
    mudlog("Losing descriptor without char.", CMP, LEV_IMM, TRUE);

  /* JE 2/22/95 -- part of my unending quest to make switch stable */
  if (d->original && d->original->desc)
    d->original->desc = NULL;

  REMOVE_FROM_LIST(d, descriptor_list, next);
  FREENULL(d->showstr_head);
  if (d->showstr_count)
    FREENULL(d->showstr_vector);
  free(d);
}

// updated to kick idle prelim clients  5/20/98 -jtrhone
void check_idle_passwords(void)
{
  dsdata *d, *next_d;
  cldesc *c, *next_c;

  for (d = descriptor_list; d; d = next_d) 
  {
    next_d = d->next;
    if (DESCMENU_HANDLER(d) != (void *)getpasswd && DESCMENU_HANDLER(d) != (void *)getnewpasswd && 
        DESCMENU_HANDLER(d) != (void *)confirmnewpasswd && DESCMENU_HANDLER(d) != (void *)getname && 
        DESCMENU_HANDLER(d) != (void *)confirmname)
      continue;
    if (!d->idle_tics) {
      d->idle_tics++;
      continue;
    } else {
      sprintf(buf, "SYSUPD: Descriptor #%d timed out.",d->desc_num);
      mudlog(buf, BUG, LEV_IMM, FALSE);
      echo_on(d);
      send_to_q("\r\nIdle-timed out... please reconnect.\r\n", d);
      STATE(d) = CON_CLOSE;
    }
  }

  for (c = prelim_list; c; c = next_c)
  {
    next_c = c->next;
    if (++(c->timer) > 1)
      close_prelim_socket(c);
  }
}

/*
 * I tried to universally convert Circle over to POSIX compliance, but
 * alas, some systems are still straggling behind and don't have all the
 * appropriate defines.  In particular, NeXT 2.x defines O_NDELAY but not
 * O_NONBLOCK.  Krusty old NeXT machines!  (Thanks to Michael Jones for
 * this and various other NeXT fixes.)
 */
#ifndef O_NONBLOCK
#define O_NONBLOCK O_NDELAY
#endif

void nonblock(int s)
{
  int flags;

  flags = fcntl(s, F_GETFL, 0);
  flags |= O_NONBLOCK;
  if (fcntl(s, F_SETFL, flags) < 0) {
    perror("Fatal error executing nonblock (comm.c)");
    log("fatal error executing nonblock comm.c");
    exit(1);
  }
}

void block(int s)
{
  int flags;

  flags = fcntl(s, F_GETFL, 0);
  REMOVE_BIT(flags,O_NONBLOCK);
  if (fcntl(s, F_SETFL, flags) < 0) {
    perror("Fatal error executing block (comm.c)");
    log("fatal error executing block comm.c");
    exit(1);
  }
}

/* ******************************************************************
*  signal-handling functions (formerly signals.c)                   *
****************************************************************** */

/*
 * This is an implementation of signal() using sigaction() for portability.
 * (sigaction() is POSIX; signal() is not.)  Taken from Stevens' _Advanced
 * Programming in the UNIX Environment_.  We are specifying that all system
 * calls _not_ be automatically restarted for uniformity, because BSD systems
 * do not restart select(), even if SA_RESTART is used.
 *
 * Note that NeXT 2.x is not POSIX and does not have sigaction; therefore,
 * I just define it to be the old signal.  If your system doesn't have
 * sigaction either, you can use the same fix.
 *
 * SunOS Release 4.0.2 (sun386) needs this too, according to Tim Aldric.
 */

#ifndef POSIX
#define my_signal(signo, func) signal(signo, func)
#else
sigfunc *my_signal(int signo, sigfunc * func)
{
  struct sigaction act, oact;

  act.sa_handler = func;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
#ifdef SA_INTERRUPT
  act.sa_flags |= SA_INTERRUPT;	/* SunOS */
#endif

  if (sigaction(signo, &act, &oact) < 0)
    return SIG_ERR;

  return oact.sa_handler;
}
#endif				/* NeXT */

RETSIGTYPE checkpointing(int sig)
{
  if (!tics) 
  {
    log("SYSERR: CHECKPOINT shutdown: tics not updated");
    /* abort(); */
    sleep(20);
  } 
  else
    tics = 0;

  setup_checkpoint();
}

RETSIGTYPE unrestrict_game(int sig)
{
  extern struct ban_list_element *ban_list;
  extern int num_invalid;

  mudlog("Received SIGUSR2 - completely unrestricting game (emergent)", BRF, LEV_IMM, TRUE);
  ban_list = NULL;
  restrict = 0;
  num_invalid = 0;
  my_signal(SIGUSR2, unrestrict_game);
}

RETSIGTYPE hupsig(int sig)
{
  // ignore all signals now... 3/6/98 -jtrhone
  default_sigs();

  log("Rcvd SIGHUP, SIGINT, or SIGTERM.  Freeing up and Shutting down...");

  circle_shutdown = TRUE;
}

/*
 * set up the deadlock-protection so that the MUD aborts itself if it gets
 * caught in an infinite loop for more than 8 minutes.  Doesn't work with
 * OS/2.
 */
void setup_checkpoint(void)
{
  struct itimerval itime;
  struct timeval interval;

  interval.tv_sec = 480;
  interval.tv_usec = 0;
  itime.it_interval = interval;
  itime.it_value = interval;
  setitimer(ITIMER_VIRTUAL, &itime, NULL);
  my_signal(SIGVTALRM, checkpointing);
}

void signal_setup(void)
{
  extern RETSIGTYPE grim_reaper(int sig_received);

  /*
   * user signal 2: unrestrict game.  Used for emergencies if you lock
   * yourself out of the MUD somehow.  (Duh...)
   */
  my_signal(SIGUSR2, unrestrict_game);

  setup_checkpoint();

  /* just to be on the safe side: */
  my_signal(SIGHUP, hupsig);
  my_signal(SIGINT, hupsig);
  my_signal(SIGTERM, hupsig);
  my_signal(SIGPIPE, SIG_IGN);
  my_signal(SIGALRM, SIG_IGN);

  // RoA -jtrhone, lets make a grim_reaper to take care of errant children
  my_signal(SIGCHLD, grim_reaper);
}

// for children that shouldn't exit any special way...
void default_sigs(void)
{
  my_signal(SIGHUP,  SIG_DFL);
  my_signal(SIGINT,  SIG_DFL);
  my_signal(SIGTERM, SIG_DFL);

  // however, have the children ignore these
  my_signal(SIGCHLD, SIG_IGN);
  my_signal(SIGVTALRM, SIG_IGN);
}

/* ****************************************************************
*	Public routines for system-to-player-communication	  *
*******************************************************************/
void	send_to_char(char *messg, chdata *ch)
{
  char cstr[MAX_STRING_LENGTH];
  if (ch->desc && messg)
  {
    color_decode(ch, messg, cstr);
    send_to_q(cstr, ch->desc);
  }
}

void send_to_room_snoopers(char *messg, int room, chdata *ch, int type)
{
  dsdata *s, *next_s;

  for (s = RSNOOPER(&world[room]); s; s=next_s)
  {
    next_s = s->next_rsnooper;
    if (s->character)
    {
      if (type == TO_SNOOP ||
	 (type == TO_SNOOP_HIDE && ch && can_see(s->character, ch)))
      {
        send_to_char("%B%5**%0 ",s->character);
        send_to_char(messg, s->character);
      }
    }
    else
      remove_room_snooper(&world[room], s);
  }
}

void    send_auction(char *messg)
{
  dsdata *i;
  char cstr[MAX_STRING_LENGTH];

  if (messg)
    for (i = descriptor_list; i; i = i->next) 
    {
      if (D_CHECK(i) && SEND_OK(i->character) &&
          !PRF_FLAGGED(i->character, PRF_NOAUCT)   &&
	  !ROOM_FLAGGED2(i->character->in_room, SOUNDPROOF))
       {
         color_decode(i->character, messg, cstr);
         send_to_q(cstr, i);
       }
    }
}

void	send_to_arena(char *messg)
{
   dsdata *i;
   char cstr[MAX_STRING_LENGTH];

   if (messg)
    for (i = descriptor_list; i; i = i->next)
     if (D_CHECK(i) && SEND_OK(i->character) &&
	 !PRF2_FLAGGED(i->character, PRF2_NOARENA))
     {
       color_decode(i->character, messg, cstr);
       send_to_q(cstr, i);
     }
}

void	send_to_all(char *messg)
{
   dsdata *i;
   char cstr[MAX_STRING_LENGTH];

   if (messg)
    for (i = descriptor_list; i; i = i->next)
     if (D_CHECK(i) && SEND_OK(i->character))
     {
       color_decode(i->character, messg, cstr);
       send_to_q(cstr, i);
     }
}

void	send_to_outdoor(char *messg)
{
  dsdata *i;
  char cstr[MAX_STRING_LENGTH];

  if (messg)
   for (i = descriptor_list; i; i = i->next)
    if (D_CHECK(i) && !INVALID_ROOM(i->character->in_room) && 
	AWAKE(i->character) && SEND_OK(i->character) && 
	OUTSIDE(i->character) && !ROOM_FLAGGED(i->character->in_room, NO_WEATHER))
    {
      color_decode(i->character, messg, cstr);
      send_to_q(cstr, i);
    }
}

void	send_to_except(char *messg, chdata *ch)
{
   dsdata *i;

   if (messg)
    for (i = descriptor_list; i; i = i->next)
     if (ch->desc != i && !i->connected)
       send_to_q(messg, i);
}

void	send_to_room(char *messg, int room)
{
  chdata *i;

  for (i = world[room].people; i; i = i->next_in_room)
    send_to_char(messg, i);
  
  send_to_room_snoopers(messg, room, NULL, TO_SNOOP);
}

/* send to ppl in room who arent building/writing/mailing */
void	send_to_room_not_busy(char *messg, int room)
{
  chdata *i;
  char mesg[MAX_STRING_LENGTH -10];

  str_cpy(mesg, messg, MAX_STRING_LENGTH-10, "send_to_room_not_busy");
  str_cat(mesg, "\n\r", MAX_STRING_LENGTH-10, "send_to_room_not_busy");

  for (i = world[room].people; i; i = i->next_in_room)
   if (AWAKE(i) && SEND_OK(i))
    send_to_char(mesg, i);

  send_to_room_snoopers(mesg, room, NULL, TO_SNOOP);
}

/* send to ppl in zone (outside) who arent building/writing/mailing */
void	send_to_zone_outside(char *messg, int zone)
{
  int r;

  for (r = 0; r < top_of_world; r++)
   if (world[r].zone == zone && WEATHER_ROOM(r))
     send_to_room_not_busy(messg, r);
}

void	send_to_room_except(char *messg, int room, chdata *ch)
{
  chdata *i;

  for (i = world[room].people; i; i = i->next_in_room)
   if (i != ch)
    send_to_char(messg, i);
}


void send_to_room_except_two(char *messg, int room, chdata *ch1, chdata *ch2)
{
  chdata *i;

  for (i = world[room].people; i; i = i->next_in_room)
   if (i != ch1 && i != ch2)
    send_to_char(messg, i);
}


char *ACTNULL = "<NULL>";

#define CHECK_NULL(pointer, expression) \
  if ((pointer) == NULL) i = ACTNULL; else i = (expression);

/* higher-level communication: the act() function */
void perform_act(char *orig,     chdata *ch, obdata *obj, void *vict_obj, chdata *to, int type)
{
  register char *i = NULL, *buf;
  static char lbuf[MAX_STRING_LENGTH];
  char cstr[MAX_STRING_LENGTH];
  char *debug_string;

  buf = lbuf;

  debug_string = orig; // so we can log violating strings from beginning -roa

  for (;;) {
    if (*orig == '$') {
      switch (*(++orig)) {
      case 'n':
	i = PERS(ch, to);
	break;
      case 'N':
	CHECK_NULL(vict_obj, PERS((chdata *) vict_obj, to));
	break;
      case 'm':
	i = HMHR(ch);
	break;
      case 'M':
	CHECK_NULL(vict_obj, HMHR((chdata *) vict_obj));
	break;
      case 's':
	i = HSHR(ch);
	break;
      case 'S':
	CHECK_NULL(vict_obj, HSHR((chdata *) vict_obj));
	break;
      case 'e':
	i = HSSH(ch);
	break;
      case 'E':
	CHECK_NULL(vict_obj, HSSH((chdata *) vict_obj));
	break;
      case 'o':
	CHECK_NULL(obj, OBJN(obj, to));
	break;
      case 'O':
	CHECK_NULL(vict_obj, OBJN((obdata *) vict_obj, to));
	break;
      case 'p':
	CHECK_NULL(obj, OBJS(obj, to));
	break;
      case 'P':
	CHECK_NULL(vict_obj, OBJS((obdata *) vict_obj, to));
	break;
      case 'a':
	CHECK_NULL(obj, SANA(obj));
	break;
      case 'A':
	CHECK_NULL(vict_obj, SANA((obdata *) vict_obj));
	break;
      case 'T':
	CHECK_NULL(vict_obj, (char *) vict_obj);
	break;
      case 'F':
	CHECK_NULL(vict_obj, fname((char *) vict_obj));
	break;
      case '$':
	i = "$";
	break;
      default:
	mudlog("SYSERR: Illegal $-code to act():", BRF, LEV_IMM, FALSE);
	sprintf(buf1, "SYSERR: (%s)", debug_string);
	mudlog(buf1, BRF, LEV_IMM, FALSE);
	return;
	break;
      }
      while ((*buf = *(i++)))
	buf++;
      orig++;
    } else if (!(*(buf++) = *(orig++)))
      break;
  }

  *(--buf) = '\r';
  *(++buf) = '\n';
  *(++buf) = '\0';

  CCAP(lbuf); 

  // roa snoop/zone/world additions -jtrhone
  switch (type) {
    case TO_SNOOP:
    case TO_SNOOP_HIDE:
      send_to_room_snoopers(lbuf, to->in_room, to, type);
      return;
    default:
      break;
  }

  if (!INCHAT(to))
  {
    color_decode(to, lbuf, cstr);
    send_to_q(cstr, to->desc);
  }
}

#define SENDOK(ch) ((ch)->desc && (AWAKE(ch) || sleep) && SEND_OK((ch)))

void act(char *str, int hide_invisible, chdata *ch, obdata *obj, void *vict_obj, int type)
{
  chdata *to;
  static int sleep;

  if (!str || !*str)
    return;

  /*
   * Warning: the following TO_SLEEP code is a hack.
   * 
   * I wanted to be able to tell act to deliver a message regardless of sleep
   * without adding an additional argument.  TO_SLEEP is 128 (a single bit
   * high up).  It's ONLY legal to combine TO_SLEEP with one other TO_x
   * command.  It's not legal to combine TO_x's with each other otherwise.
   */

  /* check if TO_SLEEP is there, and remove it if it is. */
  if ((sleep = (type & TO_SLEEP)))
    type &= ~TO_SLEEP;

  if (type == TO_CHAR) {
    if (ch && SENDOK(ch))
      perform_act(str, ch, obj, vict_obj, ch, type);
    return;
  }
  if (type == TO_VICT) {
    if ((to = (chdata *) vict_obj) && SENDOK(to))
      perform_act(str, ch, obj, vict_obj, to, type);
    return;
  }

  /* ASSUMPTION: at this point we know type must be TO_NOTVICT or TO_ROOM */

  if (ch && !IN_NOWHERE(ch))
    to = world[ch->in_room].people;
  else if (obj && !IN_NOWHERE(obj))
    to = world[obj->in_room].people;
  else {
    log("SYSERR: no valid target to act()!");
    return;
  }

  if (type == TO_ROOM)	// send to room snoopers, one time
  {
    if (hide_invisible)
      perform_act(str, ch, obj, vict_obj, ch, TO_SNOOP_HIDE);
    else
      perform_act(str, ch, obj, vict_obj, ch, TO_SNOOP);
  }

  switch(type) {
    case TO_ROOM:
    case TO_NOTVICT:
      for (; to; to = to->next_in_room)
        if (SENDOK(to) && !(hide_invisible && ch && !can_see(to, ch)) &&
      	    (to != ch) && (type == TO_ROOM || (to != vict_obj)))
          perform_act(str, ch, obj, vict_obj, to, type);
      return;

    case TO_ZONE_MORTLOG:
      for (to=character_list; to && ch; to=to->next)
	if (SENDOK(to) && !(hide_invisible && ch && !can_see(to, ch)) &&
	    (to != ch) && PRF_FLAGGED(to, PRF_MORTLOG) && 
	    world[to->in_room].zone == world[ch->in_room].zone)
          perform_act(str, ch, obj, vict_obj, to, type);
      return;

    case TO_WORLD_MORTLOG:
      for (to=character_list; to && ch; to=to->next)
	if (SENDOK(to) && !(hide_invisible && ch && !can_see(to, ch)) &&
	    (to != ch) && PRF_FLAGGED(to, PRF_MORTLOG)) 
          perform_act(str, ch, obj, vict_obj, to, type);
      return;
  }
}