pennmush-1.8.3p3/game/data/
pennmush-1.8.3p3/game/log/
pennmush-1.8.3p3/game/save/
pennmush-1.8.3p3/game/txt/evt/
pennmush-1.8.3p3/game/txt/nws/
pennmush-1.8.3p3/po/
pennmush-1.8.3p3/win32/msvc.net/
pennmush-1.8.3p3/win32/msvc6/
/**
 * \file bsd.c
 *
 * \brief Network communication through BSD sockets for PennMUSH.
 *
 * While mysocket.c provides low-level functions for working with
 * sockets, bsd.c focuses on player descriptors, a higher-level
 * structure that tracks all information associated with a connection,
 * and through which connection i/o is done.
 *
 *
 */

#include "copyrite.h"
#include "config.h"

#include <stdio.h>
#include <stdarg.h>
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#ifdef WIN32
#define FD_SETSIZE 256
#include <windows.h>
#include <winsock.h>
#include <io.h>
#define EINTR WSAEINTR
#define EWOULDBLOCK WSAEWOULDBLOCK
#define MAXHOSTNAMELEN 32
#pragma warning( disable : 4761)        /* disable warning re conversion */
#else                           /* !WIN32 */
#ifdef I_SYS_FILE
#include <sys/file.h>
#endif
#ifdef I_SYS_TIME
#include <sys/time.h>
#ifdef TIME_WITH_SYS_TIME
#include <time.h>
#endif
#else
#include <time.h>
#endif
#include <sys/ioctl.h>
#include <errno.h>
#ifdef I_SYS_SOCKET
#include <sys/socket.h>
#endif
#ifdef I_NETINET_IN
#include <netinet/in.h>
#endif
#ifdef I_NETDB
#include <netdb.h>
#endif
#ifdef I_SYS_PARAM
#include <sys/param.h>
#endif
#ifdef I_SYS_STAT
#include <sys/stat.h>
#endif
#endif                          /* !WIN32 */
#include <fcntl.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#ifdef I_SYS_SELECT
#include <sys/select.h>
#endif
#ifdef I_UNISTD
#include <unistd.h>
#endif
#include <limits.h>
#ifdef I_FLOATINGPOINT
#include <floatingpoint.h>
#endif
#ifdef HAVE_IEEEFP_H
#include <ieeefp.h>
#endif
#include <locale.h>
#include <setjmp.h>

#include "conf.h"

#include "wait.h"

#include "externs.h"
#include "chunk.h"
#include "mushdb.h"
#include "dbdefs.h"
#include "flags.h"
#include "lock.h"
#include "help.h"
#include "match.h"
#include "ansi.h"
#include "pueblo.h"
#include "parse.h"
#include "access.h"
#include "command.h"
#include "version.h"
#include "patches.h"
#include "mysocket.h"
#include "ident.h"

#ifdef INFO_SLAVE
#ifdef WIN32
#undef INFO_SLAVE
#else
#include "lookup.h"
#endif
#endif

#include "strtree.h"
#include "log.h"
#include "pcre.h"
#ifdef HAS_OPENSSL
#include "myssl.h"
#endif
#include "mymalloc.h"
#include "extmail.h"
#include "attrib.h"
#include "game.h"
#include "dbio.h"
#include "confmagic.h"

#ifdef HAS_GETRLIMIT
void init_rlimit(void);
#endif


/* BSD 4.2 and maybe some others need these defined */
#ifndef FD_ZERO
/** An fd_set is 4 bytes */
#define fd_set int
/** Clear an fd_set */
#define FD_ZERO(p)       (*p = 0)
/** Set a bit in an fd_set */
#define FD_SET(n,p)      (*p |= (1<<(n)))
/** Clear a bit in an fd_set */
#define FD_CLR(n,p)      (*p &= ~(1<<(n)))
/** Check a bit in an fd_set */
#define FD_ISSET(n,p)    (*p & (1<<(n)))
#endif                          /* defines for BSD 4.2 */

#ifdef HAS_GETRUSAGE
void rusage_stats(void);
#endif
int que_next(void);             /* from cque.c */

void dispatch(void);            /* from timer.c */
dbref email_register_player(const char *name, const char *email, const char *host, const char *ip);     /* from player.c */

#ifdef SUN_OS
static int extrafd;
#endif
int shutdown_flag = 0;          /**< Is it time to shut down? */
void chat_player_announce(dbref player, char *msg, int ungag);

static int login_number = 0;
static int under_limit = 1;

char cf_motd_msg[BUFFER_LEN];   /**< The message of the day */
char cf_wizmotd_msg[BUFFER_LEN];        /**< The wizard motd */
char cf_downmotd_msg[BUFFER_LEN];       /**< The down message */
char cf_fullmotd_msg[BUFFER_LEN];       /**< The 'mush full' message */
static char poll_msg[DOING_LEN];
char confname[BUFFER_LEN];      /**< Name of the config file */
char errlog[BUFFER_LEN];        /**< Name of the error log file */

/** Is this descriptor connected to a telnet-compatible terminal? */
#define TELNET_ABLE(d) ((d)->conn_flags & (CONN_TELNET | CONN_TELNET_QUERY))


/* When the mush gets a new connection, it tries sending a telnet
 * option negotiation code for setting client-side line-editing mode
 * to it. If it gets a reply, a flag in the descriptor struct is
 * turned on indicated telnet-awareness.
 * 
 * If the reply indicates that the client supports linemode, further
 * instructions as to what linemode options are to be used is sent.
 * Those options: Client-side line editing, and expanding literal
 * client-side-entered tabs into spaces.
 * 
 * Option negotation requests sent by the client are processed,
 * with the only one we confirm rather than refuse outright being
 * suppress-go-ahead, since a number of telnet clients try it.
 *
 * The character 255 is the telnet option escape character, so when it
 * is sent to a telnet-aware client by itself (Since it's also often y-umlaut)
 * it must be doubled to escape it for the client. This is done automatically,
 * and is the original purpose of adding telnet option support.
 */

/* Telnet codes */
#define IAC 255                 /**< interpret as command: */
#define NOP 241                 /**< no operation */
#define AYT 246                 /**< are you there? */
#define DONT 254                /**< you are not to use option */
#define DO      253             /**< please, you use option */
#define WONT 252                /**< I won't use option */
#define WILL    251             /**< I will use option */
#define SB      250             /**< interpret as subnegotiation */
#define SE      240             /**< end sub negotiation */
#define TN_SGA 3                /**< Suppress go-ahead */
#define TN_LINEMODE 34          /**< Line mode */
#define TN_NAWS 31              /**< Negotiate About Window Size */
#define TN_TTYPE 24             /**< Ask for termial type information */
static void test_telnet(DESC *d);
static void setup_telnet(DESC *d);
static int handle_telnet(DESC *d, unsigned char **q, unsigned char *qend);

/** Iterate through a list of descriptors, and do something with those
 * that are connected.
 */
#define DESC_ITER_CONN(d) \
        for(d = descriptor_list;(d);d=(d)->next) \
          if((d)->connected)

/** Is a descriptor hidden? */
#define Hidden(d)        ((d->hide == 1) && Can_Hide(d->player))

static const char *create_fail =
  "Either there is already a player with that name, or that name is illegal.";
static const char *password_fail = "The password is invalid (or missing).";
static const char *register_fail =
  "Unable to register that player with that email address.";
static const char *register_success =
  "Registration successful! You will receive your password by email.";
static const char *shutdown_message = "Going down - Bye";
#ifdef HAS_OPENSSL
static const char *ssl_shutdown_message =
  "GAME: SSL connections must be dropped, sorry.";
#endif
static const char *asterisk_line =
  "**********************************************************************";
/** Where we save the descriptor info across reboots. */
#define REBOOTFILE              "reboot.db"

#if 0
/* For translation */
static void dummy_msgs(void);
static void
dummy_msgs()
{
  char *temp;
  temp = T("Either that player does not exist, or has a different password.");
  temp =
    T
    ("Either there is already a player with that name, or that name is illegal.");
  temp = T("The password is invalid (or missing).");
  temp = T("Unable to register that player with that email address.");
  temp = T("Registration successful! You will receive your password by email.");
  temp = T("Going down - Bye");
  temp = T("GAME: SSL connections must be dropped, sorry.");
}

#endif

DESC *descriptor_list = NULL;   /**< The linked list of descriptors */

static int sock;
#ifdef HAS_OPENSSL
static int sslsock = 0;
SSL *ssl_master_socket = NULL;  /**< Master SSL socket for ssl port */
#endif
static int ndescriptors = 0;
#ifdef WIN32
static WSADATA wsadata;
#endif
int restarting = 0;     /**< Are we restarting the server after a reboot? */
int maxd = 0;

extern const unsigned char *tables;

sig_atomic_t signal_shutdown_flag = 0;  /**< Have we caught a shutdown signal? */
sig_atomic_t signal_dump_flag = 0;      /**< Have we caught a dump signal? */

#ifndef BOOLEXP_DEBUGGING
#ifdef WIN32SERVICES
void shutdown_checkpoint(void);
void mainthread(int argc, char **argv);
#else
int main(int argc, char **argv);
#endif
#endif
void set_signals(void);
static struct timeval *timeval_sub(struct timeval *now, struct timeval *then);
#ifdef WIN32
/** Windows doesn't have gettimeofday(), so we implement it here */
#define our_gettimeofday(now) win_gettimeofday((now))
static void win_gettimeofday(struct timeval *now);
#else
/** A wrapper for gettimeofday() in case the system doesn't have it */
#define our_gettimeofday(now) gettimeofday((now), (struct timezone *)NULL)
#endif
static long int msec_diff(struct timeval *now, struct timeval *then);
static struct timeval *msec_add(struct timeval *t, int x);
static void update_quotas(struct timeval *last, struct timeval *current);

int how_many_fds(void);
static void shovechars(Port_t port, Port_t sslport);
static int test_connection(int newsock);
#ifndef INFO_SLAVE
static DESC *new_connection(int oldsock, int *result, int use_ssl);
#endif

static void clearstrings(DESC *d);

/** A block of cached text. */
typedef struct fblock {
  unsigned char *buff;    /**< Pointer to the block as a string */
  size_t len;             /**< Length of buff */
} FBLOCK;

/** The complete collection of cached text files. */
struct fcache_entries {
  FBLOCK connect_fcache[2];     /**< connect.txt and connect.html */
  FBLOCK motd_fcache[2];        /**< motd.txt and motd.html */
  FBLOCK wizmotd_fcache[2];     /**< wizmotd.txt and wizmotd.html */
  FBLOCK newuser_fcache[2];     /**< newuser.txt and newuser.html */
  FBLOCK register_fcache[2];    /**< register.txt and register.html */
  FBLOCK quit_fcache[2];        /**< quit.txt and quit.html */
  FBLOCK down_fcache[2];        /**< down.txt and down.html */
  FBLOCK full_fcache[2];        /**< full.txt and full.html */
  FBLOCK guest_fcache[2];       /**< guest.txt and guest.html */
};

static struct fcache_entries fcache;
static void fcache_dump(DESC *d, FBLOCK fp[2], const unsigned char *prefix);
static int fcache_read(FBLOCK *cp, const char *filename);
static void logout_sock(DESC *d);
static void shutdownsock(DESC *d);
DESC *initializesock(int s, char *addr, char *ip, int use_ssl);
int process_output(DESC *d);
/* Notify.c */
extern void free_text_block(struct text_block *t);
extern void add_to_queue(struct text_queue *q, const unsigned char *b, int n);
extern int queue_write(DESC *d, const unsigned char *b, int n);
extern int queue_eol(DESC *d);
extern int queue_newwrite(DESC *d, const unsigned char *b, int n);
extern int queue_string(DESC *d, const char *s);
extern int queue_string_eol(DESC *d, const char *s);
extern void freeqs(DESC *d);
static void welcome_user(DESC *d);
static void dump_info(DESC *call_by);
static void save_command(DESC *d, const unsigned char *command);
static int process_input(DESC *d, int output_ready);
static void process_input_helper(DESC *d, char *tbuf1, int got);
static void set_userstring(unsigned char **userstring, const char *command);
static void process_commands(void);
static int do_command(DESC *d, char *command);
static void parse_puebloclient(DESC *d, char *command);
static int dump_messages(DESC *d, dbref player, int new);
static int check_connect(DESC *d, const char *msg);
static void parse_connect(const char *msg, char *command, char *user,
                          char *pass);
static void close_sockets(void);
dbref find_player_by_desc(int port);
static DESC *lookup_desc(dbref executor, const char *name);
void NORETURN bailout(int sig);
void WIN32_CDECL signal_shutdown(int sig);
void WIN32_CDECL signal_dump(int sig);
void reaper(int sig);
#ifndef WIN32
sig_atomic_t dump_error = 0;
WAIT_TYPE dump_status = 0;
#ifdef INFO_SLAVE
sig_atomic_t slave_error = 0;
#endif
#endif
extern pid_t forked_dump_pid;   /**< Process id of forking dump process */
static void dump_users(DESC *call_by, char *match, int doing);
static const char *time_format_1(time_t dt);
static const char *time_format_2(time_t dt);
static void announce_connect(dbref player, int isnew, int num);
static void announce_disconnect(DESC *saved);
void inactivity_check(void);
void reopen_logs(void);
void load_reboot_db(void);

static int in_suid_root_mode = 0;

#ifndef BOOLEXP_DEBUGGING
#ifdef WIN32SERVICES
/* Under WIN32, MUSH is a "service", so we just start a thread here.
 * The real "main" is in win32/services.c
 */
void
mainthread(int argc, char **argv)
#else
/** The main function.
 * \param argc number of arguments.
 * \param argv vector of arguments.
 * \return exit code.
 */
int
main(int argc, char **argv)
#endif                          /* WIN32SERVICES */
{
#ifdef AUTORESTART
  FILE *id;
#endif
  FILE *newerr;
  bool detach_session = 1;
  int logfile_pos;

/* disallow running as root on unix.
 * This is done as early as possible, before translation is initialized.
 * Hence, no T()s around messages.
 */
#ifndef WIN32
#ifdef HAVE_GETUID
  if (getuid() == 0) {
    fputs("Please run the server as another user.\n", stderr);
    fputs("PennMUSH will not run as root as a security measure.\n", stderr);
    return 1;
  }
  /* Add suid-root checks here. */
#endif
#ifdef HAVE_GETEUID
  if (geteuid() == 0) {
    fprintf(stderr, "The  %s binary is set suid and owned by root.\n", argv[0]);
#ifdef HAVE_SETEUID
    fprintf(stderr, "Changing effective user to %d.\n", getuid());
    seteuid(getuid());
    in_suid_root_mode = 1;
#endif
  }
#endif                          /* HAVE_GETEUID */
#endif                          /* !WIN32 */

  /* read the configuration file */
  if (argc < 2) {
    fprintf(stderr,
            "WARNING: Called without a config file argument. Assuming mush.cnf\n");
    strcpy(confname, "mush.cnf");
  } else {
    int n;
    for (n = 1; n < argc; n++) {
      if (argv[n][0] == '-') {
        if (strcmp(argv[n], "--no-session") == 0)
          detach_session = 0;
        else
          fprintf(stderr, "Unknown option \"%s\"\n", argv[n]);
      } else {
        mush_strncpy(confname, argv[n], BUFFER_LEN);
        logfile_pos = n + 1;
        break;
      }
    }
  }


#ifdef HAVE_FORK
  /* Fork off and detach from controlling terminal. */
  if (detach_session) {
    pid_t child;

    child = fork();
    if (child < 0) {
      /* Print a warning and continue */
      perror("fork");
    } else if (child > 0) {
      /* Parent process of a successful fork() */
      return EXIT_SUCCESS;
    } else {
      /* Child process */
      if (new_process_session() < 0)
        perror("Couldn't create a new process session");
    }
  }
#endif

#ifdef WIN32
  {
    unsigned short wVersionRequested = MAKEWORD(1, 1);
    int err;

    /* Need to include library: wsock32.lib for Windows Sockets */
    err = WSAStartup(wVersionRequested, &wsadata);
    if (err) {
      printf(T("Error %i on WSAStartup\n"), err);
      exit(1);
    }
  }
#endif                          /* WIN32 */

#ifdef HAS_GETRLIMIT
  init_rlimit();                /* unlimit file descriptors */
#endif

  /* These are FreeBSDisms to fix floating point exceptions */
#ifdef HAS_FPSETROUND
  fpsetround(FP_RN);
#endif
#ifdef HAS_FPSETMASK
  fpsetmask(0L);
#endif

  time(&mudtime);

  /* If we have setlocale, call it to set locale info
   * from environment variables
   */
#ifdef HAS_SETLOCALE
  {
    char *loc;
    if ((loc = setlocale(LC_CTYPE, "")) == NULL)
      do_rawlog(LT_ERR, "Failed to set ctype locale from environment.");
    else
      do_rawlog(LT_ERR, "Setting ctype locale to %s", loc);
    if ((loc = setlocale(LC_TIME, "")) == NULL)
      do_rawlog(LT_ERR, "Failed to set time locale from environment.");
    else
      do_rawlog(LT_ERR, "Setting time locale to %s", loc);
#ifdef LC_MESSAGES
    if ((loc = setlocale(LC_MESSAGES, "")) == NULL)
      do_rawlog(LT_ERR, "Failed to set messages locale from environment.");
    else
      do_rawlog(LT_ERR, "Setting messages locale to %s", loc);
#else
    do_rawlog(LT_ERR, "No support for message locale.");
#endif
    if ((loc = setlocale(LC_COLLATE, "")) == NULL)
      do_rawlog(LT_ERR, "Failed to set collate locale from environment.");
    else
      do_rawlog(LT_ERR, "Setting collate locale to %s", loc);
  }
#endif
#ifdef HAS_TEXTDOMAIN
  textdomain("pennmush");
#endif
#ifdef HAS_BINDTEXTDOMAIN
  bindtextdomain("pennmush", "../po");
#endif

  /* Build the locale-dependant tables used by PCRE */
  tables = pcre_maketables();

/* this writes a file used by the restart script to check for active mush */
#ifdef AUTORESTART
  id = fopen("runid", "w");
  fprintf(id, "%d", getpid());
  fclose(id);
#endif

  init_game_config(confname);

  /* save a file descriptor */
  reserve_fd();
#ifdef SUN_OS
  extrafd = open("/dev/null", O_RDWR);
#endif

  /* decide if we're in @shutdown/reboot */
  restarting = 0;
  newerr = fopen(REBOOTFILE, "r");
  if (newerr) {
    restarting = 1;
    fclose(newerr);
  }

  if (init_game_dbs() < 0) {
    do_rawlog(LT_ERR, T("ERROR: Couldn't load databases! Exiting."));
    exit(2);
  }

  init_game_postdb(confname);

  globals.database_loaded = 1;

  set_signals();

#ifdef INFO_SLAVE
  init_info_slave();
#endif

  /* go do it */
#ifdef CSRI
#ifdef CSRI_DEBUG
  mal_verify(1);
#endif
#ifdef CSRI_TRACE
  mal_leaktrace(1);
#endif
#endif
  load_reboot_db();

  shovechars((Port_t) TINYPORT, (Port_t) SSLPORT);
#ifdef CSRI
#ifdef CSRI_DEBUG
  mal_verify(1);
#endif
#endif

  /* someone has told us to shut down */
#ifdef WIN32SERVICES
  /* Keep service manager happy */
  shutdown_checkpoint();
#endif

  shutdown_queues();

#ifdef WIN32SERVICES
  /* Keep service manager happy */
  shutdown_checkpoint();
#endif

  close_sockets();
  sql_shutdown();

#ifdef INFO_SLAVE
  kill_info_slave();
#endif

#ifdef WIN32SERVICES
  /* Keep service manager happy */
  shutdown_checkpoint();
#endif

  dump_database();

  local_shutdown();

  end_all_logs();

#ifdef CSRI
#ifdef CSRI_PROFILESIZES
  mal_statsdump(stderr);
#endif
#ifdef CSRI_TRACE
  mal_dumpleaktrace(stderr);
#endif
  fflush(stderr);
#endif

#ifdef WIN32SERVICES
  /* Keep service manager happy */
  shutdown_checkpoint();
#endif

#ifdef HAS_GETRUSAGE
  rusage_stats();
#endif                          /* HAS_RUSAGE */

  do_rawlog(LT_ERR, T("MUSH shutdown completed."));

  closesocket(sock);
#ifdef WIN32
#ifdef WIN32SERVICES
  shutdown_checkpoint();
#endif
  WSACleanup();                 /* clean up */
#else
  exit(0);
#endif
}
#endif                          /* BOOLEXP_DEBUGGING */

/** Close and reopen the logfiles - called on SIGHUP */
void
reopen_logs(void)
{
  FILE *newerr;
  /* close up the log files */
  end_all_logs();
  newerr = fopen(errlog, "a");
  if (!newerr) {
    fprintf(stderr,
            T("Unable to open %s. Error output continues to stderr.\n"),
            errlog);
  } else {
    if (!freopen(errlog, "a", stderr)) {
      printf(T("Ack!  Failed reopening stderr!"));
      exit(1);
    }
    setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
    fclose(newerr);
  }
  start_all_logs();
}

/** Install our default signal handlers. */
void
set_signals(void)
{

#ifndef WIN32
  /* we don't care about SIGPIPE, we notice it in select() and write() */
  ignore_signal(SIGPIPE);
  install_sig_handler(SIGUSR2, signal_dump);
  install_sig_handler(SIGINT, signal_shutdown);
  install_sig_handler(SIGTERM, bailout);
#else
  /* Win32 stuff: 
   *   No support for SIGUSR2 or SIGINT.
   *   SIGTERM is never generated on NT-based Windows (according to MSDN)
   *   MSVC++ will let you get away with installing a handler anyway,
   *   but VS.NET will not. So if it's MSVC++, we give it a try.
   */
#if _MSC_VER < 1200
  install_sig_handler(SIGTERM, bailout);
#endif
#endif

#ifndef WIN32
  install_sig_handler(SIGCHLD, reaper);
#endif

}

#ifdef WIN32
/** Get the time using Windows function call.
 * Looks weird, but it works. :-P
 * \param now address to store timeval data.
 */
static void
win_gettimeofday(struct timeval *now)
{

  FILETIME win_time;

  GetSystemTimeAsFileTime(&win_time);
  /* dwLow is in 100-s nanoseconds, not microseconds */
  now->tv_usec = win_time.dwLowDateTime % 10000000 / 10;

  /* dwLow contains at most 429 least significant seconds, since 32 bits maxint is 4294967294 */
  win_time.dwLowDateTime /= 10000000;

  /* Make room for the seconds of dwLow in dwHigh */
  /* 32 bits of 1 = 4294967295. 4294967295 / 429 = 10011578 */
  win_time.dwHighDateTime %= 10011578;
  win_time.dwHighDateTime *= 429;

  /* And add them */
  now->tv_sec = win_time.dwHighDateTime + win_time.dwLowDateTime;
}

#endif

/** Return the difference between two timeval structs as a timeval struct.
 * \param now pointer to the timeval to subtract from.
 * \param then pointer to the timeval to subtract.
 * \return pointer to a statically allocated timeval of the difference.
 */
static struct timeval *
timeval_sub(struct timeval *now, struct timeval *then)
{
  static struct timeval mytime;
  mytime.tv_sec = now->tv_sec;
  mytime.tv_usec = now->tv_usec;

  mytime.tv_sec -= then->tv_sec;
  mytime.tv_usec -= then->tv_usec;
  if (mytime.tv_usec < 0) {
    mytime.tv_usec += 1000000;
    mytime.tv_sec--;
  }
  return &mytime;
}

/** Return the difference between two timeval structs in milliseconds.
 * \param now pointer to the timeval to subtract from.
 * \param then pointer to the timeval to subtract.
 * \return milliseconds of difference between them.
 */
static long int
msec_diff(struct timeval *now, struct timeval *then)
{
  long int secs = now->tv_sec - then->tv_sec;
  if (secs == 0)
    return (now->tv_usec - then->tv_usec) / 1000;
  else if (secs == 1)
    return (now->tv_usec + (1000000 - then->tv_usec)) / 100;
  else if (secs > 1)
    return (secs * 1000) + ((now->tv_usec + (1000000 - then->tv_usec)) / 1000);
  else
    return 0;
}

/** Add a given number of milliseconds to a timeval.
 * \param t pointer to a timeval struct.
 * \param x number of milliseconds to add to t.
 * \return address of static timeval struct representing the sum.
 */
static struct timeval *
msec_add(struct timeval *t, int x)
{
  static struct timeval mytime;
  mytime.tv_sec = t->tv_sec;
  mytime.tv_usec = t->tv_usec;
  mytime.tv_sec += x / 1000;
  mytime.tv_usec += (x % 1000) * 1000;
  if (mytime.tv_usec >= 1000000) {
    mytime.tv_sec += mytime.tv_usec / 1000000;
    mytime.tv_usec = mytime.tv_usec % 1000000;
  }
  return &mytime;
}

/** Update each descriptor's allowed rate of issuing commands.
 * Players are rate-limited; they may only perform up to a certain
 * number of commands per time slice. This function is run periodically
 * to refresh each descriptor's available command quota based on how
 * many slices have passed since it was last updated.
 * \param last pointer to timeval struct of last time quota was updated.
 * \param current pointer to timeval struct of current time.
 */
static void
update_quotas(struct timeval *last, struct timeval *current)
{
  int nslices;
  DESC *d;
  nslices = (int) msec_diff(current, last) / COMMAND_TIME_MSEC;

  if (nslices > 0) {
    for (d = descriptor_list; d; d = d->next) {
      d->quota += COMMANDS_PER_TIME * nslices;
      if (d->quota > COMMAND_BURST_SIZE)
        d->quota = COMMAND_BURST_SIZE;
    }
  }
}

static void
shovechars(Port_t port, Port_t sslport __attribute__ ((__unused__)))
{
  /* this is the main game loop */

  fd_set input_set, output_set;
  time_t now;
  struct timeval last_slice, current_time, then;
  struct timeval next_slice, *returned_time;
  struct timeval timeout, slice_timeout;
  int found;
  int queue_timeout;
  DESC *d, *dnext;
#ifndef INFO_SLAVE
  DESC *newd;
  int result;
#endif
  int avail_descriptors;
#ifdef INFO_SLAVE
  union sockaddr_u addr;
  socklen_t addr_len;
  int newsock;
#endif
  unsigned long input_ready, output_ready;

  if (!restarting) {
    sock = make_socket(port, SOCK_STREAM, NULL, NULL, MUSH_IP_ADDR);
    if (sock >= maxd)
      maxd = sock + 1;
#ifdef HAS_OPENSSL
    if (sslport) {
      sslsock = make_socket(sslport, SOCK_STREAM, NULL, NULL, SSL_IP_ADDR);
      ssl_master_socket = ssl_setup_socket(sslsock);
      if (sslsock >= maxd)
        maxd = sslsock + 1;
    }
#endif
  }
  our_gettimeofday(&last_slice);

  avail_descriptors = how_many_fds() - 4;
#ifdef INFO_SLAVE
  avail_descriptors -= 2;       /* reserve some more for setting up the slave */
#endif

  /* done. print message to the log */
  do_rawlog(LT_ERR, "%d file descriptors available.", avail_descriptors);
  do_rawlog(LT_ERR, "RESTART FINISHED.");

  our_gettimeofday(&then);

  while (shutdown_flag == 0) {
    our_gettimeofday(&current_time);

    update_quotas(&last_slice, &current_time);
    last_slice.tv_sec = current_time.tv_sec;
    last_slice.tv_usec = current_time.tv_usec;

    if (msec_diff(&current_time, &then) >= 1000) {
      globals.on_second = 1;
      then.tv_sec = current_time.tv_sec;
      then.tv_usec = current_time.tv_usec;
    }

    process_commands();

    /* Check signal handler flags */

#ifndef WIN32

    if (dump_error) {
      if (WIFSIGNALED(dump_status)) {
        do_rawlog(LT_ERR, T("ERROR! forking dump exited with signal %d"),
                  WTERMSIG(dump_status));
        flag_broadcast("ROYALTY WIZARD", 0,
                       T("GAME: ERROR! Forking database save failed!"));
      } else if (WIFEXITED(dump_status) && WEXITSTATUS(dump_status) == 0) {
        time(&globals.last_dump_time);
        if (DUMP_NOFORK_COMPLETE && *DUMP_NOFORK_COMPLETE)
          flag_broadcast(0, 0, "%s", DUMP_NOFORK_COMPLETE);
      }
      dump_error = 0;
      dump_status = 0;
    }
#ifdef INFO_SLAVE
    if (slave_error) {
      do_rawlog(LT_ERR, T("info_slave on pid %d exited unexpectedly!"),
                slave_error);
      slave_error = 0;
    }
#endif
#endif                          /* !WIN32 */


    if (signal_shutdown_flag) {
      flag_broadcast(0, 0, T("GAME: Shutdown by external signal"));
      do_rawlog(LT_ERR, T("SHUTDOWN by external signal"));
#ifdef AUTORESTART
      system("touch NORESTART");
#endif
      shutdown_flag = 1;
    }

    if (signal_dump_flag) {
      globals.paranoid_dump = 0;
      do_rawlog(LT_CHECK, "DUMP by external signal");
      fork_and_dump(1);
      signal_dump_flag = 0;
    }

    if (shutdown_flag)
      break;

    /* test for events */
    dispatch();

    /* any queued robot commands waiting? */
    /* timeout.tv_sec used to be set to que_next(), the number of
     * seconds before something on the queue needed to run, but this
     * caused a problem with stuff that had to be triggered by alarm
     * signal every second, so we're reduced to what's below:
     */
    queue_timeout = que_next();
    timeout.tv_sec = queue_timeout ? 1 : 0;
    timeout.tv_usec = 0;

    returned_time = msec_add(&last_slice, COMMAND_TIME_MSEC);
    next_slice.tv_sec = returned_time->tv_sec;
    next_slice.tv_usec = returned_time->tv_usec;

    returned_time = timeval_sub(&next_slice, &current_time);
    slice_timeout.tv_sec = returned_time->tv_sec;
    slice_timeout.tv_usec = returned_time->tv_usec;
    /* Make sure slice_timeout cannot have a negative time. Better
       safe than sorry. */
    if (slice_timeout.tv_sec < 0)
      slice_timeout.tv_sec = 0;
    if (slice_timeout.tv_usec < 0)
      slice_timeout.tv_usec = 0;

    FD_ZERO(&input_set);
    FD_ZERO(&output_set);
    if (ndescriptors < avail_descriptors)
      FD_SET(sock, &input_set);
#ifdef HAS_OPENSSL
    if (sslsock)
      FD_SET(sslsock, &input_set);
#endif
#ifdef INFO_SLAVE
    if (info_slave_state == INFO_SLAVE_PENDING)
      FD_SET(info_slave, &input_set);
#endif
    for (d = descriptor_list; d; d = d->next) {
      if (d->input.head) {
        timeout.tv_sec = slice_timeout.tv_sec;
        timeout.tv_usec = slice_timeout.tv_usec;
      } else
        FD_SET(d->descriptor, &input_set);
      if (d->output.head)
        FD_SET(d->descriptor, &output_set);
    }

    found = select(maxd, &input_set, &output_set, (fd_set *) 0, &timeout);
    if (found < 0) {
#ifdef WIN32
      if (found == SOCKET_ERROR && WSAGetLastError() != WSAEINTR)
#else
      if (errno != EINTR)
#endif
      {
        perror("select");
        return;
      }
#ifdef INFO_SLAVE
      if (info_slave_state == INFO_SLAVE_PENDING)
        update_pending_info_slaves();
#endif
    } else {
      /* if !found then time for robot commands */

      if (!found) {
        do_top(options.queue_chunk);
        continue;
      } else {
        do_top(options.active_q_chunk);
      }
      now = mudtime;
#ifdef INFO_SLAVE
      if (info_slave_state == INFO_SLAVE_PENDING
          && FD_ISSET(info_slave, &input_set)) {
        reap_info_slave();
      } else if (info_slave_state == INFO_SLAVE_PENDING
                 && now > info_queue_time + 30) {
        /* rerun any pending queries that got lost */
        update_pending_info_slaves();
      }

      if (FD_ISSET(sock, &input_set)) {
        addr_len = sizeof(addr);
        newsock = accept(sock, (struct sockaddr *) &addr, &addr_len);
        if (newsock < 0) {
          if (test_connection(newsock) < 0)
            continue;           /* this should _not_ be return. */
        }
        ndescriptors++;
        query_info_slave(newsock);
        if (newsock >= maxd)
          maxd = newsock + 1;
      }
#ifdef HAS_OPENSSL
      if (sslsock && FD_ISSET(sslsock, &input_set)) {
        addr_len = sizeof(addr);
        newsock = accept(sslsock, (struct sockaddr *) &addr, &addr_len);
        if (newsock < 0) {
          if (test_connection(newsock) < 0)
            continue;           /* this should _not_ be return. */
        }
        ndescriptors++;
        query_info_slave(newsock);
        if (newsock >= maxd)
          maxd = newsock + 1;
      }
#endif
#else                           /* INFO_SLAVE */
      if (FD_ISSET(sock, &input_set)) {
        if (!(newd = new_connection(sock, &result, 0))) {
          if (test_connection(result) < 0)
            continue;           /* this should _not_ be return. */
        } else {
          ndescriptors++;
          if (newd->descriptor >= maxd)
            maxd = newd->descriptor + 1;
        }
      }
#ifdef HAS_OPENSSL
      if (sslsock && FD_ISSET(sslsock, &input_set)) {
        if (!(newd = new_connection(sslsock, &result, 1))) {
          if (test_connection(result) < 0)
            continue;           /* this should _not_ be return. */
        } else {
          ndescriptors++;
          if (newd->descriptor >= maxd)
            maxd = newd->descriptor + 1;
        }
      }
#endif
#endif
      for (d = descriptor_list; d; d = dnext) {
        dnext = d->next;
        input_ready = FD_ISSET(d->descriptor, &input_set);
        output_ready = FD_ISSET(d->descriptor, &output_set);
        if (input_ready) {
          if (!process_input(d, output_ready)) {
            shutdownsock(d);
            continue;
          }
        }
        if (output_ready) {
          if (!process_output(d)) {
            shutdownsock(d);
          }
        }
      }
    }
  }
}

static int
test_connection(int newsock)
{
#ifdef WIN32
  if (newsock == INVALID_SOCKET && WSAGetLastError() != WSAEINTR)
#else
  if (errno && errno != EINTR)
#endif
  {
    perror("test_connection");
    return -1;
  }
  return newsock;
}


#ifndef INFO_SLAVE
static DESC *
new_connection(int oldsock, int *result, int use_ssl)
{
  int newsock;
  union sockaddr_u addr;
  struct hostname_info *hi;
  socklen_t addr_len;
  char tbuf1[BUFFER_LEN];
  char tbuf2[BUFFER_LEN];
  char *bp;
  char *socket_ident;
  char *chp;

  *result = 0;
  addr_len = MAXSOCKADDR;
  newsock = accept(oldsock, (struct sockaddr *) (addr.data), &addr_len);
  if (newsock < 0) {
    *result = newsock;
    return 0;
  }
  bp = tbuf2;
  hi = ip_convert(&addr.addr, addr_len);
  safe_str(hi ? hi->hostname : "", tbuf2, &bp);
  *bp = '\0';
  bp = tbuf1;
  if (USE_IDENT) {
    int timeout = IDENT_TIMEOUT;
    socket_ident = ident_id(newsock, &timeout);
    if (socket_ident) {
      /* Truncate at first non-printable character */
      for (chp = socket_ident; *chp && isprint((unsigned char) *chp); chp++) ;
      *chp = '\0';
      safe_str(socket_ident, tbuf1, &bp);
      safe_chr('@', tbuf1, &bp);
      free(socket_ident);
    }
  }
  hi = hostname_convert(&addr.addr, addr_len);
  safe_str(hi ? hi->hostname : "", tbuf1, &bp);
  *bp = '\0';
  if (Forbidden_Site(tbuf1) || Forbidden_Site(tbuf2)) {
    if (!Deny_Silent_Site(tbuf1, AMBIGUOUS)
        || !Deny_Silent_Site(tbuf2, AMBIGUOUS)) {
      do_log(LT_CONN, 0, 0, "[%d/%s/%s] %s (%s %s)", newsock, tbuf1, tbuf2,
             T("Refused connection"), T("remote port"),
             hi ? hi->port : T("(unknown)"));
    }
    shutdown(newsock, 2);
    closesocket(newsock);
#ifndef WIN32
    errno = 0;
#endif
    return 0;
  }
  do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connection opened."), newsock, tbuf1,
         tbuf2);
  set_keepalive(newsock);
  return initializesock(newsock, tbuf1, tbuf2, use_ssl);
}
#endif

static void
clearstrings(DESC *d)
{
  if (d->output_prefix) {
    mush_free((Malloc_t) d->output_prefix, "userstring");
    d->output_prefix = 0;
  }
  if (d->output_suffix) {
    mush_free((Malloc_t) d->output_suffix, "userstring");
    d->output_suffix = 0;
  }
}

/* Display a cached text file. If a prefix line was given,
 * display that line before the text file, but only if we've
 * got a text file to display
 */
static void
fcache_dump(DESC *d, FBLOCK fb[2], const unsigned char *prefix)
{
  /* If we've got nothing nice to say, don't say anything */
  if (!fb[0].buff && !((d->conn_flags & CONN_HTML) && fb[1].buff))
    return;
  /* We've got something to say */
  if (prefix) {
    queue_newwrite(d, prefix, u_strlen(prefix));
    queue_eol(d);
  }
  if (d->conn_flags & CONN_HTML) {
    if (fb[1].buff)
      queue_newwrite(d, fb[1].buff, fb[1].len);
    else
      queue_write(d, fb[0].buff, fb[0].len);
  } else
    queue_write(d, fb[0].buff, fb[0].len);
}


static int
fcache_read(FBLOCK *fb, const char *filename)
{
  if (!fb || !filename)
    return -1;

  /* Free prior cache */
  if (fb->buff) {
    mush_free(fb->buff, "fcache_data");
  }

  fb->buff = NULL;
  fb->len = 0;

#ifdef WIN32
  /* Win32 read code here */
  {
    HANDLE fh;
    BY_HANDLE_FILE_INFORMATION sb;
    DWORD r = 0;


    if ((fh = CreateFile(filename, GENERIC_READ, 0, NULL,
                         OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)
      return -1;

    if (!GetFileInformationByHandle(fh, &sb)) {
      CloseHandle(fh);
      return -1;
    }

    fb->len = sb.nFileSizeLow;

    if (!(fb->buff = mush_malloc(sb.nFileSizeLow, "fcache_data"))) {
      CloseHandle(fh);
      return -1;
    }

    if (!ReadFile(fh, fb->buff, sb.nFileSizeLow, &r, NULL) || fb->len != r) {
      CloseHandle(fh);
      mush_free(fb->buff, "fcache_data");
      fb->buff = NULL;
      return -1;
    }

    CloseHandle(fh);

    fb->len = sb.nFileSizeLow;
    return (int) fb->len;
  }
#else
  /* Posix read code here */
  {
    int fd, n;
    struct stat sb;

    release_fd();
    if ((fd = open(filename, O_RDONLY, 0)) < 0) {
      do_log(LT_ERR, 0, 0, T("Couldn't open cached text file '%s'"), filename);
      reserve_fd();
      return -1;
    }

    if (fstat(fd, &sb) < 0) {
      do_log(LT_ERR, 0, 0, T("Couldn't get the size of text file '%s'"),
             filename);
      close(fd);
      reserve_fd();
      return -1;
    }


    if (!(fb->buff = mush_malloc(sb.st_size, "fcache_data"))) {
      do_log(LT_ERR, 0, 0, T("Couldn't allocate %d bytes of memory for '%s'!"),
             (int) sb.st_size, filename);
      close(fd);
      reserve_fd();
      return -1;
    }

    if ((n = read(fd, fb->buff, sb.st_size)) != sb.st_size) {
      do_log(LT_ERR, 0, 0, T("Couldn't read all of '%s'"), filename);
      close(fd);
      mush_free(fb->buff, "fcache_data");
      fb->buff = NULL;
      reserve_fd();
      return -1;
    }

    close(fd);
    reserve_fd();
    fb->len = sb.st_size;

  }
#endif                          /* Posix read code */

  return fb->len;
}

/** Load all of the cached text files.
 * \param player the enactor.
 */
void
fcache_load(dbref player)
{
  int conn, motd, wiz, new, reg, quit, down, full;
  int guest;
  int i;

  for (i = 0; i < (SUPPORT_PUEBLO ? 2 : 1); i++) {
    conn = fcache_read(&fcache.connect_fcache[i], options.connect_file[i]);
    motd = fcache_read(&fcache.motd_fcache[i], options.motd_file[i]);
    wiz = fcache_read(&fcache.wizmotd_fcache[i], options.wizmotd_file[i]);
    new = fcache_read(&fcache.newuser_fcache[i], options.newuser_file[i]);
    reg = fcache_read(&fcache.register_fcache[i], options.register_file[i]);
    quit = fcache_read(&fcache.quit_fcache[i], options.quit_file[i]);
    down = fcache_read(&fcache.down_fcache[i], options.down_file[i]);
    full = fcache_read(&fcache.full_fcache[i], options.full_file[i]);
    guest = fcache_read(&fcache.guest_fcache[i], options.guest_file[i]);

    if (player != NOTHING) {
      notify_format(player,
                    T
                    ("%s sizes:  NewUser...%d  Connect...%d  Guest...%d  Motd...%d  Wizmotd...%d  Quit...%d  Register...%d  Down...%d  Full...%d"),
                    i ? "HTMLFile" : "File", new, conn, guest, motd, wiz, quit,
                    reg, down, full);
    }
  }

}

/** Initialize all of the cached text files (at startup).
 */
void
fcache_init(void)
{
  fcache_load(NOTHING);
}

static void
logout_sock(DESC *d)
{
  if (d->connected) {
    fcache_dump(d, fcache.quit_fcache, NULL);
    do_log(LT_CONN, 0, 0,
           T("[%d/%s/%s] Logout by %s(#%d) <Connection not dropped>"),
           d->descriptor, d->addr, d->ip, Name(d->player), d->player);
    announce_disconnect(d);
    do_mail_purge(d->player);
    login_number--;
    if (MAX_LOGINS) {
      if (!under_limit && (login_number < MAX_LOGINS)) {
        under_limit = 1;
        do_log(LT_CONN, 0, 0,
               T("Below maximum player limit of %d. Logins enabled."),
               MAX_LOGINS);
      }
    }
  } else {
    do_log(LT_CONN, 0, 0,
           T("[%d/%s/%s] Logout, never connected. <Connection not dropped>"),
           d->descriptor, d->addr, d->ip);
  }
  process_output(d);            /* flush our old output */
  /* pretend we have a new connection */
  d->connected = 0;
  d->output_prefix = 0;
  d->output_suffix = 0;
  d->output_size = 0;
  d->output.head = 0;
  d->player = 0;
  d->output.tail = &d->output.head;
  d->input.head = 0;
  d->input.tail = &d->input.head;
  d->raw_input = 0;
  d->raw_input_at = 0;
  d->quota = COMMAND_BURST_SIZE;
  d->last_time = mudtime;
  d->cmds = 0;
  d->hide = 0;
  d->doing[0] = '\0';
  d->mailp = NULL;
  welcome_user(d);
}

/** Disconnect a descriptor.
 * This sends appropriate disconnection text, flushes output, and
 * then closes the associated socket.
 * \param d pointer to descriptor to disconnect.
 */
static void
shutdownsock(DESC *d)
{
  if (d->connected) {
    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Logout by %s(#%d)"),
           d->descriptor, d->addr, d->ip, Name(d->player), d->player);
    if (d->connected != 2) {
      fcache_dump(d, fcache.quit_fcache, NULL);
      /* Player was not allowed to log in from the connect screen */
      announce_disconnect(d);
      do_mail_purge(d->player);
    }
    login_number--;
    if (MAX_LOGINS) {
      if (!under_limit && (login_number < MAX_LOGINS)) {
        under_limit = 1;
        do_log(LT_CONN, 0, 0,
               T("Below maximum player limit of %d. Logins enabled."),
               MAX_LOGINS);
      }
    }
  } else {
    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connection closed, never connected."),
           d->descriptor, d->addr, d->ip);
  }
  process_output(d);
  clearstrings(d);
  shutdown(d->descriptor, 2);
  closesocket(d->descriptor);
  if (d->prev)
    d->prev->next = d->next;
  else                          /* d was the first one! */
    descriptor_list = d->next;
  if (d->next)
    d->next->prev = d->prev;

#ifdef HAS_OPENSSL
  if (sslsock && d->ssl) {
    ssl_close_connection(d->ssl);
    d->ssl = NULL;
  }
#endif

  {
    freeqs(d);
    mush_free(d->ttype, "terminal description");
    mush_free((Malloc_t) d, "descriptor");
  }

  ndescriptors--;
}

/* ARGSUSED */
DESC *
initializesock(int s, char *addr, char *ip, int use_ssl
               __attribute__ ((__unused__)))
{
  DESC *d;
  d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
  if (!d)
    mush_panic("Out of memory.");
  d->descriptor = s;
  d->connected = 0;
  d->connected_at = mudtime;
  make_nonblocking(s);
  d->output_prefix = 0;
  d->output_suffix = 0;
  d->output_size = 0;
  d->output.head = 0;
  d->player = 0;
  d->output.tail = &d->output.head;
  d->input.head = 0;
  d->input.tail = &d->input.head;
  d->raw_input = 0;
  d->raw_input_at = 0;
  d->quota = COMMAND_BURST_SIZE;
  d->last_time = mudtime;
  d->cmds = 0;
  d->hide = 0;
  d->doing[0] = '\0';
  d->mailp = NULL;
  mush_strncpy(d->addr, addr, 100);
  d->addr[99] = '\0';
  mush_strncpy(d->ip, ip, 100);
  d->ip[99] = '\0';
  d->conn_flags = CONN_DEFAULT;
  d->input_chars = 0;
  d->output_chars = 0;
  d->width = 78;
  d->height = 24;
  d->ttype = mush_strdup("unknown", "terminal description");
  d->checksum[0] = '\0';
#ifdef HAS_OPENSSL
  d->ssl = NULL;
  d->ssl_state = 0;
#endif
  if (descriptor_list)
    descriptor_list->prev = d;
  d->next = descriptor_list;
  d->prev = NULL;
  descriptor_list = d;
#ifdef HAS_OPENSSL
  if (use_ssl && sslsock) {
    d->ssl = ssl_listen(d->descriptor, &d->ssl_state);
    if (d->ssl_state < 0) {
      /* Error we can't handle */
      ssl_close_connection(d->ssl);
      d->ssl = NULL;
      d->ssl_state = 0;
    }
  }
#endif
  test_telnet(d);
  welcome_user(d);
  return d;
}




/** Flush pending output for a descriptor.
 * This function actually sends the queued output over the descriptor's
 * socket.
 * \param d pointer to descriptor to send output to.
 * \retval 1 successfully flushed at least some output.
 * \retval 0 something failed, and the descriptor should probably be closed.
 */
int
process_output(DESC *d)
{
  struct text_block **qp, *cur;
  int cnt;
#ifdef HAS_OPENSSL
  int input_ready = 0;
#endif

#ifdef HAS_OPENSSL
  /* Insure that we're not in a state where we need an SSL_handshake() */
  if (d->ssl && (ssl_need_handshake(d->ssl_state))) {
    d->ssl_state = ssl_handshake(d->ssl);
    if (d->ssl_state < 0) {
      /* Fatal error */
      ssl_close_connection(d->ssl);
      d->ssl = NULL;
      d->ssl_state = 0;
      return 0;
    } else if (ssl_need_handshake(d->ssl_state)) {
      /* We're still not ready to send to this connection. Alas. */
      return 1;
    }
  }
  /* Insure that we're not in a state where we need an SSL_accept() */
  if (d->ssl && (ssl_need_accept(d->ssl_state))) {
    d->ssl_state = ssl_accept(d->ssl);
    if (d->ssl_state < 0) {
      /* Fatal error */
      ssl_close_connection(d->ssl);
      d->ssl = NULL;
      d->ssl_state = 0;
      return 0;
    } else if (ssl_need_accept(d->ssl_state)) {
      /* We're still not ready to send to this connection. Alas. */
      return 1;
    }
  }
  if (d->ssl) {
    /* process_output, alas, gets called from all kinds of places.
     * We need to know if the descriptor is waiting on input, though.
     * So let's find out
     */
    struct timeval pad;
    fd_set input_set;

    pad.tv_sec = 0;
    pad.tv_usec = 0;
    FD_ZERO(&input_set);
    FD_SET(d->descriptor, &input_set);
    input_ready = select(d->descriptor + 1, &input_set, NULL, NULL, &pad);
    if (input_ready < 0) {
      /* Well, shoot, we have no idea. Guess and proceed. */
      perror("select in process_output");
      input_ready = 0;
    }
  }
#endif

  for (qp = &d->output.head; ((cur = *qp) != NULL);) {
#ifdef HAS_OPENSSL
    if (d->ssl) {
      cnt = 0;
      d->ssl_state = ssl_write(d->ssl, d->ssl_state, input_ready, 1, cur->start,
                               cur->nchars, &cnt);
      if (ssl_want_write(d->ssl_state))
        return 1;               /* Need to retry */
    } else {
#endif
      cnt = send(d->descriptor, cur->start, cur->nchars, 0);
      if (cnt < 0) {
#ifdef WIN32
        if (cnt == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
#else
#ifdef EAGAIN
        if ((errno == EWOULDBLOCK) || (errno == EAGAIN))
#else
        if (errno == EWOULDBLOCK)
#endif
#endif
          return 1;
        return 0;
      }
#ifdef HAS_OPENSSL
    }
#endif
    d->output_size -= cnt;
    d->output_chars += cnt;
    if (cnt == cur->nchars) {
      if (!cur->nxt)
        d->output.tail = qp;
      *qp = cur->nxt;
#ifdef DEBUG
      do_rawlog(LT_ERR, "free_text_block(0x%x) at 2.", cur);
#endif                          /* DEBUG */
      free_text_block(cur);
      continue;                 /* do not adv ptr */
    }
    cur->nchars -= cnt;
    cur->start += cnt;
    break;
  }
  return 1;
}


static void
welcome_user(DESC *d)
{
  if (SUPPORT_PUEBLO && !(d->conn_flags & CONN_HTML))
    queue_newwrite(d, (const unsigned char *) PUEBLO_HELLO,
                   strlen(PUEBLO_HELLO));
  fcache_dump(d, fcache.connect_fcache, NULL);
}

static void
save_command(DESC *d, const unsigned char *command)
{
  add_to_queue(&d->input, command, u_strlen(command) + 1);
}

static void
test_telnet(DESC *d)
{
  /* Use rfc 1184 to test telnet support, as it tries to set linemode
     with client-side editing. Good for Broken Telnet Programs. */
  if (!TELNET_ABLE(d)) {
    /*  IAC DO LINEMODE */
    unsigned char query[3] = "\xFF\xFD\x22";
    queue_newwrite(d, query, 3);
    d->conn_flags |= CONN_TELNET_QUERY;
    process_output(d);
  }
}

static void
setup_telnet(DESC *d)
{
  /* Win2k telnet doesn't do local echo by default,
     apparently. Unfortunately, there doesn't seem to be a telnet
     option for local echo, just remote echo. */
  d->conn_flags |= CONN_TELNET;
  if (d->conn_flags & CONN_TELNET_QUERY) {
    /* IAC DO NAWS IAC DO TERMINAL-TYPE */
    unsigned char extra_options[6] = "\xFF\xFD\x1F" "\xFF\xFD\x18";
    d->conn_flags &= ~CONN_TELNET_QUERY;
    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Switching to Telnet mode."),
           d->descriptor, d->addr, d->ip);
    queue_newwrite(d, extra_options, 6);
    process_output(d);
  }
}

static int
handle_telnet(DESC *d, unsigned char **q, unsigned char *qend)
{
  /* *(*q - q) == IAC at this point. */
  switch (**q) {
  case SB:                     /* Sub-option */
    if (*q >= qend)
      return -1;
    (*q)++;
    if (**q == TN_LINEMODE) {
      if ((*q + 2) >= qend)
        return -1;
      *q += 2;
      while (*q < qend && **q != SE)
        (*q)++;
      if (*q >= qend)
        return -1;
    } else if (**q == TN_NAWS) {
      /* Learn what size window the client is using. */
      union {
        short s;
        unsigned char bytes[2];
      } raw;
      if (*q >= qend)
        return -1;
      (*q)++;
      /* Width */
      if (**q == IAC) {
        raw.bytes[0] = IAC;
        if (*q >= qend)
          return -1;
        (*q)++;
      } else
        raw.bytes[0] = **q;
      if (*q >= qend)
        return -1;
      (*q)++;
      if (**q == IAC) {
        raw.bytes[1] = IAC;
        if (*q >= qend)
          return -1;
        (*q)++;
      } else
        raw.bytes[1] = **q;
      if (*q >= qend)
        return -1;
      (*q)++;

      d->width = ntohs(raw.s);

      /* Height */
      if (**q == IAC) {
        raw.bytes[0] = IAC;
        if (*q >= qend)
          return -1;
        (*q)++;
      } else
        raw.bytes[0] = **q;
      if (*q >= qend)
        return -1;
      (*q)++;
      if (**q == IAC) {
        raw.bytes[1] = IAC;
        if (*q >= qend)
          return -1;
        (*q)++;
      } else
        raw.bytes[1] = **q;
      if (*q >= qend)
        return -1;
      (*q)++;
      d->height = ntohs(raw.s);

      /* IAC SE */
      if (*q + 1 >= qend)
        return -1;
      (*q)++;
    } else if (**q == TN_TTYPE) {
      /* Read the terminal type: TERMINAL-TYPE IS blah IAC SE */
      char tbuf[BUFFER_LEN], *bp = tbuf;
      if (*q >= qend)
        return -1;
      (*q)++;
      /* Skip IS */
      if (*q >= qend)
        return -1;
      (*q)++;

      /* Read up to IAC SE */
      while (1) {
        if (*q >= qend)
          return -1;
        if (**q == IAC) {
          if (*q + 1 >= qend)
            return -1;
          if (*(*q + 1) == IAC) {
            safe_chr((char) IAC, tbuf, &bp);
            (*q)++;
          } else
            break;
        } else
          safe_chr(**q, tbuf, &bp);
        (*q)++;
      }
      while (*q < qend && **q != SE)
        (*q)++;
      *bp = '\0';
      mush_free(d->ttype, "terminal description");
      d->ttype = mush_strdup(tbuf, "terminal description");
    } else {
      while (*q < qend && **q != SE)
        (*q)++;
    }
    break;
  case NOP:                    /* No-op */
    if (*q >= qend)
      return -1;
#ifdef DEBUG_TELNET
    fprintf(stderr, "Got IAC NOP\n");
#endif
    break;
  case AYT:                    /* Are you there? */
    if (*q >= qend)
      return -1;
    else {
      static unsigned char ayt_reply[] =
        "\r\n*** AYT received, I'm here ***\r\n";
      queue_newwrite(d, ayt_reply, u_strlen(ayt_reply));
      process_output(d);
    }
    break;
  case WILL:                   /* Client is willing to do something, or confirming */
    setup_telnet(d);
    if (*q >= qend)
      return -1;
    (*q)++;

    if (**q == TN_LINEMODE) {
      /* Set up our preferred linemode options. */
      /* IAC SB LINEMODE MODE (EDIT|SOFT_TAB) IAC SE */
      unsigned char reply[7] = "\xFF\xFA\x22\x01\x09\xFF\xF0";
      queue_newwrite(d, reply, 7);
#ifdef DEBUG_TELNET
      fprintf(stderr, "Setting linemode options.\n");
#endif
    } else if (**q == TN_TTYPE) {
      /* Ask for terminal type id: IAC SB TERMINAL-TYPE SEND IAC SEC */
      unsigned char reply[6] = "\xFF\xFA\x18\x01\xFF\xF0";
      queue_newwrite(d, reply, 6);
    } else if (**q == TN_SGA || **q == TN_NAWS) {
      /* This is good to be at. */
    } else {                    /* Refuse options we don't handle */
      unsigned char reply[3];
      reply[0] = IAC;
      reply[1] = DONT;
      reply[2] = **q;
      queue_newwrite(d, reply, sizeof reply);
      process_output(d);
    }
    break;
  case DO:                     /* Client is asking us to do something */
    setup_telnet(d);
    if (*q >= qend)
      return -1;
    (*q)++;
    if (**q == TN_LINEMODE) {
    } else if (**q == TN_SGA) {
      /* IAC WILL SGA IAC DO SGA */
      unsigned char reply[6] = "\xFF\xFB\x03\xFF\xFD\x03";
      queue_newwrite(d, reply, 6);
      process_output(d);
      /* Yeah, we still will send GA, which they should treat as a NOP,
       * but we'd better send newlines, too.
       */
      d->conn_flags |= CONN_PROMPT_NEWLINES;
#ifdef DEBUG_TELNET
      fprintf(stderr, "GOT IAC DO SGA, sending IAC WILL SGA IAG DO SGA\n");
#endif
    } else {
      /* Stuff we won't do */
      unsigned char reply[3];
      reply[0] = IAC;
      reply[1] = WONT;
      reply[2] = (char) **q;
      queue_newwrite(d, reply, sizeof reply);
      process_output(d);
    }
    break;
  case WONT:                   /* Client won't do something we want. */
  case DONT:                   /* Client doesn't want us to do something */
    setup_telnet(d);
#ifdef DEBUG_TELNET
    fprintf(stderr, "Got IAC %s 0x%x\n", **q == WONT ? "WONT" : "DONT",
            *(*q + 1));
#endif
    if (*q + 1 >= qend)
      return -1;
    (*q)++;
    break;
  default:                     /* Also catches IAC IAC for a literal 255 */
    return 0;
  }
  return 1;
}

static void
process_input_helper(DESC *d, char *tbuf1, int got)
{
  unsigned char *p, *pend, *q, *qend;

  if (!d->raw_input) {
    d->raw_input = mush_malloc(MAX_COMMAND_LEN, "descriptor_raw_input");
    if (!d->raw_input)
      mush_panic("Out of memory");
    d->raw_input_at = d->raw_input;
  }
  p = d->raw_input_at;
  d->input_chars += got;
  pend = d->raw_input + MAX_COMMAND_LEN - 1;
  for (q = (unsigned char *) tbuf1, qend = (unsigned char *) tbuf1 + got;
       q < qend; q++) {
    if (*q == '\r') {
      /* A broken client (read: WinXP telnet) might send only CR, and not CRLF
       * so it's nice of us to try to handle this.
       */
      *p = '\0';
      if (p > d->raw_input)
        save_command(d, d->raw_input);
      p = d->raw_input;
      if (((q + 1) < qend) && (*(q + 1) == '\n'))
        q++;                    /* For clients that work */
    } else if (*q == '\n') {
      *p = '\0';
      if (p > d->raw_input)
        save_command(d, d->raw_input);
      p = d->raw_input;
    } else if (*q == '\b') {
      if (p > d->raw_input)
        p--;
    } else if ((unsigned char) *q == IAC) {     /* Telnet option foo */
      if (q >= qend)
        break;
      q++;
      if (!TELNET_ABLE(d) || handle_telnet(d, &q, qend) == 0) {
        if (p < pend && isprint(*q))
          *p++ = *q;
      }
    } else if (p < pend && isprint(*q)) {
      *p++ = *q;
    }
  }
  if (p > d->raw_input) {
    d->raw_input_at = p;
  } else {
    mush_free((Malloc_t) d->raw_input, "descriptor_raw_input");
    d->raw_input = 0;
    d->raw_input_at = 0;
  }
}

/* ARGSUSED */
static int
process_input(DESC *d, int output_ready __attribute__ ((__unused__)))
{
  int got = 0;
  char tbuf1[BUFFER_LEN];

  errno = 0;

#ifdef HAS_OPENSSL
  if (d->ssl) {
    /* Insure that we're not in a state where we need an SSL_handshake() */
    if (ssl_need_handshake(d->ssl_state)) {
      d->ssl_state = ssl_handshake(d->ssl);
      if (d->ssl_state < 0) {
        /* Fatal error */
        ssl_close_connection(d->ssl);
        d->ssl = NULL;
        d->ssl_state = 0;
        return 0;
      } else if (ssl_need_handshake(d->ssl_state)) {
        /* We're still not ready to send to this connection. Alas. */
        return 1;
      }
    }
    /* Insure that we're not in a state where we need an SSL_accept() */
    if (ssl_need_accept(d->ssl_state)) {
      d->ssl_state = ssl_accept(d->ssl);
      if (d->ssl_state < 0) {
        /* Fatal error */
        ssl_close_connection(d->ssl);
        d->ssl = NULL;
        d->ssl_state = 0;
        return 0;
      } else if (ssl_need_accept(d->ssl_state)) {
        /* We're still not ready to send to this connection. Alas. */
        return 1;
      }
    }
    /* It's an SSL connection, proceed accordingly */
    d->ssl_state =
      ssl_read(d->ssl, d->ssl_state, 1, output_ready, tbuf1, sizeof tbuf1,
               &got);
    if (d->ssl_state < 0) {
      /* Fatal error */
      ssl_close_connection(d->ssl);
      d->ssl = NULL;
      d->ssl_state = 0;
      return 0;
    }
  } else {
#endif
    got = recv(d->descriptor, tbuf1, sizeof tbuf1, 0);
    if (got <= 0) {
      /* At this point, select() says there's data waiting to be read from
       * the socket, but we shouldn't assume that read() will actually get it
       * and blindly act like a got of -1 is a disconnect-worthy error.
       */
#ifdef EAGAIN
      if ((errno == EWOULDBLOCK) || (errno == EAGAIN) || (errno == EINTR))
#else
      if ((errno == EWOULDBLOCK) || (errno == EINTR))
#endif
        return 1;
      else
        return 0;
    }
#ifdef HAS_OPENSSL
  }
#endif

  process_input_helper(d, tbuf1, got);

  return 1;
}

static void
set_userstring(unsigned char **userstring, const char *command)
{
  if (*userstring) {
    mush_free((Malloc_t) * userstring, "userstring");
    *userstring = NULL;
  }
  while (*command && isspace((unsigned char) *command))
    command++;
  if (*command)
    *userstring = (unsigned char *) mush_strdup(command, "userstring");
}

static void
process_commands(void)
{
  int nprocessed;
  DESC *cdesc, *dnext;
  struct text_block *t;
  int retval = 1;

  do {
    nprocessed = 0;
    for (cdesc = descriptor_list; cdesc;
         cdesc = (nprocessed > 0 && retval > 0) ? cdesc->next : dnext) {
      dnext = cdesc->next;
      if (cdesc->quota > 0 && (t = cdesc->input.head)) {
        cdesc->quota--;
        nprocessed++;
        start_cpu_timer();
        retval = do_command(cdesc, (char *) t->start);
        reset_cpu_timer();
        if (retval == 0) {
          shutdownsock(cdesc);
        } else if (retval == -1) {
          logout_sock(cdesc);
        } else {
          cdesc->input.head = t->nxt;
          if (!cdesc->input.head)
            cdesc->input.tail = &cdesc->input.head;
          if (t) {
#ifdef DEBUG
            do_rawlog(LT_ERR, "free_text_block(0x%x) at 5.", t);
#endif                          /* DEBUG */
            free_text_block(t);
          }
        }
      }
    }
  } while (nprocessed > 0);
}

/** Send a descriptor's output prefix */
#define send_prefix(d) \
  if (d->output_prefix) { \
    queue_newwrite(d, d->output_prefix, u_strlen(d->output_prefix)); \
    queue_eol(d); \
  }

/** Send a descriptor's output suffix */
#define send_suffix(d) \
  if (d->output_suffix) { \
    queue_newwrite(d, d->output_suffix, u_strlen(d->output_suffix)); \
    queue_eol(d); \
  }

static int
do_command(DESC *d, char *command)
{
  int j;

  (d->cmds)++;

  if (!strcmp(command, IDLE_COMMAND))
    return 1;
  d->last_time = mudtime;
  if (!strcmp(command, QUIT_COMMAND)) {
    return 0;
  } else if (!strcmp(command, LOGOUT_COMMAND)) {
    return -1;
  } else if (!strcmp(command, INFO_COMMAND)) {
    send_prefix(d);
    dump_info(d);
    send_suffix(d);
  } else if (!strncmp(command, WHO_COMMAND, strlen(WHO_COMMAND))) {
    send_prefix(d);
    dump_users(d, command + strlen(WHO_COMMAND), 0);
    send_suffix(d);
  } else if (!strncmp(command, DOING_COMMAND, strlen(DOING_COMMAND))) {
    send_prefix(d);
    dump_users(d, command + strlen(DOING_COMMAND), 1);
    send_suffix(d);
  } else if (!strncmp(command, SESSION_COMMAND, strlen(SESSION_COMMAND))) {
    send_prefix(d);
    dump_users(d, command + strlen(SESSION_COMMAND), 2);
    send_suffix(d);
  } else if (!strncmp(command, PREFIX_COMMAND, strlen(PREFIX_COMMAND))) {
    set_userstring(&d->output_prefix, command + strlen(PREFIX_COMMAND));
  } else if (!strncmp(command, SUFFIX_COMMAND, strlen(SUFFIX_COMMAND))) {
    set_userstring(&d->output_suffix, command + strlen(SUFFIX_COMMAND));
  } else if (!strncmp(command, "SCREENWIDTH", 11)) {
    d->width = parse_integer(command + 11);
  } else if (!strncmp(command, "SCREENHEIGHT", 12)) {
    d->height = parse_integer(command + 12);
  } else if (!strncmp(command, "PROMPT_NEWLINES", 15)) {
    if (parse_integer(command + 15))
      d->conn_flags |= CONN_PROMPT_NEWLINES;
    else
      d->conn_flags &= ~CONN_PROMPT_NEWLINES;
  } else if (SUPPORT_PUEBLO
             && !strncmp(command, PUEBLO_COMMAND, strlen(PUEBLO_COMMAND))) {
    parse_puebloclient(d, command);
    if (!(d->conn_flags & CONN_HTML)) {
      queue_newwrite(d, (unsigned const char *) PUEBLO_SEND,
                     strlen(PUEBLO_SEND));
      process_output(d);
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Switching to Pueblo mode."),
             d->descriptor, d->addr, d->ip);
      d->conn_flags |= CONN_HTML;
      if (!d->connected)
        welcome_user(d);
    }
  } else {
    if (d->connected) {
      send_prefix(d);
      global_eval_context.cplr = d->player;
      mush_strncpy(global_eval_context.ccom, command, BUFFER_LEN);
      strcpy(global_eval_context.ucom, "");

      /* Clear %0-%9 and r(0) - r(9) */
      for (j = 0; j < 10; j++)
        global_eval_context.wenv[j] = (char *) NULL;
      for (j = 0; j < NUMQ; j++)
        global_eval_context.renv[j][0] = '\0';
      global_eval_context.process_command_port = d->descriptor;

      process_command(d->player, command, d->player, 1);
      send_suffix(d);
      strcpy(global_eval_context.ccom, "");
      strcpy(global_eval_context.ucom, "");
      global_eval_context.cplr = NOTHING;
    } else {
      if (!check_connect(d, command))
        return 0;
    }
  }
  return 1;
}

static void
parse_puebloclient(DESC *d, char *command)
{
  const char *p, *end;
  if ((p = string_match(command, "md5="))) {
    /* Skip md5=" */
    p += 5;
    if ((end = strchr(p, '"'))) {
      if ((end > p) && ((end - p) <= PUEBLO_CHECKSUM_LEN)) {
        /* Got it! */
        mush_strncpy(d->checksum, p, end - p);
      }
    }
  }
}

static int
dump_messages(DESC *d, dbref player, int isnew)
{
  int is_hidden;
  int num = 0;
  DESC *tmpd;

  d->connected = 1;
  d->connected_at = mudtime;
  d->player = player;
  d->doing[0] = '\0';

  login_number++;
  if (MAX_LOGINS) {
    /* check for exceeding max player limit */
    if (under_limit && (login_number > MAX_LOGINS)) {
      under_limit = 0;
      do_rawlog(LT_CONN,
                T("Limit of %d players reached. Logins disabled.\n"),
                MAX_LOGINS);
    }
  }
  /* give players a message on connection */
  if (!options.login_allow || !under_limit ||
      (Guest(player) && !options.guest_allow)) {
    if (!options.login_allow) {
      fcache_dump(d, fcache.down_fcache, NULL);
      if (cf_downmotd_msg && *cf_downmotd_msg)
        raw_notify(player, cf_downmotd_msg);
    } else if (MAX_LOGINS && !under_limit) {
      fcache_dump(d, fcache.full_fcache, NULL);
      if (cf_fullmotd_msg && *cf_fullmotd_msg)
        raw_notify(player, cf_fullmotd_msg);
    }
    if (!Can_Login(player)) {
      /* when the connection has been refused, we want to update the
       * LASTFAILED info on the player
       */
      check_lastfailed(player, d->addr);
      return 0;
    }
  }
  d->mailp = find_exact_starting_point(player);

  /* check to see if this is a reconnect and also set DARK status */
  is_hidden = Can_Hide(player) && Dark(player);
  DESC_ITER_CONN(tmpd) {
    if (tmpd->player == player) {
      num++;
      if (is_hidden)
        tmpd->hide = 1;
    }
  }
  /* give permanent text messages */
  if (isnew)
    fcache_dump(d, fcache.newuser_fcache, NULL);
  if (num == 1) {
    fcache_dump(d, fcache.motd_fcache, NULL);
    if (Hasprivs(player))
      fcache_dump(d, fcache.wizmotd_fcache, NULL);
  }
  if (Guest(player))
    fcache_dump(d, fcache.guest_fcache, NULL);

  if (ModTime(player))
    notify_format(player, T("%ld failed connections since last login."),
                  (long) ModTime(player));
  ModTime(player) = (time_t) 0;
  announce_connect(player, isnew, num); /* broadcast connect message */
  check_last(player, d->addr, d->ip);   /* set Last, Lastsite, give paycheck */
  /* Check all mail folders. If empty, report lack of mail. */
  queue_eol(d);
  if (command_check_byname(player, "@MAIL"))
    check_all_mail(player);
  set_player_folder(player, 0);
  do_look_around(player);
  if (Haven(player))
    notify(player, T("Your HAVEN flag is set. You cannot receive pages."));
  if (Vacation(player)) {
    notify(player,
           T
           ("Welcome back from vacation! Don't forget to unset your ON-VACATION flag"));
  }
  local_connect(player, isnew, num);
  return 1;
}

static int
check_connect(DESC *d, const char *msg)
{
  char command[MAX_COMMAND_LEN];
  char user[MAX_COMMAND_LEN];
  char password[MAX_COMMAND_LEN];
  char errbuf[BUFFER_LEN];
  dbref player;

  parse_connect(msg, command, user, password);

  if (string_prefix("connect", command)) {
    if ((player =
         connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
      queue_string_eol(d, errbuf);
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
             d->descriptor, d->addr, d->ip, user);
    } else {
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connected to %s(#%d) in %s(#%d)"),
             d->descriptor, d->addr, d->ip, Name(player), player,
             Name(Location(player)), Location(player));
      if ((dump_messages(d, player, 0)) == 0) {
        d->connected = 2;
        return 0;
      }
    }

  } else if (!strcasecmp(command, "cd")) {
    if ((player =
         connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
      queue_string_eol(d, errbuf);
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
             d->descriptor, d->addr, d->ip, user);
    } else {
      do_log(LT_CONN, 0, 0,
             T("[%d/%s/%s] Connected dark to %s(#%d) in %s(#%d)"),
             d->descriptor, d->addr, d->ip, Name(player), player,
             Name(Location(player)), Location(player));
      /* Set player dark */
      d->connected = 1;
      d->player = player;
      set_flag(player, player, "DARK", 0, 0, 0);
      if ((dump_messages(d, player, 0)) == 0) {
        d->connected = 2;
        return 0;
      }
    }

  } else if (!strcasecmp(command, "cv")) {
    if ((player =
         connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
      queue_string_eol(d, errbuf);
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
             d->descriptor, d->addr, d->ip, user);
    } else {
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connected to %s(#%d) in %s(#%d)"),
             d->descriptor, d->addr, d->ip, Name(player), player,
             Name(Location(player)), Location(player));
      /* Set player !dark */
      d->connected = 1;
      d->player = player;
      set_flag(player, player, "DARK", 1, 0, 0);
      if ((dump_messages(d, player, 0)) == 0) {
        d->connected = 2;
        return 0;
      }
    }

  } else if (!strcasecmp(command, "ch")) {
    if ((player =
         connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
      queue_string_eol(d, errbuf);
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
             d->descriptor, d->addr, d->ip, user);
    } else {
      do_log(LT_CONN, 0, 0,
             T("[%d/%s/%s] Connected hidden to %s(#%d) in %s(#%d)"),
             d->descriptor, d->addr, d->ip, Name(player), player,
             Name(Location(player)), Location(player));
      /* Set player hidden */
      d->connected = 1;
      d->player = player;
      if (Can_Hide(player))
        d->hide = 1;
      if ((dump_messages(d, player, 0)) == 0) {
        d->connected = 2;
        return 0;
      }
    }

  } else if (string_prefix("create", command)) {
    if (!Site_Can_Create(d->addr) || !Site_Can_Create(d->ip)) {
      fcache_dump(d, fcache.register_fcache, NULL);
      if (!Deny_Silent_Site(d->addr, AMBIGUOUS)
          && !Deny_Silent_Site(d->ip, AMBIGUOUS)) {
        do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Refused create for '%s'."),
               d->descriptor, d->addr, d->ip, user);
      }
      return 0;
    }
    if (!options.login_allow || !options.create_allow) {
      if (!options.login_allow)
        fcache_dump(d, fcache.down_fcache, NULL);
      else
        fcache_dump(d, fcache.register_fcache, NULL);
      do_rawlog(LT_CONN,
                "REFUSED CREATION for %s from %s on descriptor %d.\n",
                user, d->addr, d->descriptor);
      return 0;
    } else if (MAX_LOGINS && !under_limit) {
      fcache_dump(d, fcache.full_fcache, NULL);
      do_rawlog(LT_CONN,
                "REFUSED CREATION for %s from %s on descriptor %d.\n",
                user, d->addr, d->descriptor);
      return 0;
    }
    player = create_player(user, password, d->addr, d->ip);
    if (player == NOTHING) {
      queue_string_eol(d, T(create_fail));
      do_log(LT_CONN, 0, 0,
             T("[%d/%s/%s] Failed create for '%s' (bad name)."),
             d->descriptor, d->addr, d->ip, user);
    } else if (player == AMBIGUOUS) {
      queue_string_eol(d, T(password_fail));
      do_log(LT_CONN, 0, 0,
             T("[%d/%s/%s] Failed create for '%s' (bad password)."),
             d->descriptor, d->addr, d->ip, user);
    } else {
      do_log(LT_CONN, 0, 0, "[%d/%s/%s] Created %s(#%d)",
             d->descriptor, d->addr, d->ip, Name(player), player);
      if ((dump_messages(d, player, 1)) == 0) {
        d->connected = 2;
        return 0;
      }
    }                           /* successful player creation */

  } else if (string_prefix("register", command)) {
    if (!Site_Can_Register(d->addr) || !Site_Can_Register(d->ip)) {
      fcache_dump(d, fcache.register_fcache, NULL);
      if (!Deny_Silent_Site(d->addr, AMBIGUOUS)
          && !Deny_Silent_Site(d->ip, AMBIGUOUS)) {
        do_log(LT_CONN, 0, 0,
               T("[%d/%s/%s] Refused registration (bad site) for '%s'."),
               d->descriptor, d->addr, d->ip, user);
      }
      return 0;
    }
    if (!options.create_allow) {
      fcache_dump(d, fcache.register_fcache, NULL);
      do_rawlog(LT_CONN,
                "Refused registration (creation disabled) for %s from %s on descriptor %d.\n",
                user, d->addr, d->descriptor);
      return 0;
    }
    if ((player = email_register_player(user, password, d->addr, d->ip)) ==
        NOTHING) {
      queue_string_eol(d, T(register_fail));
      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed registration for '%s'."),
             d->descriptor, d->addr, d->ip, user);
    } else {
      queue_string_eol(d, T(register_success));
      do_log(LT_CONN, 0, 0, "[%d/%s/%s] Registered %s(#%d) to %s",
             d->descriptor, d->addr, d->ip, Name(player), player, password);
    }
    /* Whether it succeeds or fails, leave them connected */

  } else {
    /* invalid command, just repeat login screen */
    welcome_user(d);
  }
  return 1;
}

static void
parse_connect(const char *msg1, char *command, char *user, char *pass)
{
  unsigned char *p;
  unsigned const char *msg = (unsigned const char *) msg1;

  while (*msg && isspace(*msg))
    msg++;
  p = (unsigned char *) command;
  while (*msg && isprint(*msg) && !isspace(*msg))
    *p++ = *msg++;
  *p = '\0';
  while (*msg && isspace(*msg))
    msg++;
  p = (unsigned char *) user;

  if (*msg == '\"') {
    for (; *msg && ((*msg == '\"') || isspace(*msg)); msg++) ;
    while (*msg && (*msg != '\"')) {
      while (*msg && !isspace(*msg) && (*msg != '\"'))
        *p++ = *msg++;
      if (*msg == '\"') {
        msg++;
        while (*msg && isspace(*msg))
          msg++;
        break;
      }
      while (*msg && isspace(*msg))
        msg++;
      if (*msg && (*msg != '\"'))
        *p++ = ' ';
    }
  } else
    while (*msg && isprint(*msg) && !isspace(*msg))
      *p++ = *msg++;

  *p = '\0';
  while (*msg && isspace(*msg))
    msg++;
  p = (unsigned char *) pass;
  while (*msg && isprint(*msg) && !isspace(*msg))
    *p++ = *msg++;
  *p = '\0';
}

static void
close_sockets(void)
{
  DESC *d, *dnext;

  for (d = descriptor_list; d; d = dnext) {
    dnext = d->next;
    send(d->descriptor, T(shutdown_message), strlen(T(shutdown_message)), 0);
    send(d->descriptor, "\r\n", 2, 0);
#ifdef HAS_OPENSSL
    if (d->ssl) {
      ssl_close_connection(d->ssl);
      d->ssl = NULL;
      d->ssl_state = 0;
    }
#endif
    if (shutdown(d->descriptor, 2) < 0)
      perror("shutdown");
    closesocket(d->descriptor);
  }
}

/** Give everyone the boot.
 */
void
emergency_shutdown(void)
{
  close_sockets();
#ifdef INFO_SLAVE
  kill_info_slave();
#endif
}

/** Disconnect a descriptor.
 * \param d pointer to descriptor to disconnect.
 */
void
boot_desc(DESC *d)
{
  shutdownsock(d);
}

/** Given a player dbref, return the player's first connected descriptor.
 * \param player dbref of player.
 * \return pointer to player's first connected descriptor, or NULL.
 */
DESC *
player_desc(dbref player)
{
  DESC *d;
  for (d = descriptor_list; d; d = d->next) {
    if (d->connected && (d->player == player)) {
      return d;
    }
  }
  return (DESC *) NULL;
}

/** Page a specified socket.
 * \param player the enactor.
 * \param pc string containing port number to send message to.
 * \param message message to send.
 */
void
do_page_port(dbref player, const char *pc, const char *message)
{
  int p, key;
  DESC *d;
  const char *gap;
  char tbuf[BUFFER_LEN], *tbp = tbuf;
  dbref target = NOTHING;

  if (!Hasprivs(player)) {
    notify(player, T("Permission denied."));
    return;
  }
  p = atoi(pc);
  if (p <= 0) {
    notify(player, T("That's not a port number."));
    return;
  }

  if (!message || !*message) {
    notify(player, T("What do you want to page with?"));
    return;
  }

  gap = " ";
  switch (*message) {
  case SEMI_POSE_TOKEN:
    gap = "";
  case POSE_TOKEN:
    key = 1;
    break;
  default:
    key = 3;
    break;
  }

  d = port_desc(p);
  if (!d) {
    notify(player, T("That port's not active."));
    return;
  }
  if (d->connected)
    target = d->player;
  switch (key) {
  case 1:
    safe_format(tbuf, &tbp, T("From afar, %s%s%s"), Name(player), gap,
                message + 1);
    notify_format(player, T("Long distance to %s: %s%s%s"),
                  target != NOTHING ? Name(target) :
                  T("a connecting player"), Name(player), gap, message + 1);
    break;
  case 3:
    safe_format(tbuf, &tbp, T("%s pages: %s"), Name(player), message);
    notify_format(player, T("You paged %s with '%s'."),
                  target != NOTHING ? Name(target) :
                  T("a connecting player"), message);
    break;
  }
  *tbp = '\0';
  if (target != NOTHING)
    page_return(player, target, "Idle", "IDLE", NULL);
  if (Typeof(player) != TYPE_PLAYER && Nospoof(target))
    queue_string_eol(d, tprintf("[#%d] %s", player, tbuf));
  else
    queue_string_eol(d, tbuf);
}


/** Return an inactive descriptor, as long as there's more than
 * one descriptor connected. Used for boot/me.
 * \param player player to find an inactive descriptor for.
 * \return pointer to player's inactive descriptor, or NULL.
 */
DESC *
inactive_desc(dbref player)
{
  DESC *d, *in = NULL;
  time_t now;
  int numd = 0;
  now = mudtime;
  DESC_ITER_CONN(d) {
    if (d->player == player) {
      numd++;
      if (now - d->last_time > 60)
        in = d;
    }
  }
  if (numd > 1)
    return in;
  else
    return (DESC *) NULL;
}

/** Given a port (a socket number), return the descriptor.
 * \param port port (socket file descriptor number).
 * \return pointer to descriptor associated with the port.
 */
DESC *
port_desc(int port)
{
  DESC *d;
  for (d = descriptor_list; (d); d = d->next) {
    if (d->descriptor == port) {
      return d;
    }
  }
  return (DESC *) NULL;
}

/** Given a port, find the matching player dbref.
 * \param port (socket file descriptor number).
 * \return dbref of connected player using that port, or NOTHING.
 */
dbref
find_player_by_desc(int port)
{
  DESC *d;
  for (d = descriptor_list; (d); d = d->next) {
    if (d->connected && (d->descriptor == port)) {
      return d->player;
    }
  }

  /* didn't find anything */
  return NOTHING;
}


#ifndef WIN32
/** Handler for SIGINT. Note that we've received it, and reinstall.
 * \param sig signal caught.
 */
void
signal_shutdown(int sig __attribute__ ((__unused__)))
{
  signal_shutdown_flag = 1;
  reload_sig_handler(SIGINT, signal_shutdown);
}

/** Handler for SIGUSR2. Note that we've received it, and reinstall
 * \param sig signal caught.
 */
void
signal_dump(int sig __attribute__ ((__unused__)))
{
  signal_dump_flag = 1;
  reload_sig_handler(SIGUSR2, signal_dump);
}
#endif

/** A general handler to puke and die.
 * \param sig signal caught.
 */
void
bailout(int sig)
{
  mush_panicf(T("BAILOUT: caught signal %d"), sig);
}

#ifndef WIN32
/** Reap child processes, notably info_slaves and forking dumps,
 * when we receive a SIGCHLD signal. Don't fear this function. :)
 * \param sig signal caught.
 */
void
reaper(int sig __attribute__ ((__unused__)))
{
  WAIT_TYPE my_stat;
  pid_t pid;

  while ((pid = mush_wait(-1, &my_stat, WNOHANG)) > 0) {
#ifdef INFO_SLAVE
    if (info_slave_pid > -1 && pid == info_slave_pid) {
      slave_error = info_slave_pid;
      info_slave_state = INFO_SLAVE_DOWN;
      info_slave_pid = -1;
    } else
#endif
    if (forked_dump_pid > -1 && pid == forked_dump_pid) {
      dump_error = forked_dump_pid;
      dump_status = my_stat;
      forked_dump_pid = -1;
    }
  }
  reload_sig_handler(SIGCHLD, reaper);
}
#endif                          /* !(Mac or WIN32) */


static void
dump_info(DESC *call_by)
{
  int count = 0;
  DESC *d;
  queue_string_eol(call_by, tprintf("### Begin INFO %s", INFO_VERSION));

  /* Count connected players */
  for (d = descriptor_list; d; d = d->next) {
    if (d->connected) {
      if (!GoodObject(d->player))
        continue;
      if (COUNT_ALL || !Hidden(d))
        count++;
    }
  }
  queue_string_eol(call_by, tprintf("Name: %s", options.mud_name));
  queue_string_eol(call_by,
                   tprintf("Uptime: %s",
                           show_time(globals.first_start_time, 0)));
  queue_string_eol(call_by, tprintf("Connected: %d", count));
  queue_string_eol(call_by, tprintf("Size: %d", db_top));
  queue_string_eol(call_by,
                   tprintf("Version: PennMUSH %sp%s", VERSION, PATCHLEVEL));
#ifdef PATCHES
  queue_string_eol(call_by, tprintf("Patches: %s", PATCHES));
#endif
  queue_string_eol(call_by, "### End INFO");
}

/** Determine if a new guest can connect at this point. If so, return
 * the dbref of the player they should connect to.
 * The algorithm looks like this:
 * \verbatim
 * 1. Count connected guests. If we have a fixed maximum number and we've
 *    reached it already, fail now.
 * 2. Otherwise, we either have no limit or we're limited to available
 *    unconnected guest players. So if the requested player isn't
 *    connected, succeed now.
 * 3. Search the db for unconnected guest players. If we find any,
 *    succeed immediately.
 * 4. If none were found, succeed only if we have no limit.
 * \endverbatim
 * \param player dbref of guest that connection was attempted to.
 */
dbref
guest_to_connect(dbref player)
{
  DESC *d;
  int desc_count = 0;
  dbref i;

  DESC_ITER_CONN(d) {
    if (!GoodObject(d->player))
      continue;
    if (Guest(d->player))
      desc_count++;
  }
  if ((MAX_GUESTS > 0) && (desc_count >= MAX_GUESTS))
    return NOTHING;             /* Limit already reached */

  if (!Connected(player))
    return player;              /* Connecting to a free guest */

  /* The requested guest isn't free. Find an available guest in the db */
  for (i = 0; i < db_top; i++) {
    if (IsPlayer(i) && !Hasprivs(i) && Guest(i) && !Connected(i))
      return i;
  }

  /* Oops, all guests are in use. Either fail now or succeed now with
   * a log message
   */
  if (MAX_GUESTS < 0)
    return NOTHING;

  do_log(LT_CONN, 0, 0, T("Multiple connection to Guest #%d"), player);
  return player;
}


static void
dump_users(DESC *call_by, char *match, int doing)
    /* doing: 0 if normal WHO, 1 if DOING, 2 if SESSION */
{
  DESC *d;
  int count = 0;
  time_t now;
  char tbuf1[BUFFER_LEN];
  char tbuf2[BUFFER_LEN];

  if (!GoodObject(call_by->player)) {
    do_log(LT_ERR, 0, 0, T("Bogus caller #%d of dump_users"), call_by->player);
    return;
  }
  while (*match && *match == ' ')
    match++;
  now = mudtime;

  /* If a wizard/royal types "DOING" it gives him the normal player WHO,
   * BUT flags are not shown. Wizard/royal WHO does not show @doings.
   */

  if (SUPPORT_PUEBLO && (call_by->conn_flags & CONN_HTML)) {
    queue_newwrite(call_by, (const unsigned char *) "<img xch_mode=html>", 19);
    queue_newwrite(call_by, (const unsigned char *) "<PRE>", 5);
  }

  if ((doing == 1) || !call_by->player || !Priv_Who(call_by->player)) {
    if (poll_msg[0] == '\0')
      strcpy(poll_msg, "Doing");
    if (ShowAnsi(call_by->player))
      snprintf(tbuf2, BUFFER_LEN, "%-16s %10s %6s  %s%s\n",
               T("Player Name"), T("On For"), T("Idle"), poll_msg, ANSI_END);
    else
      snprintf(tbuf2, BUFFER_LEN, "%-16s %10s %6s  %s\n",
               T("Player Name"), T("On For"), T("Idle"), poll_msg);
    queue_string(call_by, tbuf2);
  } else if (doing == 2) {
    snprintf(tbuf2, BUFFER_LEN,
             "%-16s %6s %9s %5s %5s Des  Sent    Recv  Pend\n",
             T("Player Name"), T("Loc #"), T("On For"), T("Idle"), T("Cmds"));
    queue_string(call_by, tbuf2);
  } else {
    snprintf(tbuf2, BUFFER_LEN, "%-16s %6s %9s %5s %5s Des  Host\n",
             T("Player Name"), T("Loc #"), T("On For"), T("Idle"), T("Cmds"));
    queue_string(call_by, tbuf2);
  }

  for (d = descriptor_list; d; d = d->next) {
    if (d->connected) {
      if (!GoodObject(d->player))
        continue;
      if (COUNT_ALL || (!Hidden(d)
                        || (call_by->player && Priv_Who(call_by->player))))
        count++;
      if (match && !(string_prefix(Name(d->player), match)))
        continue;

      if (call_by->connected && doing == 0 && call_by->player
          && Priv_Who(call_by->player)) {
        sprintf(tbuf1, "%-16s %6s %9s %5s  %4d %3d%c %s", Name(d->player),
                unparse_dbref(Location(d->player)),
                time_format_1(now - d->connected_at),
                time_format_2(now - d->last_time), d->cmds, d->descriptor,
#ifdef HAS_OPENSSL
                d->ssl ? 'S' : ' ',
#else
                ' ',
#endif
                d->addr);
        tbuf1[78] = '\0';
        if (Dark(d->player)) {
          tbuf1[71] = '\0';
          strcat(tbuf1, " (Dark)");
        } else if (Hidden(d)) {
          tbuf1[71] = '\0';
          strcat(tbuf1, " (Hide)");
        }
      } else if (call_by->connected && doing == 2 && call_by->player
                 && Priv_Who(call_by->player)) {
        sprintf(tbuf1, "%-16s %6s %9s %5s %5d %3d%c %5lu %7lu %5d",
                Name(d->player), unparse_dbref(Location(d->player)),
                time_format_1(now - d->connected_at),
                time_format_2(now - d->last_time), d->cmds, d->descriptor,
#ifdef HAS_OPENSSL
                d->ssl ? 'S' : ' ',
#else
                ' ',
#endif
                d->input_chars, d->output_chars, d->output_size);
      } else {
        if (!Hidden(d)
            || (call_by->player && Priv_Who(call_by->player) && (doing))) {
          sprintf(tbuf1, "%-16s %10s   %4s%c %s", Name(d->player),
                  time_format_1(now - d->connected_at),
                  time_format_2(now - d->last_time),
                  (Dark(d->player) ? 'D' : (Hidden(d) ? 'H' : ' '))
                  , d->doing);
        }
      }

      if (!Hidden(d) || (call_by->player && Priv_Who(call_by->player))) {
        queue_string(call_by, tbuf1);
        queue_newwrite(call_by, (const unsigned char *) "\r\n", 2);
      }
    } else if (call_by->player && Priv_Who(call_by->player) && doing != 1
               && (!match || !*match)) {
      if (doing == 0) {
        /* Wizard WHO for non-logged in connections */
        snprintf(tbuf1, BUFFER_LEN, "%-16s %6s %9s %5s  %4d %3d%c %s",
                 T("Connecting..."), "#-1",
                 time_format_1(now - d->connected_at),
                 time_format_2(now - d->last_time), d->cmds, d->descriptor,
#ifdef HAS_OPENSSL
                 d->ssl ? 'S' : ' ',
#else
                 ' ',
#endif
                 d->addr);
        tbuf1[78] = '\0';
      } else {
        /* SESSION for non-logged in connections */
        snprintf(tbuf1, BUFFER_LEN, "%-16s %5s %9s %5s %5d %3d%c %5lu %7lu %5d",
                 T("Connecting..."), "#-1",
                 time_format_1(now - d->connected_at),
                 time_format_2(now - d->last_time), d->cmds, d->descriptor,
#ifdef HAS_OPENSSL
                 d->ssl ? 'S' : ' ',
#else
                 ' ',
#endif
                 d->input_chars, d->output_chars, d->output_size);
      }
      queue_string(call_by, tbuf1);
      queue_newwrite(call_by, (const unsigned char *) "\r\n", 2);
    }
  }
  switch (count) {
  case 0:
    mush_strncpy(tbuf1, T("There are no players connected."), BUFFER_LEN);
    break;
  case 1:
    mush_strncpy(tbuf1, T("There is 1 player connected."), BUFFER_LEN);
    break;
  default:
    snprintf(tbuf1, BUFFER_LEN, T("There are %d players connected."), count);
    break;
  }
  queue_string(call_by, tbuf1);
  if (SUPPORT_PUEBLO && (call_by->conn_flags & CONN_HTML)) {
    queue_newwrite(call_by, (const unsigned char *) "\n</PRE>\n", 8);
    queue_newwrite(call_by, (const unsigned char *) "<img xch_mode=purehtml>",
                   23);
  } else
    queue_newwrite(call_by, (const unsigned char *) "\r\n", 2);
}

static const char *
time_format_1(time_t dt)
{
  register struct tm *delta;
  static char buf[64];
  if (dt < 0)
    dt = 0;
  delta = gmtime(&dt);
  if (delta->tm_yday > 0) {
    sprintf(buf, "%dd %02d:%02d",
            delta->tm_yday, delta->tm_hour, delta->tm_min);
  } else {
    sprintf(buf, "%02d:%02d", delta->tm_hour, delta->tm_min);
  }
  return buf;
}

static const char *
time_format_2(time_t dt)
{
  register struct tm *delta;
  static char buf[64];
  if (dt < 0)
    dt = 0;

  delta = gmtime(&dt);
  if (delta->tm_yday > 0) {
    sprintf(buf, "%dd", delta->tm_yday);
  } else if (delta->tm_hour > 0) {
    sprintf(buf, "%dh", delta->tm_hour);
  } else if (delta->tm_min > 0) {
    sprintf(buf, "%dm", delta->tm_min);
  } else {
    sprintf(buf, "%ds", delta->tm_sec);
  }
  return buf;
}

/* connection messages
 * isnew: newly creaetd or not?
 * num: how many times connected?
 */
static void
announce_connect(dbref player, int isnew, int num)
{
  dbref loc;
  char tbuf1[BUFFER_LEN];
  char *myenv[2];
  dbref zone;
  dbref obj;
  int j;

  set_flag_internal(player, "CONNECTED");

  if (isnew) {
    /* A brand new player created. */
    snprintf(tbuf1, BUFFER_LEN, T("%s created."), Name(player));
    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);
    if (Suspect(player))
      flag_broadcast("WIZARD", 0, T("GAME: Suspect %s created."), Name(player));
  }

  /* Redundant, but better for translators */
  if (Dark(player)) {
    snprintf(tbuf1, BUFFER_LEN, (num > 1) ? T("%s has DARK-reconnected.") :
             T("%s has DARK-connected."), Name(player));
  } else if (hidden(player)) {
    snprintf(tbuf1, BUFFER_LEN, (num > 1) ? T("%s has HIDDEN-reconnected.") :
             T("%s has HIDDEN-connected."), Name(player));
  } else {
    snprintf(tbuf1, BUFFER_LEN, (num > 1) ? T("%s has reconnected.") :
             T("%s has connected."), Name(player));
  }

  /* send out messages */
  if (Suspect(player))
    flag_broadcast("WIZARD", 0, T("GAME: Suspect %s"), tbuf1);

  if (Dark(player)) {
    flag_broadcast("ROYALTY WIZARD", "MONITOR", "%s %s", T("GAME:"), tbuf1);
  } else
    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);

  if (ANNOUNCE_CONNECTS)
    chat_player_announce(player, tbuf1, (num == 1));

  loc = Location(player);
  if (!GoodObject(loc)) {
    notify(player, T("You are nowhere!"));
    return;
  }
  orator = player;

  if (cf_motd_msg && *cf_motd_msg) {
    raw_notify(player, cf_motd_msg);
  }
  raw_notify(player, " ");
  if (Hasprivs(player) && cf_wizmotd_msg && *cf_wizmotd_msg) {
    if (cf_motd_msg && *cf_motd_msg)
      raw_notify(player, asterisk_line);
    raw_notify(player, cf_wizmotd_msg);
  }

  if (ANNOUNCE_CONNECTS)
    notify_except(Contents(player), player, tbuf1, 0);

  /* added to allow player's inventory to hear a player connect */
  if (ANNOUNCE_CONNECTS)
    if (!Dark(player))
      notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);

  /* clear the environment for possible actions */
  for (j = 0; j < 10; j++)
    global_eval_context.wnxt[j] = NULL;
  for (j = 0; j < NUMQ; j++)
    global_eval_context.rnxt[j] = NULL;
  strcpy(global_eval_context.ccom, "");
  /* And then load it up, as follows:
   * %0 (unused, reserved for "reason for disconnect")
   * %1 (number of connections after connect)
   */
  myenv[0] = NULL;
  myenv[1] = mush_strdup(unparse_integer(num), "myenv");
  for (j = 0; j < 2; j++)
    global_eval_context.wnxt[j] = myenv[j];

  /* do the person's personal connect action */
  (void) queue_attribute(player, "ACONNECT", player);
  if (ROOM_CONNECTS) {
    /* Do the room the player connected into */
    if (IsRoom(loc) || IsThing(loc)) {
      (void) queue_attribute(loc, "ACONNECT", player);
    }
  }
  /* do the zone of the player's location's possible aconnect */
  if ((zone = Zone(loc)) != NOTHING) {
    switch (Typeof(zone)) {
    case TYPE_THING:
      (void) queue_attribute(zone, "ACONNECT", player);
      break;
    case TYPE_ROOM:
      /* check every object in the room for a connect action */
      DOLIST(obj, Contents(zone)) {
        (void) queue_attribute(obj, "ACONNECT", player);
      }
      break;
    default:
      do_log(LT_ERR, 0, 0,
             T("Invalid zone #%d for %s(#%d) has bad type %d"), zone,
             Name(player), player, Typeof(zone));
    }
  }
  /* now try the master room */
  DOLIST(obj, Contents(MASTER_ROOM)) {
    (void) queue_attribute(obj, "ACONNECT", player);
  }
  for (j = 0; j < 2; j++)
    if (myenv[j])
      mush_free(myenv[j], "myenv");
  for (j = 0; j < 10; j++)
    global_eval_context.wnxt[j] = NULL;
  strcpy(global_eval_context.ccom, "");
}

static void
announce_disconnect(DESC *saved)
{
  dbref loc;
  int num;
  DESC *d;
  char tbuf1[BUFFER_LEN];
  dbref zone, obj;
  int j;
  char *myenv[5];
  dbref player;
  ATTR *a;

  player = saved->player;
  loc = Location(player);
  if (!GoodObject(loc))
    return;

  orator = player;

  for (num = 0, d = descriptor_list; d; d = d->next)
    if (d->connected && (d->player == player))
      num++;


  /* clear the environment for possible actions */
  for (j = 0; j < 10; j++)
    global_eval_context.wnxt[j] = NULL;
  for (j = 0; j < NUMQ; j++)
    global_eval_context.rnxt[j] = NULL;
  strcpy(global_eval_context.ccom, "");
  /* And then load it up, as follows:
   * %0 (unused, reserved for "reason for disconnect")
   * %1 (number of connections remaining after disconnect)
   * %2 (bytes received)
   * %3 (bytes sent)
   * %4 (commands queued)
   */
  myenv[0] = NULL;
  myenv[1] = mush_strdup(unparse_integer(num - 1), "myenv");
  myenv[2] = mush_strdup(unparse_integer(saved->input_chars), "myenv");
  myenv[3] = mush_strdup(unparse_integer(saved->output_chars), "myenv");
  myenv[4] = mush_strdup(unparse_integer(saved->cmds), "myenv");
  for (j = 0; j < 5; j++)
    global_eval_context.wnxt[j] = myenv[j];

  (void) queue_attribute(player, "ADISCONNECT", player);
  if (ROOM_CONNECTS)
    if (IsRoom(loc) || IsThing(loc)) {
      a = queue_attribute_getatr(loc, "ADISCONNECT", 0);
      if (a) {
        if (!Priv_Who(loc) && !Can_Examine(loc, player))
          global_eval_context.wnxt[1] = NULL;
        (void) queue_attribute_useatr(loc, a, player);
        global_eval_context.wnxt[1] = myenv[1];
      }
    }
  /* do the zone of the player's location's possible adisconnect */
  if ((zone = Zone(loc)) != NOTHING) {
    switch (Typeof(zone)) {
    case TYPE_THING:
      a = queue_attribute_getatr(zone, "ADISCONNECT", 0);
      if (a) {
        if (!Priv_Who(zone) && !Can_Examine(zone, player))
          global_eval_context.wnxt[1] = NULL;
        (void) queue_attribute_useatr(zone, a, player);
        global_eval_context.wnxt[1] = myenv[1];
      }
      break;
    case TYPE_ROOM:
      /* check every object in the room for a connect action */
      DOLIST(obj, Contents(zone)) {
        a = queue_attribute_getatr(obj, "ADISCONNECT", 0);
        if (a) {
          if (!Priv_Who(obj) && !Can_Examine(obj, player))
            global_eval_context.wnxt[1] = NULL;
          (void) queue_attribute_useatr(obj, a, player);
          global_eval_context.wnxt[1] = myenv[1];
        }
      }
      break;
    default:
      do_log(LT_ERR, 0, 0,
             T("Invalid zone #%d for %s(#%d) has bad type %d"), zone,
             Name(player), player, Typeof(zone));
    }
  }
  /* now try the master room */
  DOLIST(obj, Contents(MASTER_ROOM)) {
    a = queue_attribute_getatr(obj, "ADISCONNECT", 0);
    if (a) {
      if (!Priv_Who(obj) && !Can_Examine(obj, player))
        global_eval_context.wnxt[1] = NULL;
      (void) queue_attribute_useatr(obj, a, player);
      global_eval_context.wnxt[1] = myenv[1];
    }
  }

  for (j = 0; j < 5; j++)
    if (myenv[j])
      mush_free(myenv[j], "myenv");
  for (j = 0; j < 10; j++)
    global_eval_context.wnxt[j] = NULL;
  strcpy(global_eval_context.ccom, "");

  /* Redundant, but better for translators */
  if (Dark(player)) {
    sprintf(tbuf1, (num < 2) ? T("%s has DARK-disconnected.") :
            T("%s has partially DARK-disconnected."), Name(player));
  } else if (hidden(player)) {
    sprintf(tbuf1, (num < 2) ? T("%s has HIDDEN-disconnected.") :
            T("%s has partially HIDDEN-disconnected."), Name(player));
  } else {
    sprintf(tbuf1, (num < 2) ? T("%s has disconnected.") :
            T("%s has partially disconnected."), Name(player));
  }

  if (ANNOUNCE_CONNECTS) {
    if (!Dark(player))
      notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);
    /* notify contents */
    notify_except(Contents(player), player, tbuf1, 0);
    /* notify channels */
    chat_player_announce(player, tbuf1, 0);
  }

  /* Monitor broadcasts */
  if (Suspect(player))
    flag_broadcast("WIZARD", 0, T("GAME: Suspect %s"), tbuf1);
  if (Dark(player)) {
    flag_broadcast("ROYALTY WIZARD", "MONITOR", "%s %s", T("GAME:"), tbuf1);
  } else
    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);

  if (num < 2) {
    clear_flag_internal(player, "CONNECTED");
    (void) atr_add(player, "LASTLOGOUT", show_time(mudtime, 0), GOD, 0);
  }
  local_disconnect(player, num);
}

/** Set an motd message.
 * \verbatim
 * This implements @motd.
 * \endverbatim
 * \param player the enactor.
 * \param key type of MOTD to set.
 * \param message text to set the motd to.
 */
void
do_motd(dbref player, enum motd_type key, const char *message)
{

  if (!Wizard(player) && key != MOTD_LIST) {
    notify(player,
           T
           ("You may get 15 minutes of fame and glory in life, but not right now."));
    return;
  }
  switch (key) {
  case MOTD_MOTD:
    strcpy(cf_motd_msg, message);
    notify(player, T("Motd set."));
    break;
  case MOTD_WIZ:
    strcpy(cf_wizmotd_msg, message);
    notify(player, T("Wizard motd set."));
    break;
  case MOTD_DOWN:
    strcpy(cf_downmotd_msg, message);
    notify(player, T("Down motd set."));
    break;
  case MOTD_FULL:
    strcpy(cf_fullmotd_msg, message);
    notify(player, T("Full motd set."));
    break;
  case MOTD_LIST:
    notify_format(player, "MOTD: %s", cf_motd_msg);
    if (Hasprivs(player)) {
      notify_format(player, T("Wiz MOTD: %s"), cf_wizmotd_msg);
      notify_format(player, T("Down MOTD: %s"), cf_downmotd_msg);
      notify_format(player, T("Full MOTD: %s"), cf_fullmotd_msg);
    }
  }
}

/** Set a DOING message.
 * \verbatim
 * This implements @doing.
 * \endverbatim
 * \param player the enactor.
 * \param message the message to set.
 */
void
do_doing(dbref player, const char *message)
{
  char buf[MAX_COMMAND_LEN];
  DESC *d;
  int i;

  if (!Connected(player)) {
    /* non-connected things have no need for a doing */
    notify(player, T("Why would you want to do that?"));
    return;
  }
  mush_strncpy(buf, remove_markup(message, NULL), DOING_LEN);

  /* now smash undesirable characters and truncate */
  for (i = 0; i < DOING_LEN; i++) {
    if ((buf[i] == '\r') || (buf[i] == '\n') ||
        (buf[i] == '\t') || (buf[i] == BEEP_CHAR))
      buf[i] = ' ';
  }
  buf[DOING_LEN - 1] = '\0';

  /* set it */
  for (d = descriptor_list; d; d = d->next)
    if (d->connected && (d->player == player))
      strcpy(d->doing, buf);
  if (strlen(message) >= DOING_LEN) {
    notify_format(player,
                  T("Doing set. %d characters lost."),
                  (int) strlen(message) - (DOING_LEN - 1));
  } else
    notify(player, T("Doing set."));
}

/** Set a poll message (which replaces "Doing" in the DOING output).
 * \verbatim
 * This implements @poll.
 * \endverbatim
 * \param player the enactor.
 * \param message the message to set.
 * \param clear true if the poll should be reset to the default 'Doing'
 */
void
do_poll(dbref player, const char *message, int clear)
{
  int i;

  if ((!message || !*message) && !clear) {
    /* Just display the poll. */
    notify_format(player, T("The current poll is: %s"), poll_msg);
    return;
  }

  if (!Change_Poll(player)) {
    notify(player, T("Who do you think you are, Gallup?"));
    return;
  }

  if (clear) {
    strcpy(poll_msg, "Doing");
    notify(player, T("Poll reset."));
    return;
  }

  strncpy(poll_msg, remove_markup(message, NULL), DOING_LEN - 1);
  for (i = 0; i < DOING_LEN; i++) {
    if ((poll_msg[i] == '\r') || (poll_msg[i] == '\n') ||
        (poll_msg[i] == '\t') || (poll_msg[i] == BEEP_CHAR))
      poll_msg[i] = ' ';
  }
  poll_msg[DOING_LEN - 1] = '\0';

  if (strlen(message) >= DOING_LEN) {
    poll_msg[DOING_LEN - 1] = 0;
    notify_format(player,
                  T("Poll set to '%s'. %d characters lost."), poll_msg,
                  (int) strlen(message) - (DOING_LEN - 1));
  } else
    notify_format(player, T("Poll set to: %s"), poll_msg);
  do_log(LT_WIZ, player, NOTHING, T("Poll Set to '%s'."), poll_msg);
}

/** Match the partial name of a connected player.
 * \param match string to match.
 * \return dbref of a unique connected player whose name partial-matches, 
 * AMBIGUOUS, or NOTHING.
 */
dbref
short_page(const char *match)
{
  DESC *d;
  dbref who1 = NOTHING;
  int count = 0;

  for (d = descriptor_list; d; d = d->next) {
    if (d->connected) {
      if (match && !string_prefix(Name(d->player), match))
        continue;
      if (!strcasecmp(Name(d->player), match)) {
        count = 1;
        who1 = d->player;
        break;
      }
      if (who1 == NOTHING || d->player != who1) {
        who1 = d->player;
        count++;
      }
    }
  }

  if (count > 1)
    return AMBIGUOUS;
  else if (count == 0)
    return NOTHING;

  return who1;
}

/** Match the partial name of a connected player the enactor can see.
 * \param player the enactor
 * \param match string to match.
 * \return dbref of a unique connected player whose name partial-matches, 
 * AMBIGUOUS, or NOTHING.
 */
dbref
visible_short_page(dbref player, const char *match)
{
  dbref target;
  target = short_page(match);
  if (Priv_Who(player) || !GoodObject(target))
    return target;
  if (Dark(target) || (hidden(target) && !nearby(player, target)))
    return NOTHING;
  return target;
}

/* LWHO() function - really belongs elsewhere but needs stuff declared here */

FUNCTION(fun_xwho)
{
  DESC *d;
  int nwho;
  int first;
  int start, count;
  int powered = (*(called_as + 1) != 'M');
  int objid = (strchr(called_as, 'D') != NULL);

  if (!is_strict_integer(args[0]) || !is_strict_integer(args[1])) {
    safe_str(T(e_int), buff, bp);
    return;
  }
  start = parse_integer(args[0]);
  count = parse_integer(args[1]);

  if (start < 1 || count < 1) {
    safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
    return;
  }

  nwho = 0;
  first = 1;

  DESC_ITER_CONN(d) {
    if (!Hidden(d) || (powered && Priv_Who(executor))) {
      nwho += 1;
      if (nwho >= start && nwho < (start + count)) {
        if (first)
          first = 0;
        else
          safe_chr(' ', buff, bp);
        safe_dbref(d->player, buff, bp);
        if (objid) {
          safe_chr(':', buff, bp);
          safe_integer(CreTime(d->player), buff, bp);
        }
      }
    }
  }

}

/* ARGSUSED */
FUNCTION(fun_nwho)
{
  DESC *d;
  int count = 0;
  int powered = (*(called_as + 1) != 'M');

  DESC_ITER_CONN(d) {
    if (!Hidden(d) || (powered && Priv_Who(executor))) {
      count++;
    }
  }
  safe_integer(count, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_lwho)
{
  DESC *d;
  int first;
  dbref victim;
  int powered = ((*called_as == 'L') && Priv_Who(executor));
  int objid = (strchr(called_as, 'D') != NULL);

  first = 1;

  if (nargs && args[0] && *args[0]) {
    /* An argument was given. Find the victim and choose the lowest
     * perms possible */
    if (!powered) {
      safe_str(T(e_perm), buff, bp);
      return;
    }
    if ((victim = noisy_match_result(executor, args[0], NOTYPE,
                                     MAT_EVERYTHING)) == 0) {
      safe_str(T(e_notvis), buff, bp);
      return;
    }
    if (!Priv_Who(victim))
      powered = 0;
  }

  DESC_ITER_CONN(d) {
    if (!Hidden(d) || powered) {
      if (first)
        first = 0;
      else
        safe_chr(' ', buff, bp);
      safe_dbref(d->player, buff, bp);
      if (objid) {
        safe_chr(':', buff, bp);
        safe_integer(CreTime(d->player), buff, bp);
      }
    }
  }
}


/** Look up a DESC by character name or file descriptor.
 * \param executor the dbref of the object calling the function calling this.
 * \param name the name or descriptor to look up.
 * \retval a pointer to the proper DESC, or NULL
 */
static DESC *
lookup_desc(dbref executor, const char *name)
{
  DESC *d;

  /* Given a file descriptor. See-all only. */
  if (is_strict_integer(name)) {
    int fd = parse_integer(name);
    DESC_ITER_CONN(d) {
      if (d->descriptor == fd) {
        if (Priv_Who(executor) || d->player == executor)
          return d;
        else
          return NULL;
      }
    }
    return NULL;
  } else {                      /* Look up player name */
    DESC *match = NULL;
    dbref target = lookup_player(name);
    if (target == NOTHING) {
      target = match_result(executor, name, TYPE_PLAYER,
                            MAT_ABSOLUTE | MAT_PLAYER | MAT_ME);
    }
    if (!GoodObject(target) || !Connected(target))
      return NULL;
    else {
      /* walk the descriptor list looking for a match of a dbref */
      DESC_ITER_CONN(d) {
        if ((d->player == target) &&
            (!Hidden(d) || Priv_Who(executor)) &&
            (!match || (d->last_time > match->last_time)))
          match = d;
      }
      return match;
    }
  }
}

/** Return the conn time of the longest-connected connection of a player.
 * This function treats hidden connectios as nonexistent.
 * \param player dbref of player to get ip for.
 * \return connection time of player as an INT, or -1 if not found or hidden.
 */
int
most_conn_time(dbref player)
{
  DESC *d, *match = NULL;
  DESC_ITER_CONN(d) {
    if ((d->player == player) && !Hidden(d) && (!match ||
                                                (d->connected_at >
                                                 match->connected_at)))
      match = d;
  }
  if (match) {
    double result = difftime(mudtime, match->connected_at);
    return (int) result;
  } else
    return -1;
}

/** Return the conn time of the longest-connected connection of a player.
 * This function does includes hidden people.
 * \param player dbref of player to get ip for.
 * \return connection time of player as an INT, or -1 if not found.
 */
int
most_conn_time_priv(dbref player)
{
  DESC *d, *match = NULL;
  DESC_ITER_CONN(d) {
    if ((d->player == player) && (!match ||
                                  (d->connected_at > match->connected_at)))
      match = d;
  }
  if (match) {
    double result = difftime(mudtime, match->connected_at);
    return (int) result;
  } else
    return -1;
}

/** Return the idle time of the least-idle connection of a player.
 * This function treats hidden connections as nonexistant.
 * \param player dbref of player to get time for.
 * \return idle time of player as an INT, or -1 if not found or hidden.
 */
int
least_idle_time(dbref player)
{
  DESC *d, *match = NULL;
  DESC_ITER_CONN(d) {
    if ((d->player == player) && !Hidden(d) &&
        (!match || (d->last_time > match->last_time)))
      match = d;
  }
  if (match) {
    double result = difftime(mudtime, match->last_time);
    return (int) result;
  } else
    return -1;
}

/** Return the idle time of the least-idle connection of a player.
 * This function performs no permission checking.
 * \param player dbref of player to get time for.
 * \return idle time of player as an INT, or -1 if not found.
 */
int
least_idle_time_priv(dbref player)
{
  DESC *d, *match = NULL;
  DESC_ITER_CONN(d) {
    if ((d->player == player) && (!match || (d->last_time > match->last_time)))
      match = d;
  }
  if (match) {
    double result = difftime(mudtime, match->last_time);
    return (int) result;
  } else
    return -1;
}

/** Return the ip address of the least-idle connection of a player.
 * This function performs no permission checking, and returns the
 * pointer from the descriptor itself (so don't destroy the result!)
 * \param player dbref of player to get ip for.
 * \return IP address of player as a string or NULL if not found.
 */
char *
least_idle_ip(dbref player)
{
  DESC *d, *match = NULL;
  DESC_ITER_CONN(d) {
    if ((d->player == player) && (!match || (d->last_time > match->last_time)))
      match = d;
  }
  return match ? (match->ip) : NULL;
}

/** Return the hostname of the least-idle connection of a player.
 * This function performs no permission checking, and returns a static
 * string.
 * \param player dbref of player to get ip for.
 * \return hostname of player as a string or NULL if not found.
 */
char *
least_idle_hostname(dbref player)
{
  DESC *d, *match = NULL;
  static char hostname[101];
  char *p;

  DESC_ITER_CONN(d) {
    if ((d->player == player) && (!match || (d->last_time > match->last_time)))
      match = d;
  }
  if (!match)
    return NULL;
  strcpy(hostname, match->addr);
  if ((p = strchr(hostname, '@'))) {
    p++;
    return p;
  } else
    return hostname;
}

/* ZWHO() function - really belongs in eval.c but needs stuff declared here */
/* ARGSUSED */
FUNCTION(fun_zwho)
{
  DESC *d;
  dbref zone, victim;
  int first;
  int powered = (strcmp(called_as, "ZMWHO") && Priv_Who(executor));
  first = 1;

  zone = match_thing(executor, args[0]);

  if (nargs == 1) {
    victim = executor;
  } else if ((nargs == 2) && powered) {
    if ((victim = match_thing(executor, args[1])) == 0) {
      safe_str(T(e_match), buff, bp);
      return;
    }
  } else {
    safe_str(T(e_perm), buff, bp);
    return;
  }

  if (!GoodObject(zone)
      || (!Priv_Who(executor) && !eval_lock(victim, zone, Zone_Lock))) {
    safe_str(T(e_perm), buff, bp);
    return;
  }
  if ((getlock(zone, Zone_Lock) == TRUE_BOOLEXP) ||
      (IsPlayer(zone) && !(has_flag_by_name(zone, "SHARED", TYPE_PLAYER)))) {
    safe_str(T("#-1 INVALID ZONE."), buff, bp);
    return;
  }

  /* Use lowest privilege for victim */
  if (!Priv_Who(victim))
    powered = 0;

  DESC_ITER_CONN(d) {
    if (!Hidden(d) || powered) {
      if (Zone(Location(d->player)) == zone) {
        if (first) {
          first = 0;
        } else {
          safe_chr(' ', buff, bp);
        }
        safe_dbref(d->player, buff, bp);
      }
    }
  }
}

/* ARGSUSED */
FUNCTION(fun_player)
{
  /* Gets the player associated with a particular descriptor */
  DESC *d = lookup_desc(executor, args[0]);
  if (d)
    safe_dbref(d->player, buff, bp);
  else
    safe_str("#-1", buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_doing)
{
  /* Gets a player's @doing */
  DESC *d = lookup_desc(executor, args[0]);
  if (d)
    safe_str(d->doing, buff, bp);
  else
    safe_str("#-1", buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_hostname)
{
  /* Gets a player's hostname */
  DESC *d = lookup_desc(executor, args[0]);
  if (d && (d->player == executor || See_All(executor)))
    safe_str(d->addr, buff, bp);
  else
    safe_str("#-1", buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_ipaddr)
{
  /* Gets a player's IP address */
  DESC *d = lookup_desc(executor, args[0]);
  if (d && (d->player == executor || See_All(executor)))
    safe_str(d->ip, buff, bp);
  else
    safe_str("#-1", buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_cmds)
{
  /* Gets a player's IP address */
  DESC *d = lookup_desc(executor, args[0]);
  if (d && (d->player == executor || See_All(executor)))
    safe_integer(d->cmds, buff, bp);
  else
    safe_integer(-1, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_sent)
{
  /* Gets a player's bytes sent */
  DESC *d = lookup_desc(executor, args[0]);
  if (d && (d->player == executor || See_All(executor)))
    safe_integer(d->input_chars, buff, bp);
  else
    safe_integer(-1, buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_recv)
{
  /* Gets a player's bytes received */
  DESC *d = lookup_desc(executor, args[0]);
  if (d && (d->player == executor || See_All(executor)))
    safe_integer(d->output_chars, buff, bp);
  else
    safe_integer(-1, buff, bp);
}

FUNCTION(fun_poll)
{
  /* Gets the current poll */
  if (poll_msg[0] == '\0')
    strcpy(poll_msg, "Doing");

  safe_str(poll_msg, buff, bp);
}

FUNCTION(fun_pueblo)
{
  /* Return the status of the pueblo flag on the least idle descriptor we
   * find that matches the player's dbref.
   */
  DESC *match = lookup_desc(executor, args[0]);
  if (match)
    safe_boolean(match->conn_flags & CONN_HTML, buff, bp);
  else
    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
}

FUNCTION(fun_ssl)
{
  /* Return the status of the ssl flag on the least idle descriptor we
   * find that matches the player's dbref.
   */
#ifdef HAS_OPENSSL
  DESC *match;
  if (!sslsock) {
    safe_boolean(0, buff, bp);
    return;
  }
  match = lookup_desc(executor, args[0]);
  if (match) {
    if (match->player == executor || See_All(executor))
      safe_boolean((match->ssl != NULL), buff, bp);
    else
      safe_str(T(e_perm), buff, bp);
  } else
    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
#else
  safe_boolean(0, buff, bp);
#endif
}

FUNCTION(fun_width)
{
  DESC *match;
  if (!*args[0])
    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
  else if ((match = lookup_desc(executor, args[0])))
    safe_integer(match->width, buff, bp);
  else if (args[1])
    safe_str(args[1], buff, bp);
  else
    safe_str("78", buff, bp);
}

FUNCTION(fun_height)
{
  DESC *match;
  if (!*args[0])
    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
  else if ((match = lookup_desc(executor, args[0])))
    safe_integer(match->height, buff, bp);
  else if (args[1])
    safe_str(args[1], buff, bp);
  else
    safe_str("24", buff, bp);
}

FUNCTION(fun_terminfo)
{
  DESC *match;
  if (!*args[0])
    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
  else if ((match = lookup_desc(executor, args[0]))) {
    if (match->player == executor || See_All(executor)) {
      safe_str(match->ttype, buff, bp);
      if (match->conn_flags & CONN_HTML)
        safe_str(" pueblo", buff, bp);
      if (match->conn_flags & CONN_TELNET)
        safe_str(" telnet", buff, bp);
      if (match->conn_flags & CONN_PROMPT_NEWLINES)
        safe_str(" prompt_newlines", buff, bp);
#ifdef HAS_OPENSSL
      if (sslsock && match->ssl)
        safe_str(" ssl", buff, bp);
#endif
    } else
      safe_str(T(e_perm), buff, bp);
  } else
    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_idlesecs)
{
  /* returns the number of seconds a player has been idle, using
   * their least idle connection
   */

  DESC *match = lookup_desc(executor, args[0]);
  if (match)
    safe_number(difftime(mudtime, match->last_time), buff, bp);
  else
    safe_str("-1", buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_conn)
{
  /* returns the number of seconds a player has been connected, using
   * their longest-connected descriptor
   */

  DESC *match = lookup_desc(executor, args[0]);
  if (match)
    safe_number(difftime(mudtime, match->connected_at), buff, bp);
  else
    safe_str("-1", buff, bp);
}

/* ARGSUSED */
FUNCTION(fun_lports)
{
  DESC *d;
  int first = 1;

  if (!Priv_Who(executor)) {
    safe_str(T(e_perm), buff, bp);
    return;
  }

  DESC_ITER_CONN(d) {
    if (first)
      first = 0;
    else
      safe_chr(' ', buff, bp);
    safe_integer(d->descriptor, buff, bp);
  }
}

/* ARGSUSED */
FUNCTION(fun_ports)
{
  /* returns a list of the network descriptors that a player is
   * connected to 
   */

  dbref target;
  DESC *d;
  int first;

  target = lookup_player(args[0]);
  if (target == NOTHING) {
    target = match_result(executor, args[0], TYPE_PLAYER,
                          MAT_ABSOLUTE | MAT_PLAYER | MAT_ME);
  }
  if (target != executor && !Priv_Who(executor)) {
    /* This should probably be a safe_str */
    notify(executor, T("Permission denied."));
    return;
  }
  if (!GoodObject(target) || !Connected(target)) {
    return;
  }
  /* Walk descriptor chain. */
  first = 1;
  DESC_ITER_CONN(d) {
    if (d->player == target) {
      if (first)
        first = 0;
      else
        safe_chr(' ', buff, bp);
      safe_integer(d->descriptor, buff, bp);
    }
  }
}


/** Hide or unhide a player.
 * Although hiding is a per-descriptor state, this function sets all of
 * a player's connected descriptors to be hidden.
 * \param player dbref of player to hide.
 * \param hide if 1, hide; if 0, unhide.
 */
void
hide_player(dbref player, int hide)
{
  DESC *d;
  if (!Connected(player))
    return;
  if (!Can_Hide(player)) {
    notify(player, T("Permission denied."));
    return;
  }
  /* change status on WHO */
  if (Can_Hide(player)) {
    DESC_ITER_CONN(d) {
      if (d->player == player)
        d->hide = hide;
    }
  }
  if (hide)
    notify(player, T("You no longer appear on the WHO list."));
  else
    notify(player, T("You now appear on the WHO list."));
}

/** Perform the periodic check of inactive descriptors, and 
 * disconnect them or autohide them as appropriate.
 */
void
inactivity_check(void)
{
  DESC *d, *nextd;
  time_t now, idle, idle_for, unconnected_idle;
  if (!INACTIVITY_LIMIT && !UNCONNECTED_LIMIT)
    return;
  now = mudtime;
  idle = INACTIVITY_LIMIT ? INACTIVITY_LIMIT : INT_MAX;
  unconnected_idle = UNCONNECTED_LIMIT ? UNCONNECTED_LIMIT : INT_MAX;
  for (d = descriptor_list; d; d = nextd) {
    nextd = d->next;
    idle_for = now - d->last_time;
    /* If they've been connected for 60 seconds without getting a telnet-option
       back, the client probably doesn't understand them */
    if ((d->conn_flags & CONN_TELNET_QUERY) && idle_for > 60)
      d->conn_flags &= ~CONN_TELNET_QUERY;
    if ((d->connected) ? (idle_for > idle) : (idle_for > unconnected_idle)) {

      if (!d->connected)
        shutdownsock(d);
      else if (!Can_Idle(d->player)) {

        queue_string(d, T("\n*** Inactivity timeout ***\n"));
        do_log(LT_CONN, 0, 0,
               T("[%d/%s/%s] Logout by %s(#%d) <Inactivity Timeout>"),
               d->descriptor, d->addr, d->ip, Name(d->player), d->player);
        boot_desc(d);
      } else if (Unfind(d->player)) {

        if ((Can_Hide(d->player)) && (!Hidden(d))) {
          queue_string(d,
                       T
                       ("\n*** Inactivity limit reached. You are now HIDDEN. ***\n"));
          d->hide = 1;
        }
      }
    }
  }
}


/** Given a player dbref, return the player's hidden status.
 * \param player dbref of player to check.
 * \retval 1 player is hidden.
 * \retval 0 player is not hidden.
 */
int
hidden(dbref player)
{
  DESC *d;
  DESC_ITER_CONN(d) {
    if (d->player == player) {
      if (Hidden(d))
        return 1;
      else
        return 0;
    }
  }
  return 0;
}


/** Return the mailp of the player closest in db# to player,
 * or NULL if there's nobody on-line.
 * In the current mail system, mail is stored in a linked list, sorted
 * by recipient, which makes the most common operations (listing and reading
 * your mail) fast. When a player first connects, we store (on the
 * mailp element of their descriptor) a pointer to the beginning of
 * their part of the linked list. Rather than search the whole linked
 * list to find this location, we look at the mailp's of all the other
 * connected players, and find the mailp of the player whose dbref
 * is closest to the connecting player, and start our search from that
 * point. This scales up nicely - as a mushes get larger, the linked
 * list gets larger, but the more people connected at once, the faster
 * the search for a newly connecting player's first mail.
 * \param player player whose db# we want to get near.
 * \return pointer to first mail of connected player with db# closest to
 * player.
 */
MAIL *
desc_mail(dbref player)
{
  DESC *d;
  int i;
  int diff = db_top;
  static MAIL *mp;
  mp = NULL;
  DESC_ITER_CONN(d) {
    i = abs(d->player - player);
    if (i == 0)
      return d->mailp;
    if ((i < diff) && d->mailp) {
      diff = i;
      mp = d->mailp;
    }
  }
  return mp;
}

/** Set a player's mail position on all their descriptors.
 * \param player player to set mail position for.
 * \param mp pointer to first mail in their list.
 */
void
desc_mail_set(dbref player, MAIL *mp)
{
  DESC *d;
  DESC_ITER_CONN(d) {
    if (d->player == player)
      d->mailp = mp;
  }
}

/** Clear mail positions on all descriptors. Called from do_mail_nuke().
 */
void
desc_mail_clear(void)
{
  DESC *d;
  DESC_ITER_CONN(d) {
    d->mailp = NULL;
  }
}




#ifdef SUN_OS
/* SunOS's implementation of stdio breaks when you get a file descriptor
 * greater than 128. Brain damage, brain damage, brain damage!
 *
 * Our objective, therefore, is not to fix stdio, but to work around it,
 * so that performance degrades semi-gracefully when you are using a lot
 * of file descriptors.
 * Therefore, we'll save a file descriptor when we start up that is less
 * than 128, so that if we get a file descriptor that is >= 128, we can
 * use our own saved file descriptor instead. This is only one level of
 * defense; if you have more than 128 fd's in use, and you try two fopen's
 * before doing an fclose(), the second will fail.
 */

FILE *
fopen(file, mode)
    const char *file;
    const char *mode;
{
/* FILE *f; */
  int fd, rw, oflags = 0;
/* char tbchar; */
  rw = (mode[1] == '+') || (mode[1] && (mode[2] == '+'));
  switch (*mode) {
  case 'a':
    oflags = O_CREAT | (rw ? O_RDWR : O_WRONLY);
    break;
  case 'r':
    oflags = rw ? O_RDWR : O_RDONLY;
    break;
  case 'w':
    oflags = O_TRUNC | O_CREAT | (rw ? O_RDWR : O_WRONLY);
    break;
  default:
    return (NULL);
  }
/* SunOS fopen() doesn't use the 't' or 'b' flags. */


  fd = open(file, oflags, 0666);
  if (fd < 0)
    return NULL;
  /* My addition, to cope with SunOS brain damage! */
  if (fd >= 128) {
    close(fd);
    if ((extrafd < 128) && (extrafd >= 0)) {
      close(extrafd);
      fd = open(file, oflags, 0666);
      extrafd = -1;
    } else {
      return NULL;
    }
  }
  /* End addition. */

  return fdopen(fd, mode);
}


#undef fclose(x)
int
f_close(stream)
    FILE *stream;
{
  int fd = fileno(stream);
  /* if extrafd is bad, and the fd we're closing is good, recycle the
   * fd into extrafd.
   */
  fclose(stream);
  if (((extrafd < 0)) && (fd >= 0) && (fd < 128)) {
    extrafd = open("/dev/null", O_RDWR);
    if (extrafd >= 128) {
      /* To our surprise, we didn't get a usable fd. */
      close(extrafd);
      extrafd = -1;
    }
  }
  return 0;
}

#define fclose(x) f_close(x)
#endif                          /* SUN_OS */

#ifdef HAS_OPENSSL
/** Take down all SSL client connections and close the SSL server socket.
 * Typically, this is in preparation for a shutdown/reboot.
 */
void
close_ssl_connections(void)
{
  DESC *d;

  if (!sslsock)
    return;

  /* Close clients */
  DESC_ITER_CONN(d) {
    if (d->ssl) {
      queue_string_eol(d, T(ssl_shutdown_message));
      process_output(d);
      ssl_close_connection(d->ssl);
      d->ssl = NULL;
      d->conn_flags |= CONN_CLOSE_READY;
    }
  }
  /* Close server socket */
  ssl_close_connection(ssl_master_socket);
  shutdown(sslsock, 2);
  closesocket(sslsock);
  sslsock = 0;
  options.ssl_port = 0;
}
#endif


/** Dump the descriptor list to our REBOOTFILE so we can restore it on reboot.
 */
void
dump_reboot_db(void)
{
  FILE *f;
  DESC *d;
  long flags = RDBF_SCREENSIZE | RDBF_TTYPE | RDBF_PUEBLO_CHECKSUM;
  if (setjmp(db_err)) {
    flag_broadcast(0, 0, T("GAME: Error writing reboot database!"));
    exit(0);
  } else {

    f = fopen(REBOOTFILE, "w");
    /* This shouldn't happen */
    if (!f) {
      flag_broadcast(0, 0, T("GAME: Error writing reboot database!"));
      exit(0);
    }
    /* Write out the reboot db flags here */
    fprintf(f, "V%ld\n", flags);
    putref(f, sock);
    putref(f, maxd);
    /* First, iterate through all descriptors to get to the end
     * we do this so the descriptor_list isn't reversed on reboot
     */
    for (d = descriptor_list; d && d->next; d = d->next) ;
    /* Second, we iterate backwards from the end of descriptor_list
     * which is now in the d variable.
     */
    for (; d != NULL; d = d->prev) {
      putref(f, d->descriptor);
      putref(f, d->connected_at);
      putref(f, d->hide);
      putref(f, d->cmds);
      if (GoodObject(d->player))
        putref(f, d->player);
      else
        putref(f, -1);
      putref(f, d->last_time);
      if (d->output_prefix)
        putstring(f, (char *) d->output_prefix);
      else
        putstring(f, "__NONE__");
      if (d->output_suffix)
        putstring(f, (char *) d->output_suffix);
      else
        putstring(f, "__NONE__");
      putstring(f, d->addr);
      putstring(f, d->ip);
      putstring(f, d->doing);
      putref(f, d->conn_flags);
      putref(f, d->width);
      putref(f, d->height);
      putstring(f, d->ttype);
      putstring(f, d->checksum);
    }                           /* for loop */

    putref(f, 0);
    putstring(f, poll_msg);
    putref(f, globals.first_start_time);
    putref(f, globals.reboot_count);
    fclose(f);
  }
}

/** Load the descriptor list back from the REBOOTFILE on reboot.
 */
void
load_reboot_db(void)
{
  FILE *f;
  DESC *d = NULL;
  DESC *closed = NULL, *nextclosed;
  int val;
  const char *temp;
  char c;
  long flags = 0;
  f = fopen(REBOOTFILE, "r");
  if (!f) {
    restarting = 0;
    return;
  }
  restarting = 1;
  /* Get the first line and see if it's a set of reboot db flags.
   * Those start with V<number>
   * If not, assume we're using the original format, in which the
   * sock appears first
   * */
  c = getc(f);                  /* Skip the V */
  if (c == 'V') {
    flags = getref(f);
  } else {
    ungetc(c, f);
  }

  sock = getref(f);
  val = getref(f);
  if (val > maxd)
    maxd = val;

  while ((val = getref(f)) != 0) {
    ndescriptors++;
    d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
    d->descriptor = val;
    d->connected_at = getref(f);
    d->hide = getref(f);
    d->cmds = getref(f);
    d->player = getref(f);
    d->last_time = getref(f);
    d->connected = GoodObject(d->player) ? 1 : 0;
    temp = getstring_noalloc(f);
    d->output_prefix = NULL;
    if (strcmp(temp, "__NONE__"))
      set_userstring(&d->output_prefix, temp);
    temp = getstring_noalloc(f);
    d->output_suffix = NULL;
    if (strcmp(temp, "__NONE__"))
      set_userstring(&d->output_suffix, temp);
    mush_strncpy(d->addr, getstring_noalloc(f), 100);
    mush_strncpy(d->ip, getstring_noalloc(f), 100);
    mush_strncpy(d->doing, getstring_noalloc(f), DOING_LEN);
    d->conn_flags = getref(f);
    if (flags & RDBF_SCREENSIZE) {
      d->width = getref(f);
      d->height = getref(f);
    } else {
      d->width = 78;
      d->height = 24;
    }
    if (flags & RDBF_TTYPE)
      d->ttype = mush_strdup(getstring_noalloc(f), "terminal description");
    else
      d->ttype = mush_strdup("unknown", "terminal description");
    if (flags & RDBF_PUEBLO_CHECKSUM)
      strcpy(d->checksum, getstring_noalloc(f));
    else
      d->checksum[0] = '\0';
    d->input_chars = 0;
    d->output_chars = 0;
    d->output_size = 0;
    d->output.head = 0;
    d->output.tail = &d->output.head;
    d->input.head = 0;
    d->input.tail = &d->input.head;
    d->raw_input = NULL;
    d->raw_input_at = NULL;
    d->quota = options.starting_quota;
    d->mailp = NULL;
#ifdef HAS_OPENSSL
    d->ssl = NULL;
    d->ssl_state = 0;
#endif
    if (d->conn_flags & CONN_CLOSE_READY) {
      /* This isn't really an open descriptor, we're just tracking
       * it so we can announce the disconnect properly. Do so, but
       * don't link it into the descriptor list. Instead, keep a
       * separate list.
       */
      if (closed)
        closed->prev = d;
      d->next = closed;
      d->prev = NULL;
      closed = d;
    } else {
      if (descriptor_list)
        descriptor_list->prev = d;
      d->next = descriptor_list;
      d->prev = NULL;
      descriptor_list = d;
      if (d->connected && d->player && GoodObject(d->player) &&
          IsPlayer(d->player))
        set_flag_internal(d->player, "CONNECTED");
      else if ((!d->player || !GoodObject(d->player)) && d->connected) {
        d->connected = 0;
        d->player = 0;
      }
    }
  }                             /* while loop */

  /* Now announce disconnects of everyone who's not really here */
  while (closed) {
    nextclosed = closed->next;
    announce_disconnect(closed);
    mush_free(closed, "descriptor");
    closed = nextclosed;
  }

  strcpy(poll_msg, getstring_noalloc(f));
  globals.first_start_time = getref(f);
  globals.reboot_count = getref(f) + 1;
  DESC_ITER_CONN(d) {
    d->mailp = find_exact_starting_point(d->player);
  }
#ifdef HAS_OPENSSL
  if (SSLPORT) {
    sslsock = make_socket(SSLPORT, SOCK_STREAM, NULL, NULL, SSL_IP_ADDR);
    ssl_master_socket = ssl_setup_socket(sslsock);
    if (sslsock >= maxd)
      maxd = sslsock + 1;
  }
#endif

  fclose(f);
  remove(REBOOTFILE);
  flag_broadcast(0, 0, T("GAME: Reboot finished."));
}