ldmud-3.3.719/
ldmud-3.3.719/doc/
ldmud-3.3.719/doc/efun.de/
ldmud-3.3.719/doc/efun/
ldmud-3.3.719/doc/man/
ldmud-3.3.719/doc/other/
ldmud-3.3.719/mud/
ldmud-3.3.719/mud/heaven7/
ldmud-3.3.719/mud/lp-245/
ldmud-3.3.719/mud/lp-245/banish/
ldmud-3.3.719/mud/lp-245/doc/
ldmud-3.3.719/mud/lp-245/doc/examples/
ldmud-3.3.719/mud/lp-245/doc/sefun/
ldmud-3.3.719/mud/lp-245/log/
ldmud-3.3.719/mud/lp-245/obj/Go/
ldmud-3.3.719/mud/lp-245/players/lars/
ldmud-3.3.719/mud/lp-245/room/death/
ldmud-3.3.719/mud/lp-245/room/maze1/
ldmud-3.3.719/mud/lp-245/room/sub/
ldmud-3.3.719/mud/lp-245/secure/
ldmud-3.3.719/mud/sticklib/
ldmud-3.3.719/mud/sticklib/src/
ldmud-3.3.719/mudlib/deprecated/
ldmud-3.3.719/mudlib/uni-crasher/
ldmud-3.3.719/pkg/
ldmud-3.3.719/pkg/debugger/
ldmud-3.3.719/pkg/diff/
ldmud-3.3.719/pkg/misc/
ldmud-3.3.719/src/
ldmud-3.3.719/src/autoconf/
ldmud-3.3.719/src/ptmalloc/
ldmud-3.3.719/src/util/
ldmud-3.3.719/src/util/erq/
ldmud-3.3.719/src/util/indent/hosts/next/
ldmud-3.3.719/src/util/xerq/
ldmud-3.3.719/src/util/xerq/lpc/
ldmud-3.3.719/src/util/xerq/lpc/www/
ldmud-3.3.719/test/generic/
ldmud-3.3.719/test/inc/
ldmud-3.3.719/test/t-0000398/
ldmud-3.3.719/test/t-0000548/
ldmud-3.3.719/test/t-030925/
ldmud-3.3.719/test/t-040413/
ldmud-3.3.719/test/t-041124/
ldmud-3.3.719/test/t-language/
/*---------------------------------------------------------------------------
 * Gamedriver Communications module.
 *
 *---------------------------------------------------------------------------
 * Throughout the module the fact is used that valid socket numbers
 * are always >= 0. Unused sockets are therefore marked with negative
 * numbers.
 *
 * All information needed for an interactive object are stored in
 * a 'interactive_t'. This struct is linked to by the shadow sentence
 * of the interactive object.
 *
 * Sending data is performed through the function add_message().
 * The function collects the data in interactive.message_buf[] until
 * it is filled (or a special 'flush' message is passed), then the
 * data is written en-bloc to the network.
 *
 * Incoming data is collected in interactive.text[]. interactive.text_end
 * indexes the first free character in the buffer where new data is to
 * be appended. The new data is not passed directly to the command parser,
 * instead it is processed by a dfa implementing the important parts
 * of the telnet protocol. The dfa analyses the data read, interprets
 * any telnet commands and stores the remaining 'pure' data starting
 * from the beginning of .text[].
 *
 * Initialized to start working in the state TS_DATA, the dfa does its
 * thing until it either reaches a line end or the end of the current
 * data. If it is a line end, it terminates the pure data collected so
 * far with a '\0', goes into state TS_READY, and lets .tn_end index
 * the next unprocessed raw data char. If it is the end of the current
 * data, the dfa stays in whatever state it was and indexes the current end
 * of the pure data gathered so far with interactive.command_end. Once
 * a new chunk of data has been read, the dfa simply continues where it
 * took off.
 *
 * There are some variations to this scheme, but you get the idea. It is
 * possible to do all telnet negotiations on mudlib level, but it must
 * be either the driver or the mudlib, not both.
 *
 * To understand get_message() itself fully, think of it as a coroutine
 * with its own state. It does not really return to the caller (though
 * that is how it is implemented), in merely yields control back to
 * caller in order to process the found command or the pending heartbeat.
 * TODO: Obvious possibility for implementation multi-threading.
 *
 * Timing is implemented this way: The driver usually stays in the input
 * loop, waiting in 1 second intervals for incoming data. An alarm() is
 * triggered by backend.c every 2 seconds and sets the flag variable
 * comm_time_to_call_heart_beat. The loop checks this variable every second
 * and, if it is set, aborts its input loop and returns to the backend.
 * To mark the cause of the return, the variable time_to_call_heart_beat is
 * set before return.
 *
 * TODO: The noecho/charmode logic, especially in combination with
 * TODO:: the telnet machine is frustratingly underdocumented.
 *
 * The data that can not written directly to the sockets is put instead into
 * an intermediate buffer, from which the backend loop will continue writing
 * when possible. The buffers are stored in a linked list in the interactive_s
 * structure.
 *
 * TODO: Fiona says: The telnet code is frustrating. It would be better if
 * TODO:: the common handling of e.g. TELNET_NAWS is offered by hooks,
 * TODO:: as the Anarres version of MudOS does. This would mean a rewrite.
 *---------------------------------------------------------------------------
 */

#define SAVE_NOECHO       /* TODO: Define to enable safe NOECHO mode */
#define SIMULATE_CHARMODE /* TODO: Even linemode clients stay in charmode */

#include "driver.h"
#include "typedefs.h"

#include "my-alloca.h"

#include <stdio.h>
#include <ctype.h>
#include <sys/time.h>
#include <stdarg.h>
#include <stddef.h>
#include <sys/ioctl.h>

#define TELOPTS
#include "../mudlib/sys/telnet.h"

#ifdef HAVE_NETDB_H
#    include <netdb.h>
#endif

#ifdef HAVE_SYS_PARAM_H
#    include <sys/param.h>
#endif

#include <signal.h>

#if defined(_AIX)
#    include <sys/select.h>
#endif

#ifdef HAVE_FCNTL
#    include <fcntl.h>
#endif

#ifdef SOCKET_INC
#    include SOCKET_INC
#endif

#include "comm.h"
#include "access_check.h"
#include "actions.h"
#include "array.h"
#include "closure.h"
#include "ed.h"
#include "exec.h"
#include "filestat.h"
#include "gcollect.h"
#include "interpret.h"
#include "main.h"
#include "mstrings.h"
#include "object.h"
#include "pkg-mccp.h"
#include "pkg-pgsql.h"
#ifdef USE_TLS
#include "pkg-tls.h"
#endif
#include "sent.h"
#include "simulate.h"
#include "stdstrings.h"
#include "svalue.h"
#include "swap.h"
#include "wiz_list.h"
#include "xalloc.h"

#include "i-eval_cost.h"

#include "../mudlib/sys/comm.h"
#include "../mudlib/sys/configuration.h"
#include "../mudlib/sys/driver_hook.h"
#include "../mudlib/sys/input_to.h"

/* if driver is compiled for ERQ demon then include the necessary file
 */
#ifdef ERQ_DEMON
#    ifdef ERQ_INCLUDE
#        include ERQ_INCLUDE
#    else
#        include "util/erq/erq.h"
#    endif
#endif

/* When no special networking code is needed, define the
 * socket function to their normal Unix names.
 */
#if !defined (SOCKET_LIB) && !defined(SOCKET_INC)
#    define socket_number(s) (s)
#    define socket_ioctl  ioctl
#    ifndef hpux
#        define socket_select select
#    else
#        define socket_select(n,r,w,x,t) select(n, (int *)r, (int *)w, (int *)x, t)
         /* Silences the compiler */
#    endif
#    define socket_read   read
#    define socket_write  write
#    define socket_close  close
#endif /* SOCKET_LIB */

#if defined(_AIX)
typedef unsigned long length_t;
#elif defined(__INTEL_COMPILER) || defined (__GNUC__)
typedef socklen_t length_t;
#else
typedef int length_t;
#endif

#if defined(CYGWIN)
extern int socketpair(int, int, int, int[2]);
#endif

#ifndef EPROTO
#    define EPROTO EINTR
#endif
#ifndef SIGCLD
#    define SIGCLD SIGCHLD
#endif

#ifndef MAXHOSTNAMELEN
#    define MAXHOSTNAMELEN 64
#endif

#ifndef INET_ADDRSTRLEN
#    define INET_ADDRSTRLEN 16
#endif

/* Amazing how complicated networking can be, hm? */

/*-------------------------------------------------------------------------*/
/* Types */

/* --- struct input_to_s: input_to() datastructure
 *
 * input-to structures describe a pending input_to() for a given
 * interactive object. It is a specialization of input_s,
 * which therefore must be the first element.
 */
typedef struct input_to_s input_to_t;

struct input_to_s {
    input_t     input;
    callback_t  fun;        /* The function to call, and its args */
    p_uint      eval_nr;    /* The thread number where this started. */
};

/*-------------------------------------------------------------------------*/
/* Variables */

interactive_t *all_players[MAX_PLAYERS];
  /* Pointers to the structures of the interactive users.
   * Unused entries are NULL.
   * TODO: A list would be nicer
   */

static int max_player = -1;
  /* Index of the last used entry in all_players[]. */

int num_player = 0;
  /* The current number of active users */

char *message_flush = NULL;
  /* Special flush message for add_message().
   * It is a variable instead of a define to keep gcc from complaining about
   * a 'null format string'.
   */

p_int write_buffer_max_size = WRITE_BUFFER_MAX_SIZE;
  /* Amount of data held pending in the write fifo queue.
   *   0: No queue.
   *  -1: Infinite queue.
   */


#ifdef COMM_STAT

/* The statistics were originally introduced to measure the efficiency
 * of the message buffering in comparison to the unbuffered sending of
 * data. Nowadays, it's just interesting to know how much bandwidth you
 * use.
 */

unsigned long add_message_calls = 0;
  /* Number of calls to add_message() */

unsigned long inet_packets = 0;
  /* Number packets sent to the users */

unsigned long inet_volume = 0;
  /* Amount of data sent to the users */

unsigned long inet_packets_in = 0;
  /* Number packets received from the users */

unsigned long inet_volume_in = 0;
  /* Amount of data received from the users */

#endif

/*-------------------------------------------------------------------------*/

#ifdef ERQ_DEMON

#define MAX_PENDING_ERQ  32  /* Max number of pending requests */

#define FLAG_NO_ERQ   -2    /* No ERQ connected */
#define FLAG_ERQ_STOP -1    /* Severing connection */

static SOCKET_T erq_demon = FLAG_NO_ERQ;
  /* Socket of the connection to the erq demon. */

static SOCKET_T erq_proto_demon = -1;
  /* Socket to hold the connection to an aspiring new erq demon
   * while the connection to the current one is being severed.
   */

static char buf_from_erq[ERQ_MAX_REPLY];
  /* Buffer for the data received from the erq */

static char * input_from_erq = &buf_from_erq[0];
  /* Pointer to the first free byte in buf_from_erq. */

static unsigned long erq_pending_len = 0;
  /* erq_pending_len is used by send_erq(), but needs
   * to be cleared by stop_erq_demon().
   */

typedef struct erq_callback_s {
    svalue_t fun;
    Bool     string_arg;
} erq_callback_t;

static erq_callback_t pending_erq[MAX_PENDING_ERQ+1];
  /* ERQ callback handles. The last one is reserved for callback-free
   * requests.
   * .fun is the callback closure, .string_arg is TRUE if the closure
   * takes its data as a string instead of an array as argument.
   *
   * The free entries are organised in a singly linked list of
   * T_INVALID .fun svalues, using the .fun.u.generic to point to the next
   * free entry.
   */

static erq_callback_t *free_erq;
  /* The first free entry in the freelist in pending_erq[] */

/* The size of the IPTABLE depends on the number of users,
 * and is at least 200.
 */
#if MAX_PLAYERS > 700
#    define IPSIZE MAX_PLAYERS
#else
#    if MAX_PLAYERS > 100
#        define IPSIZE (MAX_PLAYERS*2)
#    else
#        define IPSIZE 200
#    endif
#endif

static struct ipentry {
    struct in_addr  addr;  /* The address (only .s_addr is significant) */
    string_t       *name;  /* tabled string with the hostname for <addr> */
} iptable[IPSIZE];
  /* Cache of known names for given IP addresses.
   * It is used as a ringbuffer, indexed by ipcur.
   * TODO: Instead of a simple circular buffer, the lookup should be
   * TODO:: hashed over the IP address. Worst case would still be O(IPSIZE),
   * TODO:: but best case would be O(1).
   * TODO: Allow unlimited numbers of entries, but give them a max lifetime
   * TODO:: of a day. After that, even existing entries need to be
   * TODO:: re-resolved. Maybe an additional size limit. (suggested by Coogan)
   */

static int ipcur = 0;
  /* Index of the next entry to use in the iptable[].
   */

#endif /* ERQ_DEMON */

/*-------------------------------------------------------------------------*/

/* --- Communication sockets --- */

static SOCKET_T sos[MAXNUMPORTS];
  /* The login sockets.
   */

static SOCKET_T udp_s = -1;
  /* The UDP socket */

/* --- Networking information --- */

static char host_name[MAXHOSTNAMELEN+1];
  /* This computer's hostname, used for query_host_name() efun.
   */

static struct in_addr host_ip_number;
  /* This computer's numeric IP address only, used for
   * the query_host_ip_number() efun.
   */

static struct sockaddr_in host_ip_addr_template;
  /* The template address of this computer. It is copied locally
   * and augmented with varying port numbers to open the driver's ports.
   */

char * domain_name = NULL;
  /* This computer's domain name, as needed by lex.c::get_domainname().
   */

static int min_nfds = 0;
  /* The number of fds used by the driver's sockets (udp, erq, login).
   * It is the number of the highest fd plus one.
   */

/* --- Telnet handling --- */

Bool sending_telnet_command = MY_FALSE;
  /* Mutex queried in add_message() to hide telnet commands
   * from snoopers and shadows.
   */

static void (*telopts_do  [NTELOPTS])(int);
static void (*telopts_dont[NTELOPTS])(int);
static void (*telopts_will[NTELOPTS])(int);
static void (*telopts_wont[NTELOPTS])(int);

  /* Tables with the telnet statemachine handlers.
   */

enum telnet_states {
  TS_DATA = 0,
  TS_IAC,
  TS_WILL,
  TS_WONT,
  TS_DO,
  TS_DONT,
  TS_SB,
  TS_SB_IAC,
  TS_READY,
  TS_SYNCH,
  TS_INVALID
};

  /* Telnet states
   */

static inline int TN_START_VALID(int x) {
  return (x & ~TS_IAC) == TS_SB;
}


/* --- Misc --- */

static volatile Bool urgent_data = MY_FALSE;
  /* Flag set when a SIGURG/SIGIO announces the arrival of
   * OOB data.
   */

static volatile mp_int urgent_data_time;
  /* The backend::current_time when urgent_data was set last.
   */

static object_t *first_player_for_flush = NULL;
  /* First interactive user object to flush. Marks the head
   * of the list formed by interactive.{next,previous}_player_for_flush
   */


/* Bitflags for interactive.do_close
 *
 * Putting PROTO_ERQ into do_close looks strange, but actually makes
 * sense because some of the steps to be taken for both are the
 * same.
 */

#define FLAG_DO_CLOSE   0x1
#define FLAG_PROTO_ERQ  0x2


/*-------------------------------------------------------------------------*/

/* Outgoing connections in-progress */

typedef enum {
      ocNotUsed    /* Entry not used */
    , ocUsed       /* Entry holds pending connection */
    , ocLoggingOn  /* Entry is doing the LPC logon protocol.
                    * This value should not appear outside of
                    * check_for_out_connections(), if it does, it
                    * means that LPC logon threw an error.
                    */
} OutConnStatus;

struct OutConn {
    struct sockaddr_in   target;   /* Address connected to (allocated) */
    object_t           * curr_obj; /* Associated object */
    int                  socket;   /* Socket on our side */
    OutConnStatus        status;   /* Status of this entry */
} outconn[MAX_OUTCONN];

/*-------------------------------------------------------------------------*/

/* Forward declarations */

static void mccp_telnet_neg(int);

static void free_input_to(input_to_t *);
static void free_input_handler(input_t *);
static void telnet_neg(interactive_t *);
static void send_will(int);
static void send_wont(int);
static void send_do(int);
static void send_dont(int);
static void add_flush_entry(interactive_t *ip);
static void remove_flush_entry(interactive_t *ip);
static void clear_message_buf(interactive_t *ip);
static void new_player(object_t *receiver, SOCKET_T new_socket, struct sockaddr_in *addr, size_t len, int login_port);

#ifdef ERQ_DEMON

static long read_32(char *);
static Bool send_erq(int handle, int request, const char *arg, size_t arglen);
static void shutdown_erq_demon(void);
static void stop_erq_demon(Bool);
static string_t * lookup_ip_entry (struct in_addr addr, Bool useErq);
static void add_ip_entry(struct in_addr addr, const char *name);
#ifdef USE_IPV6
static void update_ip_entry(const char *oldname, const char *newname);
static int open_ipv6_conn(const char *hostname, const unsigned short int port, struct sockaddr_in *pTarget);
#endif

#endif /* ERQ_DEMON */

static INLINE ssize_t comm_send_buf(char *msg, size_t size, interactive_t *ip);

#ifdef USE_IPV6

/*-------------------------------------------------------------------------*/

/* Not every IPv6 supporting platform has all the defines (like AIX 4.3) */

#ifndef IPV6_ADDR_SCOPE_GLOBAL
#  define IPV6_ADDR_SCOPE_GLOBAL 0x0e
#endif

#ifndef s6_addr8
#  define s6_addr8  __u6_addr.__u6_addr8
#endif

#ifndef s6_addr16
#  define s6_addr16 __u6_addr.__u6_addr16
#endif

#ifndef s6_addr32
#  define s6_addr32 __u6_addr.__u6_addr32
#endif

static inline void CREATE_IPV6_MAPPED(struct in_addr *v6, uint32 v4) {
  v6->s6_addr32[0] = 0;
  v6->s6_addr32[1] = 0;
  v6->s6_addr32[2] = 0x0000ffff;
  v6->s6_addr32[2] = 0xffff0000;
  v6->s6_addr32[3] = v4;
}

/* These are the typical IPv6 structures - we use them transparently.
 *
 * --- arpa/inet.h ---
 *
 * struct in6_addr {
 *         union {
 *                 u_int32_t u6_addr32[4];
 * #ifdef notyet
 *                 u_int64_t u6_addr64[2];
 * #endif
 *                 u_int16_t u6_addr16[8];
 *                 u_int8_t  u6_addr8[16];
 *         } u6_addr;
 * };
 * #define s6_addr32       u6_addr.u6_addr32
 * #ifdef notyet
 * #define s6_addr64       u6_addr.u6_addr64
 * #endif
 * #define s6_addr16       u6_addr.u6_addr16
 * #define s6_addr8        u6_addr.u6_addr8
 * #define s6_addr         u6_addr.u6_addr8
 *
 * --- netinet/in.h ---
 *
 * struct sockaddr_in6 {
 *    u_char                sin6_len;
 *    u_char                sin6_family;
 *    u_int16_t        sin6_port;
 *    u_int32_t        sin6_flowinfo;
 *    struct                 in6_addr        sin6_addr;
 * };
 *
 */

/*-------------------------------------------------------------------------*/
static char *
inet6_ntoa (struct in6_addr in)

/* Convert the ipv6 address <in> into a string and return it.
 * Note: the string is stored in a local buffer.
 */

{
    static char str[INET6_ADDRSTRLEN+1];

    if (NULL == inet_ntop(AF_INET6, &in, str, INET6_ADDRSTRLEN))
    {
        perror("inet_ntop");
    }
    return str;
} /* inet6_ntoa() */

/*-------------------------------------------------------------------------*/
static struct in6_addr
inet6_addr (const char *to_host)

/* Convert the name <to_host> into a ipv6 address and return it.
 */

{
    struct in6_addr addr;

    inet_pton(AF_INET6, to_host, &addr);
    return addr;
} /* inet6_addr() */

#endif /* USE_IPV6 */

/*-------------------------------------------------------------------------*/
static char *
decode_noecho (char noecho)

/* Decode the <noecho> flag byte into a string.
 * Result is a pointer to a static buffer.
 */

{
    static char buf[100];
    strcpy(buf, "(");
    if (noecho & NOECHO_REQ) strcat(buf, "NOECHO_REQ, ");
    if (noecho & CHARMODE_REQ) strcat(buf, "CHARMODE_REQ, ");
    if (noecho & NOECHO) strcat(buf, "NOECHO, ");
    if (noecho & CHARMODE) strcat(buf, "CHARMODE, ");
    if (noecho & NOECHO_ACK) strcat(buf, "NOECHO_ACK, ");
    if (noecho & CHARMODE_ACK) strcat(buf, "CHARMODE_ACK, ");
#ifdef SAVE_NOECHO
    if (noecho & NOECHO_DELAYED) strcat(buf, "NOECHO_DELAYED, ");
#endif
    if (noecho & NOECHO_STALE) strcat(buf, "NOECHO_STALE, ");
    if (noecho & IGNORE_BANG) strcat(buf, "IGNORE_BANG");
    strcat(buf, ")");

    return buf;
} /* decode_noecho() */

/*-------------------------------------------------------------------------*/
static void
dump_bytes (void * data, size_t length, int indent)

/* Write the datablock starting at <data> of size <length> to stderr.
 * If it spans more than one line, indent the following lines by <indent>.
 */

{
    int cur_indent = 0;
    unsigned char * datap = (unsigned char *)data;

    while (length > 0)
    {
        size_t count;

        if (cur_indent)
            fprintf(stderr, "%*.*s", cur_indent, cur_indent, " ");
        else
            cur_indent = indent;
        fprintf(stderr, " %p:", datap);

        for (count = 0; count < 16 && length > 0; ++count, --length, ++datap)
        {
            fprintf(stderr, " %02x", *datap);
        }
        putc('\n', stderr);
    }
} /* dump_bytes() */

/*-------------------------------------------------------------------------*/
static void comm_fatal (interactive_t *ip, char *fmt, ...)
                          FORMATDEBUG(printf,2,3) ;
static void
comm_fatal (interactive_t *ip, char *fmt, ...)

/* The telnet code ran into a fatal error.
 * Dump the data from the current interactive structure and disconnect
 * the user (we have to assume that the interactive structure is
 * irrecoverably hosed).
 * TODO: Make similar functions comm_error(), comm_perror() which prefix
 * TODO:: the error message with the ip %p and obj name.
 */

{
    va_list va;
    static Bool in_fatal = MY_FALSE;
    char * ts;
    char * msg = "\r\n=== Internal communications error in mud driver.\r\n"
                   "=== Please log back in and inform the administration.\r\n"
                   "\r\n";

    /* Prevent double fatal. */
    if (in_fatal)
        fatal("Recursive call to comm_fatal().");
    in_fatal = MY_TRUE;
    ts = time_stamp();

    /* Print the error message */

    va_start(va, fmt);

    fflush(stdout);
    fprintf(stderr, "%s ", ts);
    vfprintf(stderr, fmt, va);
    fflush(stderr);
    if (current_object)
        fprintf(stderr, "%s Current object was %s\n"
                      , ts, current_object->name
                            ? get_txt(current_object->name) : "<null>");
    debug_message("%s ", ts);
    vdebug_message(fmt, va);
    if (current_object)
        debug_message("%s Current object was %s\n"
                     , ts, current_object->name
                           ? get_txt(current_object->name) : "<null>");
    debug_message("%s Dump of the call chain:\n", ts);
    (void)dump_trace(MY_TRUE, NULL); fflush(stdout);

    va_end(va);

    /* Dump the interactive structure */

    fprintf(stderr, "--- Dump of current interactive structure (%p..%p) --- \n"
                  , ip, ip + sizeof(*ip) - 1);
    fprintf(stderr, "  .socket:            %d\n", ip->socket);
    fprintf(stderr, "  .ob:                %p", ip->ob);
      if (ip->ob)  fprintf(stderr, " (%s)", get_txt(ip->ob->name));
      putc('\n', stderr);
    fprintf(stderr, "  .input_handler:     %p\n", ip->input_handler);
    fprintf(stderr, "  .modify_command:    %p", ip->modify_command);
      if (ip->modify_command)  fprintf(stderr, " (%s)", get_txt(ip->modify_command->name));
      putc('\n', stderr);
    fprintf(stderr, "  .prompt:           ");
      dump_bytes(&(ip->prompt), sizeof(ip->prompt), 21);
    fprintf(stderr, "  .addr:             ");
      dump_bytes(&(ip->addr), sizeof(ip->addr), 21);
    fprintf(stderr, "  .closing:           %02hhx\n", (unsigned char)ip->closing);
    fprintf(stderr, "  .tn_enabled:        %02hhx\n", (unsigned char)ip->tn_enabled);
    fprintf(stderr, "  .do_close:          %02hhx", (unsigned char)ip->do_close);
      if (ip->do_close & (FLAG_DO_CLOSE|FLAG_PROTO_ERQ)) fprintf(stderr, " (");
      if (ip->do_close & FLAG_DO_CLOSE) fprintf(stderr, "DO_CLOSE");
      if (ip->do_close & (FLAG_DO_CLOSE|FLAG_PROTO_ERQ)) fprintf(stderr, ", ");
      if (ip->do_close & FLAG_PROTO_ERQ) fprintf(stderr, "PROTO_ERQ");
      if (ip->do_close & (FLAG_DO_CLOSE|FLAG_PROTO_ERQ)) fprintf(stderr, ")");
      putc('\n', stderr);
    fprintf(stderr, "  .noecho:            %02hhx", (unsigned char)ip->noecho);
      fprintf(stderr, " %s\n", decode_noecho(ip->noecho));
    fprintf(stderr, "  .tn_state:          %hhd", ip->tn_state);
      switch(ip->tn_state) {
      case TS_DATA:    fprintf(stderr, " (TS_DATA)\n"); break;
      case TS_IAC:     fprintf(stderr, " (TS_IAC)\n"); break;
      case TS_WILL:    fprintf(stderr, " (TS_WILL)\n"); break;
      case TS_WONT:    fprintf(stderr, " (TS_WONT)\n"); break;
      case TS_DO:      fprintf(stderr, " (TS_DO)\n"); break;
      case TS_DONT:    fprintf(stderr, " (TS_DONT)\n"); break;
      case TS_SB:      fprintf(stderr, " (TS_SB)\n"); break;
      case TS_SB_IAC:  fprintf(stderr, " (TS_SB_IAC)\n"); break;
      case TS_READY:   fprintf(stderr, " (TS_READY)\n"); break;
      case TS_SYNCH:   fprintf(stderr, " (TS_SYNCH)\n"); break;
      case TS_INVALID: fprintf(stderr, " (TS_INVALID)\n"); break;
      default: putc('\n', stderr);
      }
    fprintf(stderr, "  .save_tn_state:     %hhd", ip->save_tn_state);
      switch(ip->save_tn_state) {
      case TS_DATA:    fprintf(stderr, " (TS_DATA)\n"); break;
      case TS_IAC:     fprintf(stderr, " (TS_IAC)\n"); break;
      case TS_WILL:    fprintf(stderr, " (TS_WILL)\n"); break;
      case TS_WONT:    fprintf(stderr, " (TS_WONT)\n"); break;
      case TS_DO:      fprintf(stderr, " (TS_DO)\n"); break;
      case TS_DONT:    fprintf(stderr, " (TS_DONT)\n"); break;
      case TS_SB:      fprintf(stderr, " (TS_SB)\n"); break;
      case TS_SB_IAC:  fprintf(stderr, " (TS_SB_IAC)\n"); break;
      case TS_READY:   fprintf(stderr, " (TS_READY)\n"); break;
      case TS_SYNCH:   fprintf(stderr, " (TS_SYNCH)\n"); break;
      case TS_INVALID: fprintf(stderr, " (TS_INVALID)\n"); break;
      default: putc('\n', stderr);
      }
    fprintf(stderr, "  .supress_go_ahead:  %02hhx\n", (unsigned char)ip->supress_go_ahead);
    fprintf(stderr, "  .text_end:          %hd (%p)\n", ip->text_end, ip->text+ip->text_end);
    fprintf(stderr, "  .command_start:     %hd (%p)\n", ip->command_start, ip->text+ip->command_start);
    fprintf(stderr, "  .command_end:       %hd (%p)\n", ip->command_end, ip->text+ip->command_end);
    fprintf(stderr, "  .tn_start:          %hd (%p)\n", ip->tn_start, ip->text+ip->tn_start);
    fprintf(stderr, "  .tn_end:            %hd (%p)\n", ip->tn_end, ip->text+ip->tn_end);
    fprintf(stderr, "  .chars_ready:       %"PRId32"\n",ip->chars_ready);
    fprintf(stderr, "  .snoop_on:          %p", ip->snoop_on);
      if (ip->snoop_on && ip->snoop_on->ob) fprintf(stderr, " (%s)", get_txt(ip->snoop_on->ob->name));
      putc('\n', stderr);
    fprintf(stderr, "  .snoop_by:          %p", ip->snoop_by);
      if (ip->snoop_by) fprintf(stderr, " (%s)", get_txt(ip->snoop_by->name));
      putc('\n', stderr);
    fprintf(stderr, "  .last_time:         %"PRIdMPINT"\n", ip->last_time);
    fprintf(stderr, "  .numCmds:           %ld\n", ip->numCmds);
    fprintf(stderr, "  .maxNumCmds:        %ld\n", ip->maxNumCmds);
    fprintf(stderr, "  .trace_level:       %d\n", ip->trace_level);
    fprintf(stderr, "  .trace_prefix:      %p", ip->trace_prefix);
      if (ip->trace_prefix) fprintf(stderr, " '%s'", get_txt(ip->trace_prefix));
      putc('\n', stderr);
    fprintf(stderr, "  .message_length:    %d (%p)\n", ip->message_length, ip->message_buf+ip->message_length);
    fprintf(stderr, "  .next_for_flush:    %p", ip->next_player_for_flush);
      if (ip->next_player_for_flush) fprintf(stderr, " (%s)", get_txt(ip->next_player_for_flush->name));
      putc('\n', stderr);
    fprintf(stderr, "  .prev_for_flush:    %p", ip->previous_player_for_flush);
      if (ip->previous_player_for_flush) fprintf(stderr, " (%s)", get_txt(ip->previous_player_for_flush->name));
      putc('\n', stderr);
    fprintf(stderr, "  .access_class:      %ld\n", ip->access_class);
    fprintf(stderr, "  .charset:          ");
      dump_bytes(&(ip->charset), sizeof(ip->charset), 21);
    fprintf(stderr, "  .combine_cset:     ");
      dump_bytes(&(ip->combine_cset), sizeof(ip->combine_cset), 21);
    fprintf(stderr, "  .quote_iac:         %02hhx\n", (unsigned char)ip->quote_iac);
    fprintf(stderr, "  .catch_tell_activ:  %02hhx\n", (unsigned char)ip->catch_tell_activ);
    fprintf(stderr, "  .gobble_char:       %02hhx\n", (unsigned char)ip->gobble_char);
    fprintf(stderr, "  .ts_data:           %02hhx\n", (unsigned char)ip->ts_data);
    fprintf(stderr, "  .text:             ");
      dump_bytes(&(ip->text), sizeof(ip->text), 21);
    fprintf(stderr, "  .message_buf:      ");
      dump_bytes(&(ip->message_buf), sizeof(ip->message_buf), 21);
    fprintf(stderr, "------\n");

    /* Disconnect the user */
    comm_send_buf(msg, strlen(msg), ip);
    remove_interactive(ip->ob, MY_TRUE);

    /* Unset mutex */
    in_fatal = MY_FALSE;
} /* comm_fatal() */

/*-------------------------------------------------------------------------*/
static void
set_socket_nonblocking (SOCKET_T new_socket)

/* Set the <new_socket> into non-blocking mode.
 * Abort on error.
 */

{
    int tmp;

    tmp = 1;

# ifdef USE_IOCTL_FIONBIO
    if (socket_ioctl(new_socket, FIONBIO, &tmp) == -1) {
        perror("ioctl socket FIONBIO");
        abort();
    }
# else /* !USE_IOCTL_FIONBIO */
# ifdef USE_FCNTL_O_NDELAY
    if (fcntl(new_socket, F_SETFL, O_NDELAY) == -1) {
# else
    if (fcntl(new_socket, F_SETFL, FNDELAY) == -1) {
# endif
        perror("fcntl socket FNDELAY");
        abort();
    }
# endif /* !USE_IOCTL_FIONBIO */

} /* set_socket_nonblocking() */

/*-------------------------------------------------------------------------*/
static void
set_close_on_exec (SOCKET_T new_socket)

/* Set that <new_socket> is closed when the driver performs an exec()
 * (i.e. when starting the ERQ).
 * Failure is acceptable as this is just a nicety.
 */

{
#ifdef HAVE_FCNTL
    fcntl(new_socket, F_SETFD, 1L);
#endif
} /* set_close_on_exec() */

/*-------------------------------------------------------------------------*/
static void
set_socket_own (SOCKET_T new_socket)

/* Enable OOB communication on <new_socket>: the driver is set to
 * receive SIGIO and SIGURG signals, and OOBINLINE is enabled.
 * Failure is acceptable as both facilities are not available everywhere.
 */

{
#if defined(F_SETOWN) && defined(USE_FCNTL_SETOWN)
    if (0 > fcntl(new_socket, F_SETOWN, getpid()))
    {
        perror("fcntl SETOWN");
    }
#endif
#if defined(SO_OOBINLINE) && defined(USE_OOBINLINE)
    {
        int on = 1;
        if (0 > setsockopt(new_socket, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof on))
        {
            perror("setsockopt SO_OOBINLINE");
        }
    }
#endif
    new_socket = 0; /* Prevent 'not used' warning */
} /* set_socket_own() */

/*-------------------------------------------------------------------------*/
void
initialize_host_name (const char *hname)

/* This function is called at an early stage of the driver startup in order
 * to initialise the global host_name with a useful value (specifically so
 * that we can open the debug.log file).
 * If <hname> is given, the hostname is parsed from the string, otherwise it
 * is queried from the system.
 *
 * The value set in this function will later be overwritten by the
 * call to initialize_host_ip_number().
 *
 * exit() on failure.
 */

{
    char *domain;

    /* Get the (possibly qualified) hostname */
    if (hname != NULL)
    {
        if (strlen(hname) > MAXHOSTNAMELEN)
        {
            fprintf(stderr, "%s Given hostname '%s' too long.\n"
                          , time_stamp(), hname);
            exit(1);
        }
        else
            strcpy(host_name, hname);
    }
    else
    {
        if (gethostname(host_name, sizeof host_name) == -1) {
            herror("gethostname");
            exit(1);
        }
    }

    /* Cut off the domain name part of the hostname, if any.
     */
    domain = strchr(host_name, '.');
    if (domain)
        *domain = '\0';
} /* initialize_host_name() */

/*-------------------------------------------------------------------------*/
void
initialize_host_ip_number (const char *hname, const char * haddr)

/* Initialise the globals host_ip_number and host_ip_addr_template.
 * If <hname> or <haddr> are given, the hostname/hostaddr are parsed
 * from the strings, otherwise they are queried from the system.
 *
 * Open the UDP port if requested so that it can be used in inaugurate_master().
 * exit() on failure.
 */

{
    char *domain;
    length_t tmp;

    /* Get the (possibly qualified) hostname */
    if (hname != NULL)
    {
        if (strlen(hname) > MAXHOSTNAMELEN)
        {
            fprintf(stderr, "%s Given hostname '%s' too long.\n"
                          , time_stamp(), hname);
            exit(1);
        }
        else
            strcpy(host_name, hname);
    }
    else
    {
        if (gethostname(host_name, sizeof host_name) == -1) {
            herror("gethostname");
            exit(1);
        }
    }

    /* Get the host address */
    memset(&host_ip_addr_template, 0, sizeof host_ip_addr_template);
    if (haddr != NULL)
    {
#ifndef USE_IPV6
        host_ip_number.s_addr = inet_addr(haddr);
        host_ip_addr_template.sin_family = AF_INET;
        host_ip_addr_template.sin_addr = host_ip_number;
#else
        host_ip_number = inet6_addr(haddr);
        host_ip_addr_template.sin_family = AF_INET6;
        host_ip_addr_template.sin_addr = host_ip_number;
#endif

        /* Find the domain part of the hostname */
        domain = strchr(host_name, '.');
    }
    else
    {
        struct hostent *hp;

        hp = gethostbyname(host_name);
        if (!hp) {
            fprintf(stderr, "%s gethostbyname: unknown host '%s'.\n"
                          , time_stamp(), host_name);
            exit(1);
        }
        memcpy(&host_ip_addr_template.sin_addr, hp->h_addr, (size_t)hp->h_length);
        host_ip_addr_template.sin_family = (unsigned short)hp->h_addrtype;
        host_ip_number = host_ip_addr_template.sin_addr;

        /* Now set the template to the proper _ANY value */
        memset(&host_ip_addr_template.sin_addr, 0, sizeof(host_ip_addr_template.sin_addr));
#ifndef USE_IPV6
        host_ip_addr_template.sin_addr.s_addr = INADDR_ANY;
        host_ip_addr_template.sin_family = AF_INET;
#else
        host_ip_addr_template.sin_addr = in6addr_any;
        host_ip_addr_template.sin_family = AF_INET6;
#endif

        /* Find the domain part of the hostname */
        if (hname == NULL)
            domain = strchr(hp->h_name, '.');
        else
            domain = strchr(host_name, '.');
    }

#ifndef USE_IPV6
    printf("%s Hostname '%s' address '%s'\n"
          , time_stamp(), host_name, inet_ntoa(host_ip_number));
    debug_message("%s Hostname '%s' address '%s'\n"
                 , time_stamp(), host_name, inet_ntoa(host_ip_number));
#else
    printf("%s Hostname '%s' address '%s'\n"
          , time_stamp(), host_name, inet6_ntoa(host_ip_number));
    debug_message("%s Hostname '%s' address '%s'\n"
                 , time_stamp(), host_name, inet6_ntoa(host_ip_number));
#endif

    /* Put the domain name part of the hostname into domain_name, then
     * strip it off the host_name[] (as only query_host_name() is going
     * to need it).
     * Note that domain might not point into host_name[] here, so we
     * can't just stomp '\0' in there.
     */
    if (domain)
    {
        domain_name = strdup(domain+1);
    }
    else
        domain_name = strdup("unknown");

    domain = strchr(host_name, '.');
    if (domain)
        *domain = '\0';

    /* Initialize udp at an early stage so that the master object can use
     * it in inaugurate_master() , and the port number is known.
     */
    if (udp_port != -1)
    {
        struct sockaddr_in host_ip_addr;

        memcpy(&host_ip_addr, &host_ip_addr_template, sizeof(host_ip_addr));

        host_ip_addr.sin_port = htons((u_short)udp_port);
        debug_message("%s UDP recv-socket requested for port: %d\n"
                     , time_stamp(), udp_port);
        udp_s = socket(host_ip_addr.sin_family, SOCK_DGRAM, 0);
        if (udp_s == -1)
        {
            perror("socket(udp_socket)");
            exit(1);
        }
        tmp = 1;
        if (setsockopt (udp_s, SOL_SOCKET, SO_REUSEADDR,
                        (char *) &tmp, sizeof (tmp)) < 0)
        {
            perror ("setsockopt(udp_s, SO_REUSEADDR)");
            exit(1);
        }

        /* Bind the UDP socket to an address.
         * First, try the given port number, if that one is in use
         * already, let bind() select one. If that one is in use, too,
         * close the socket again and pretend that we never had one.
         * Other errors abort the driver.
         */
        for(;;) {
            if (-1 == bind(udp_s, (struct sockaddr *)&host_ip_addr
                          , sizeof(host_ip_addr)))
            {
                if (errno == EADDRINUSE) {
                    fprintf(stderr, "%s UDP port %d already bound!\n"
                                  , time_stamp(), udp_port);
                    debug_message("%s UDP port %d already bound!\n"
                                  , time_stamp(), udp_port);
                    if (host_ip_addr.sin_port) {
                        host_ip_addr.sin_port = 0;
                        continue;
                    }
                    close(udp_s);
                    udp_s = -1;
                } else {
                    perror("udp-bind");
                    exit(1);
                }
            }
            break;
        }
    }

    /* If we got the UDP socket, get query it's real address and
     * initialise it.
     */
    if (udp_s >= 0) {
        struct sockaddr_in host_ip_addr;

        tmp = sizeof(host_ip_addr);
        if (!getsockname(udp_s, (struct sockaddr *)&host_ip_addr, &tmp))
        {
            int oldport = udp_port;

            udp_port = ntohs(host_ip_addr.sin_port);
            if (oldport != udp_port)
                debug_message("%s UDP recv-socket on port: %d\n"
                             , time_stamp(), udp_port);
        }
        set_socket_nonblocking(udp_s);
        set_close_on_exec(udp_s);
        if (socket_number(udp_s) >= min_nfds)
            min_nfds = socket_number(udp_s)+1;
    }

} /* initialize_host_ip_number() */


/*-------------------------------------------------------------------------*/
static void
ignore_handler (int signo)

/* Signal handler for ignored signals: it just reinitializes the signal
 * handler for this signal. It is used for OS where a signal(,SIG_IGN)
 * is implemented with a one-shot handler (e.g. Linux).
 */

{
#ifdef DEBUG
    if (signo != SIGPIPE) /* the only ignored signal so far */
    {
        fprintf(stderr, "%s Error: OS passes signo %d instead of SIGPIPE (%d) to handler.\n", time_stamp(), signo, SIGPIPE);
        signo = SIGPIPE;
    }
#endif
    signal(signo, (RETSIGTYPE(*)(int))ignore_handler);
}

/*-------------------------------------------------------------------------*/
static void
urgent_data_handler (int signo)

/* Signal handler for SIGURG/SIGIO: set the urgent_data flag and
 * note the time.
 */

{
    if (d_flag)
        write(2, "received urgent data\n", 21);
    urgent_data = MY_TRUE;
    urgent_data_time = current_time;
    signal(signo, (RETSIGTYPE(*)(int))urgent_data_handler);
}

/*-------------------------------------------------------------------------*/
void
prepare_ipc(void)

/* Open all login sockets on driver startup, exit() on a failure.
 */

{
    length_t tmp;
    int i;

#ifdef ERQ_DEMON
    /* Initialize the IP name lookup table */
    memset(iptable, 0, sizeof(iptable));
#endif

    for (i = 0; i < MAX_OUTCONN; i++)
        outconn[i].status = ocNotUsed;

    /* Initialize the telnet machine unless mudlib_telopts() already
     * did that.
     */
    if (!telopts_do[0])
      init_telopts();

    /* Loop over all given port numbers.
     * Remember: positive number are actual port numbers to be opened,
     * negative numbers are the fd numbers of already existing sockets.
     */
    for (i = 0; i < numports; i++)
    {
        struct sockaddr_in host_ip_addr;

        memcpy(&host_ip_addr, &host_ip_addr_template, sizeof(host_ip_addr));

        if (port_numbers[i] > 0)
        {
            /* Real port number */

            host_ip_addr.sin_port = htons((u_short)port_numbers[i]);
            sos[i] = socket(host_ip_addr.sin_family, SOCK_STREAM, 0);
            if ((int)sos[i] == -1) {
                perror("socket");
                exit(1);
            }
            tmp = 1;
            if (setsockopt(sos[i], SOL_SOCKET, SO_REUSEADDR
                          , (char *) &tmp, sizeof (tmp)) < 0) {
                perror ("setsockopt");
                exit (1);
            }
            if (bind(sos[i], (struct sockaddr *)&host_ip_addr, sizeof host_ip_addr) == -1) {
                if (errno == EADDRINUSE) {
                    fprintf(stderr, "%s Port %d already bound!\n"
                                  , time_stamp(), port_numbers[i]);
                    debug_message("%s Port %d already bound!\n"
                                 , time_stamp(), port_numbers[i]);
                    exit(errno);
                } else {
                    perror("bind");
                    exit(1);
                }
            }
        }
        else {

            /* Existing socket */

            sos[i] = -port_numbers[i];
            tmp = sizeof(host_ip_addr);
            if (!getsockname(sos[i], (struct sockaddr *)&host_ip_addr, &tmp))
                port_numbers[i] = ntohs(host_ip_addr.sin_port);
        }

        /* Initialise the socket */
        if (listen(sos[i], 5) == -1) {
            perror("listen");
            exit(1);
        }
        set_socket_nonblocking(sos[i]);
        set_close_on_exec(sos[i]);

        if (socket_number(sos[i]) >= min_nfds)
            min_nfds = socket_number(sos[i])+1;
    } /* for(i = 0..numports) */

    /* We handle SIGPIPEs ourself */
#if defined(__linux__)
    signal(SIGPIPE, (RETSIGTYPE(*)(int))ignore_handler);
#else
    signal(SIGPIPE, SIG_IGN);
#endif

#if defined(SIGURG)
    signal(SIGURG, (RETSIGTYPE(*)(int))urgent_data_handler);
#endif
#if defined(SIGIO)
    signal(SIGIO, (RETSIGTYPE(*)(int))urgent_data_handler);
#endif
} /* prepare_ipc() */

/*-------------------------------------------------------------------------*/
void
ipc_remove (void)

/* Called when the driver is shutting down, this function closes all
 * open sockets.
 */

{
    int i;

    printf("%s Shutting down ipc...\n", time_stamp());
    for (i = 0; i < numports; i++)
        socket_close(sos[i]);

    if (udp_s >= 0)
        socket_close(udp_s);

#ifdef ERQ_DEMON
    shutdown_erq_demon();
#endif

} /* ipc_remove() */

/*-------------------------------------------------------------------------*/
static INLINE ssize_t
comm_send_buf (char *msg, size_t size, interactive_t *ip)

/* Low level send routine, just tries to send the data without buffering. 
   A return value -1 indicates a total failure (i.e. the connection should
   be dropped), otherwise the amount of data sent (even if it's 0).
*/
{
    int     retries;   /* Number of retries left when sending data */
    ssize_t n;         /* Bytes that have been sent */

    for (retries = 6;;)
    {
#ifdef USE_TLS
        if ((n = (ip->tls_status == TLS_INACTIVE ? 
                  (int)socket_write(ip->socket, msg, size):
                  (int)tls_write(ip, msg, size))) != -1)
#else
        if ((n = (int)socket_write(ip->socket, msg, size)) != -1)
#endif
        {
            break;
        }

        switch (errno)
        {

        case EINTR:
            if (--retries)
                continue;
            n = 0;
            break;

        case EWOULDBLOCK:
            n = 0;
            break;

        case EMSGSIZE:
            fprintf(stderr, "%s comm: write EMSGSIZE.\n", time_stamp());
            break;

        case EINVAL:
            fprintf(stderr, "%s comm: write EINVAL.\n", time_stamp());
            break;

        case ENETUNREACH:
            fprintf(stderr, "%s comm: write ENETUNREACH.\n", time_stamp());
            break;

        case EHOSTUNREACH:
            fprintf(stderr, "%s comm: write EHOSTUNREACH.\n", time_stamp());
            break;

        case EPIPE:
            fprintf(stderr, "%s comm: write EPIPE detected\n", time_stamp());
            break;

        case ECONNRESET:
            fprintf(stderr, "%s comm: write ECONNRESET detected\n", time_stamp());
            break;

        default:
            {
                int e = errno;
                fprintf(stderr, "%s comm: write: unknown errno %d (%s)\n"
                                  , time_stamp(), e, strerror(e));
            }
        }

        if (n == 0)
            break;

        ip->do_close = FLAG_DO_CLOSE;
        return -1;

    } /* for (retries) */

    /* EWOULDBLOCK and EINTR are not real failures. */
    if (n == -1)
        return 0;

#ifdef COMM_STAT
    inet_packets++;
    inet_volume += n;
#endif

    return n;
} /* comm_send_buf() */

/*-------------------------------------------------------------------------*/
Bool
comm_socket_write (char *msg, size_t size, interactive_t *ip, write_buffer_flag_t flags)

/* Stand in for socket_write(): take the data to be written, compress and
 * encrypt them if needed and send them to <ip>. If no data can be send
 * right now append them to the buffer. Returns false if no more data
 * is accepted at this time (messages are discarded or the connection
 * was dropped).
 */

{
    struct write_buffer_s *b;
    char *buf;
    size_t length;

    if (size == 0)
        return MY_TRUE;

#ifdef USE_MCCP
    /* We cannot discard already compressed packets,
     * because the zlib will generate a checksum
     * over all bytes. So we have to check before
     * compressing the message whether we can send 
     * or put them in the write buffer.
     *
     * To provide a consistent behavior we do this
     * also for uncompressed connections.
     */
#endif

    if (!(flags & WB_NONDISCARDABLE) && ip->write_first)
    {
        p_int max_size;

        max_size = ip->write_max_size;
        if (max_size == -2)
            max_size = write_buffer_max_size;

        if (max_size >= 0 && ip->write_size >= (p_uint) max_size)
        {
            /* Buffer overflow. */
            if (ip->msg_discarded != DM_NONE)
                return MY_FALSE; /* Message will be or was sent. */

            /* Notify the master about it. */
            ip->msg_discarded = DM_SEND_INFO;
            add_flush_entry(ip);

            return MY_FALSE;
        }
    }

#ifdef USE_MCCP
    if (ip->out_compress)
    {
        int status;

        ip->out_compress->next_in = (unsigned char *) msg;
        ip->out_compress->avail_in = size;

        ip->out_compress->next_out = ip->out_compress_buf;
        ip->out_compress->avail_out = COMPRESS_BUF_SIZE;

        status = deflate(ip->out_compress, Z_SYNC_FLUSH);

        if (status != Z_OK)
        {
            fprintf(stderr, "%s comm: MCCP compression error: %d\n"
                          , time_stamp(), status);
            return MY_FALSE;
        }

        /* ok.. perhaps i should take care that all data in message_buf
         * is compressed, but i guess there is no chance that 1024 byte
         * compressed won't fit into the 8192 byte buffer
         */

        length = ip->out_compress->next_out - ip->out_compress_buf;
        buf = (char *) ip->out_compress_buf;
    }
    else
#endif /* USE_MCCP */
    {
        buf = msg;
        length = size;
    }

    /* now sending the buffer... */
    if (ip->write_first == NULL)
    {
        /* Try writing to the socket first. */
        ssize_t n = comm_send_buf(buf, length, ip);

        if (n == -1)
            return MY_FALSE;
        else if (n == length)
        {
            /* We're done. */
            return MY_TRUE;
        }
        else if (n > 0)
        {
            buf += n;
            length -= n;
        }
    }

    /* We have to enqueue the message. */

    b = xalloc(sizeof(struct write_buffer_s) + length - 1);
    if (!b)
        outofmem(sizeof(struct write_buffer_s) + length - 1, "comm_socket_write()");

    b->length = length;
    b->pos = 0;
    b->flags = flags;
    b->next = NULL;
    memcpy(b->buffer, buf, length);

     /* Chain in the new buffer */
    if (ip->write_first)
        ip->write_last = ip->write_last->next = b;
    else
        ip->write_last = ip->write_first = b;

    ip->write_size += length;
    ip->msg_discarded = DM_NONE;

    return MY_TRUE;
} /* comm_socket_write() */

/*-------------------------------------------------------------------------*/
static void
comm_write_pending (interactive_t * ip)

/* Send as much from the write buffer of <ip> as possible.
 */

{
    while (ip->write_first != NULL)
    {
        struct write_buffer_s *buf;
        ssize_t n;

        buf = ip->write_first;
        n = comm_send_buf(buf->buffer + buf->pos, buf->length - buf->pos, ip);

        if (n == -1)
            return;

        buf->pos += n;
        if (buf->pos < buf->length)
            return;

        ip->write_first = buf->next;
        ip->write_size -= buf->length;
        xfree(buf);
    }
} /* comm_write_pending() */

/*-------------------------------------------------------------------------*/
static void
add_discarded_message (interactive_t *ip)

/* Calls the H_MSG_DISCARDED driver hook and adds the
 * message to the write buffer of <ip>. <ip> is removed
 * from the list of dirty interactives if it's clean afterwards.
 */
{
    string_t *discarded_msg;

    if (driver_hook[H_MSG_DISCARDED].type == T_CLOSURE)
    {
        if (driver_hook[H_MSG_DISCARDED].x.closure_type == CLOSURE_LAMBDA)
        {
            free_object(driver_hook[H_MSG_DISCARDED].u.lambda->ob, "add_discarded_message");
            driver_hook[H_MSG_DISCARDED].u.lambda->ob = ref_object(ip->ob, "add_discarded_message");
        }

        push_ref_valid_object(inter_sp, ip->ob, "add_discarded_message");

        call_lambda(&driver_hook[H_MSG_DISCARDED], 1);

        if (inter_sp->type == T_STRING)
        {
            /* The new discarded_msg. Adopt the reference. */
            discarded_msg = inter_sp->u.str;

            inter_sp->type = T_INVALID;
            inter_sp--;
        }
        else
        {
            pop_stack();
            discarded_msg = NULL;
        }
    }
    else if (driver_hook[H_MSG_DISCARDED].type == T_STRING)
    {
        discarded_msg = ref_mstring(driver_hook[H_MSG_DISCARDED].u.str);
    }
    else
    {
        discarded_msg = ref_mstring(STR_DISCARDED_MSG);
    }

    if (discarded_msg)
    {
        /* Append it to the write buffer. */
        comm_socket_write(get_txt(discarded_msg), mstrsize(discarded_msg)
                        , ip, WB_NONDISCARDABLE);
        free_mstring(discarded_msg);
    }

    ip->msg_discarded = DM_INFO_WAS_SENT;

    if (!ip->message_length)
        remove_flush_entry(ip);
}

/*-------------------------------------------------------------------------*/
void
add_message (const char *fmt, ...)

/* Send a message to the current command_giver. The message is composed
 * from the <fmt> string and the following arguments using the normal
 * printf() semantics.
 *
 * The format string "%s" is special in that it bypasses the normal
 * printf() handling and uses the given char* argument directly as data
 * source, allowing to send strings of arbitrary length.
 * The format string FMT_STRING accepts a string_t argument of arbitrary length.
 * The format string FMT_BINARY accepts a char* as text argument, followed
 * by a size_t with the string length. The text may contain any character.
 *
 * All other format strings compose the message to send in a local buffer
 * and are therefore subject to a length restriction.
 *
 * This function also does the telnet, snooping, and shadow handling.
 * If an interactive player is shadowed, object.c::shadow_catch_message()
 * is called to give the shadows the opportunity to intercept the message.
 *
 * All messages are accumulated in interactive.message_buf, which is
 * flushed when it is full. This flush can be forced by passing the
 * special 'string' message_flush (which is actually just a NULL pointer)
 * as <fmt> string to this function.
 *
 * Messages which can't be send (e.g. because the command_giver was
 * destructed or disconnected) are printed on stdout, preceeded by ']'.
 *
 * If an error other than EINTR occured while sending the data to
 * the network, the message is discarded and the socket is marked
 * for disconnection.
 *
 * Note that add_message() might be called recursively.
 */

{
    char  buff[MAX_TEXT + MAX_TEXT/2];
      /* Composition buffer for the final message.
       * We hope that it's big enough, but to be sure the code will
       * check for buffer overruns.
       * Message is composed starting from buff[1] on, buff[0] is
       * set to '%' for easier snooper-message generation.
       */
    int   length;
    int   min_length;
      /* When accumulating data in ip.message_buf[], this is the
       * threshold over which the buffer will be written to the
       * socket.
       * TODO: Actually, it is used just as a flag for flush/non-flush.
       */
    string_t *srcstr;
      /* If not NULL, this string was passed in to be printed.
       * source will point to the first real character of it.
       */
    int   old_message_length;  /* accumulated message length so far */
    char *source;              /* Pointer to the final message to add */
    size_t srclen;             /* Length of the message in source/srcstr */
    char *end;                 /* One char past the end of .message_buf[] */
    char *dest;                /* First free char in .message_buf[] */
    va_list va;
    interactive_t *ip;       /* The interactive user */
    object_t      *snooper;  /* Snooper of <ip> */

    source = NULL;
    srcstr = NULL;
    srclen = 0;
    length = 0;

    va_start(va, fmt);

    /* Test if the command_giver is a real, living, undestructed user,
     * and not disconnected, closing or actually a new ERQ demon.
     * If the command_giver fails the test, the message is printed
     * to stdout and the function returns.
     */
    if ( command_giver == NULL
     || (   command_giver->flags & O_DESTRUCTED
         && fmt != message_flush )
     || !(O_SET_INTERACTIVE(ip, command_giver))
     || (ip->do_close && fmt != message_flush && !sending_telnet_command)
       )
    {
        putchar(']');
        if ( fmt == FMT_STRING )
        {
            /* Make sure to print embedded '\0' characters as well */

            size_t len;

            srcstr = va_arg(va, string_t *);
            source = get_txt(srcstr);
            srclen = mstrsize(srcstr);

            for ( len = 0; len < srclen; )
            {
                if (*source == '\0')
                {
                    putc('\0', stdout);
                    source++;
                    len++;
                }
                else
                {
                    size_t slen;

                    fputs(source, stdout);
                    slen = strlen(source);
                    source += slen;
                    len += slen;
                }
            }
        }
        else if ( fmt != message_flush )
        {
            vprintf(fmt, va);
        }
        fflush(stdout);
        va_end(va);
        return;
    }

    old_message_length = ip->message_length;

    /* --- Compose the final message --- */

    /* Create the final message and handle snoopers and shadows.
     */

    min_length = MAX_SOCKET_PACKET_SIZE-1;
      /* Allow some wiggle room for source characters like NL which
       * expand into two characters.
       */

    if ( fmt == message_flush )
    {
        /* Just flush, nothing to add */

        min_length = 1;
        source = "";
        srclen = 0;
        srcstr = NULL;
    }
    else /* add the message */
    {
#ifdef COMM_STAT
        add_message_calls++;
#endif

        /* Compose the final message in buff[] (while checking for overruns)
         * and point source to it.
         * Recognize the special formats '%s', FMT_STRING and FMT_BINARY
         * to bypass buff[] for messages of arbitrary length and content.
         */

        if (fmt == FMT_STRING)
        {
            srcstr = va_arg(va, string_t *);
            va_end(va);

            source = get_txt(srcstr);
            srclen = mstrsize(srcstr);
        }
        else if (fmt == FMT_BINARY)
        {
            source = va_arg(va, char *);
            srclen = va_arg(va, size_t);
            va_end(va);
            srcstr = NULL;
        }
        else if (fmt[0] == '%' && fmt[1] == 's' && !fmt[2])
        {
            source = va_arg(va, char *);
            va_end(va);
            srclen = strlen(source);
            srcstr = NULL;
        }
        else
        {
            size_t len;
            len = vsnprintf(buff+1, sizeof(buff)-1, fmt,va);
            va_end(va);
            /* old sprintf() implementations returned -1 if the output was
             * truncated. Since size_t is an unsigned type, the check for 
             * len == -1 is implicitly included by >= sizeof(...)-1, because
             * -1 will be wrapped to SIZE_T_MAX which is the maximum sizeof()
             * can return and can never be valid as return value here. */
            if (len >= sizeof(buff)-1)
            {
                char err[] = "\n*** Message truncated ***\n";
                debug_message("%s Message too long (Length: %zu): '%.200s...'\n"
                             , time_stamp(), len, buff);
                (void)strcpy(buff+(sizeof(buff)-sizeof(err)), err);
            }
            source = buff+1;
            srclen = strlen(buff+1);
            srcstr = NULL;
        }

        /* If we're not sending a telnet command with this message,
         * pass on the new data to any snooper and/or shadow
         */

        if (!sending_telnet_command)
        {

            /* If there's a shadow successfully handling the
             * message, return.
             * This may cause a recursive call to add_message()!.
             */

            if (shadow_catch_message(command_giver, source))
            {
                return;
            }

            /* If there's a snooper, send it the new message prepended
             * with a '%'.
             * For interactive snoopers this means a recursion with
             * the command_giver set to the snooper, for non-interactive
             * snoopers it's a simple call to tell_npc(), with an
             * adaption of the global trace_level to this users trace
             * settings.
             */

            if ( NULL != (snooper = ip->snoop_by)
             && !(snooper->flags & O_DESTRUCTED))
            {
                buff[0] = '%';
                if (O_IS_INTERACTIVE(snooper))
                {
                    object_t *save;

                    save = command_giver;
                    command_giver = snooper;
                    if (source != buff+1)
                    {
                        if (srcstr != NULL)
                        {
                            add_message("%s", "%");
                            add_message(FMT_STRING, srcstr);
                        }
                        else if (srclen >= sizeof buff - 1)
                        {
                            add_message("%s", "%");
                            add_message("%s", source);
                        }
                        else
                        {
                            strcpy(buff+1, source);
                            add_message("%s", buff);
                        }
                    }
                    else
                    {
                        add_message("%s", buff);
                    }
                    command_giver = save;
                }
                else
                {
                    trace_level |= ip->trace_level;
                    if (source != buff+1)
                    {
                        if (srcstr != NULL)
                        {
                            tell_npc(snooper, STR_PERCENT);
                            tell_npc(snooper, srcstr);
                        }
                        else if (srclen >= sizeof buff - 1)
                        {
                            tell_npc(snooper, STR_PERCENT);
                            tell_npc_str(snooper, source);
                        }
                        else
                        {
                            strcpy(buff+1, source);
                            tell_npc_str(snooper, buff);
                        }
                    } else
                    {
                        tell_npc_str(snooper, buff);
                    }
                }
            } /* if (snooper) */
        } /* if (!sending_telnet_command */
    } /* if (flush or not) */

#ifdef DEBUG
    if (d_flag > 1)
        debug_message("%s [%s (%zu)]: %s"
                     , time_stamp(), get_txt(command_giver->name)
                     , srclen, source);
#endif

    /* --- Send the final message --- */

    /* Append the final message to the .message_buf[], taking
     * care of all the necessary charset and telnet translations.
     */

    dest = &ip->message_buf[old_message_length];
    end  = &ip->message_buf[sizeof ip->message_buf];

    /* If there's any recursive call, let it begin
     * at the start.
     */
    ip->message_length = 0;
    
    /* This loop advances source until it reaches the end.
     * Every character encountered is copied, translated or fed
     * into the telnet machine.
     */

#ifdef DEBUG_TELNET
if (sending_telnet_command)
{
    char *cp;
    long left;
    printf("%s TDEBUG: '%s' Sending telnet (%zu bytes): "
          , time_stamp(), get_txt(ip->ob->name), strlen(source));
    for (cp = source, left = srclen; left > 0; cp++, left--)
        printf(" %02x", (unsigned char)*cp);
    printf("\n");
}
#endif

    do /* while (srclen != 0) */
    {
        ptrdiff_t chunk;  /* Current size of data in .message_buf[] */
        char   c;         /* Currently processed character */

        while (srclen != 0 && dest != end)
        {
            c = *source++;
            srclen--;

            /* Process the character:
             *  - copy it if the corresponding .charset bit is set,
             *    or if it's part of a telnet command.
             *  - translate a '\n' into '\r\n'
             *  - double an IAC if quote_iac is active.
             *  - stop this loop if the source is exhausted or
             *    if the buffer is full.
             */
            if (sending_telnet_command)
            {
                *dest++ = c;
            }
            else if (!(ip->charset[(c&0xff)>>3] & 1<<(c&7)))
                NOOP;
            else if (c == '\n')
            {
                if (dest + 1 == end)
                {
                    /* Not enough space in the buffer - revisit this char
                     * on the next time around */
                    source--;
                    srclen++;
                    break;
                }

                /* Insert CR before NL */
                *dest++ = '\r';
                *dest++ = c;
            }
            else if ( (unsigned char)c == IAC && ip->quote_iac)
            {
                if (dest + 1 == end)
                {
                    /* Not enough space in the buffer - revisit this char
                     * on the next time around */
                    source--;
                    srclen++;
                    break;
                }

                *dest++ = c;
                *dest++ = c;
            }
            else
            {
                *dest++ = c;
            }

            /* Other characters are silently dropped */
        } /* while() */

        /* Check how much data there is in .message_buf[].
         * If it is enough, send it, else terminate the outer loop
         * (because *source must be exhausted for this to happen).
         */
        chunk = dest - ip->message_buf;
        if (chunk < min_length)
        {
            break;
        }

        /* Write .message_buf[] to the network. */

        if (!comm_socket_write(ip->message_buf, (size_t)chunk, ip, 0))
        {
            if (old_message_length)
                clear_message_buf(ip);
            return;
        }

        /* Continue with the processing of source */
        dest = &ip->message_buf[0];
    } while (srclen != 0);

    /* --- Final touches --- */

    ip->message_length = length = dest - ip->message_buf;

    /* Update the list of interactives with pending data */

    if ( length && !old_message_length )
    {
        /* Buffer became 'dirty': add this interactive to the list.
         */
        add_flush_entry(ip);
    }
    if ( !length && old_message_length ) /* buffer has become empty */
    {
        clear_message_buf(ip);
    }
} /* add_message() */

/*-------------------------------------------------------------------------*/
static INLINE void
reset_input_buffer (interactive_t *ip)

/* When returning from CHARMODE to LINEMODE, the input buffer variables
 * need to be reset. This function takes care of it.
 * Note that there might be additional lines pending between .tn_end and
 * .text_end.
 */

{
    if (ip->command_start)
    {
        DTN(("reset input buffer: cmd_start %hd, tn_start %hd, tn_end %hd, text_end %hd\n", 
             ip->command_start, ip->tn_start, ip->tn_end, ip->text_end));
        ip->tn_start -= ip->command_start;
        ip->tn_end -= ip->command_start;
        ip->text_end -= ip->command_start;
        if (ip->tn_start < 0)
            ip->tn_start = 0;
        if (ip->tn_end < 0)
            ip->tn_end = 0;
        if (ip->text_end <= 0)
            ip->text_end = 0;
        else
        {
            move_memory( ip->text, ip->text + ip->command_start
                       , ip->text_end
                       );
        }
        if (ip->command_end)
            ip->command_end = ip->tn_end;
        ip->command_start = 0;
    }
} /* reset_input_buffer() */

/*-------------------------------------------------------------------------*/
static void
add_flush_entry (interactive_t *ip)

/* Add the given interactive <ip> to the list of 'dirty' interactives.
 * The function is safe to call for interactives already in the list.
 */

{
    if ( ip->previous_player_for_flush || first_player_for_flush == ip->ob)
        return;

    if ( NULL != (ip->next_player_for_flush = first_player_for_flush) )
    {
        O_GET_INTERACTIVE(first_player_for_flush)->
          previous_player_for_flush = ip->ob;
    }
    ip->previous_player_for_flush = NULL;
    first_player_for_flush = ip->ob;
} /* add_flush_entry() */

/*-------------------------------------------------------------------------*/
static void
remove_flush_entry (interactive_t *ip)

/* Remove the given interactive <ip> from the list of 'dirty' interactives
 * and make sure it is really clean. The function is safe to call for
 * interactives not in the list.
 */

{
    /* To make it safe for calling the function even for interactives
     * not in the flush list, we check that <ip> is either in the middle
     * or at the end of the flush list (one or both of the .previous
     * and .next pointers is !NULL), or if .previous is NULL, that it is
     * the first entry in the list.
     */

    if ( ip->previous_player_for_flush )
    {
        O_GET_INTERACTIVE(ip->previous_player_for_flush)->next_player_for_flush
          = ip->next_player_for_flush;
    }
    else if (first_player_for_flush == ip->ob)
    {
        first_player_for_flush = ip->next_player_for_flush;
    }

    if ( ip->next_player_for_flush )
    {
        O_GET_INTERACTIVE(ip->next_player_for_flush)->previous_player_for_flush
          = ip->previous_player_for_flush;
    }

    ip->previous_player_for_flush = NULL;
    ip->next_player_for_flush = NULL;
} /* remove_flush_entry() */

/*-------------------------------------------------------------------------*/
void
flush_all_player_mess (void)

/* Flush all pending data from the interactives. Usually called before
 * every input loop, after a user logged in, or after an LPC runtime
 * error was processed.
 */

{
    object_t *p, *np;
    interactive_t *ip;
    object_t *save = command_giver;

    for ( p = first_player_for_flush; p != NULL; p = np)
    {
        ip = O_GET_INTERACTIVE(p);
        np = ip->next_player_for_flush;
          /* add_message() will clobber (p)->next_player_for_flush! */
        command_giver = p;
        add_message(message_flush);

        if(ip->msg_discarded == DM_SEND_INFO)
            add_discarded_message(ip);
    }
    command_giver = save;
} /* flush_all_player_mess() */

/*-------------------------------------------------------------------------*/
static void
clear_message_buf (interactive_t *ip)

/* Clear the buffer of the given interactive <ip> and remove it from
 * the list of 'dirty' interactives if there is nothing else to do.
 * The function is safe to call for interactives not in the list.
 *
 * This function is called after an interactive sent all pending data (or
 * failing while doing so).
 */

{
    ip->message_length = 0;

    if (ip->msg_discarded != DM_SEND_INFO)
        remove_flush_entry(ip);
} /* clear_message_buf() */

/*-------------------------------------------------------------------------*/
Bool
get_message (char *buff)

/* Get a message from a user, or wait until it is time for the next
 * heartbeat/callout. You can tell this apart by the result:
 *
 *   true: a user message was received and placed into buff; the user
 *         object is set as command_giver.
 *   false: it is just time to call the heart_beat.
 *
 * In both cases, time_to_call_heart_beat is set if a heartbeat is due.
 *
 * Internally, get_message() scans the array of interactive users in
 * search for one with a complete message in its incoming buffer. If
 * an earlier select() marked the socket for the current user as pending
 * with data, this data is read into the buffer before the check for
 * a message is performed. get_message() returns for the first user found
 * with a complete message. Since get_message() keeps its own
 * status of which user was looked at last, the next call to get_message()
 * will continue the scan where it left off.
 *
 * If no user has a complete message, a call to select() waits for more
 * incoming data. If this succeeds (and no heartbeat requires an
 * immediate return), the cycle begins again. If a heart_beat is due
 * even before select() executed, the waiting time for select() is
 * set to 0 so that only the status of the sockets is recorded and
 * get_message returns (almost) immediately.
 *
 * Normally, users can give only one command per cycle. The exception
 * is when they are editing, then they can give up to ALLOWED_ED_CMDS.
 *
 * Heartbeats are detected by checking the backend variable comm_time_-
 * to_call_heart_beat, which is set by the SIGALRM handler. If it is
 * true, get_message() sets the variable time_to_call_heart_beat to
 * inform the backend and returns.
 *
 * If a heart_beat occurs during the reading and returning of player
 * commands, the comm_time_c_h_b variable is set, but not evaluated.
 * This evaluation happens only when a select() is performed (therefore
 * the second variable time_to_c_h_b). This way every user can issure
 * at least one command in one backend cycle, even if that takes longer
 * than one heart_beat time. This makes it also legal for comm_to_c_h_b
 * to be set upon entering get_message().
 *
 * For short latency, the UDP socket is checked on every call to
 * get_message(), even if a previous select() did not mark it as ready
 * (this is disabled under BeOS and Windows).
 */

{
    /* State information: */
    static fd_set readfds, writefds;
      /* List of sockets with pending data.
       * You can ignore a 'could be used uninitialized' warning.
       */
    static int NextCmdGiver = -1;
      /* Index of current user to check */
    static int CmdsGiven = 0;
      /* Number of commands the current user gave in this cycle. */

#   define StartCmdGiver  (max_player)
#   define IncCmdGiver    NextCmdGiver--

    int    i;
    interactive_t * ip = NULL;
    fd_set exceptfds;


    /* The endless loop */

    while(MY_TRUE)
    {
        struct sockaddr_in addr;
        length_t length; /* length of <addr> */
        struct timeval timeout;

        /* --- select() on the sockets and handle ERQ --- */

        /* This also removes users which connection is marked
         * as to be closed.
         */

        if (NextCmdGiver < 0)
        {
            int nfds;     /* number of fds for select() */
            int res;      /* result from select() */
            int twait;    /* wait time in seconds for select() */
            int retries;  /* retries of select() after EINTR */

            flush_all_player_mess();
            twait = comm_time_to_call_heart_beat ? 0 : 1;
              /* If the heart_beat is due, just check the state
               * of the sockets, but don't wait.
               */

            /* Set up fd-sets. */

            FD_ZERO(&readfds);
            FD_ZERO(&writefds);
            for (i = 0; i < numports; i++) {
                FD_SET(sos[i], &readfds);
            } /* for */
            nfds = min_nfds;
            for (i = max_player + 1; --i >= 0;)
            {
                ip = all_players[i];
                if (!ip)
                    continue;

                if (ip->do_close)
                {
                    ip->do_close &= FLAG_PROTO_ERQ;
                    remove_interactive(ip->ob, MY_FALSE);
                    continue;
                }

                if (ip->tn_state == TS_READY)
                {
                    /* If telnet is ready for commands, react quickly. */
                    twait = 0;
                }
                else
                {
                    FD_SET(ip->socket, &readfds);
                    if (socket_number(ip->socket) >= nfds)
                        nfds = socket_number(ip->socket)+1;
                }

                if (ip->write_first != NULL)
                {
                    /* There is something to write. */
                    FD_SET(ip->socket, &writefds);
                }

            } /* for (all players) */
#ifdef ERQ_DEMON
            if (erq_demon >= 0)
            {
                FD_SET(erq_demon, &readfds);
            }
#endif
            if (udp_s >= 0)
            {
                FD_SET(udp_s, &readfds);
            }

#ifdef USE_PGSQL
            pg_setfds(&readfds, &writefds, &nfds);
#endif

            /* select() until time is up or there is data */

            for (retries = 6;;)
            {
                check_alarm();
                timeout.tv_sec = twait;
                timeout.tv_usec = 0;
                res = socket_select(nfds, &readfds, &writefds, 0, &timeout);
                if (res == -1)
                {
                    /* BeOS <= PR2 returns errno -1 instead of EINTR :-( */
                    if (errno == EINTR || errno == -1)
                    {
                        /* We got an alarm, probably need heart_beat.
                         * But finish the select call since we already have
                         * prepared readfds.
                         */
                        if (comm_time_to_call_heart_beat)
                            twait = 0;
                        if (--retries >= 0)
                            continue;
                    }
                    else
                    {
                        perror("select");
                    }

                    /* Despite the failure, pretend select() suceeded with
                     * zero sockets to read, and process heart_beat / buffered
                     * commands.
                     */
                    FD_ZERO(&readfds);
                }
                break;
            } /* for (retries) */

            /* If we got a SIGIO/SIGURG, telnet wants to synch with us.
             */
            if (urgent_data)
            {
                DTN(("telnet wants to sync\n"));
                check_alarm();
                urgent_data = MY_FALSE;
                timeout.tv_sec = 0;
                timeout.tv_usec = 0;
                memset((char *)&exceptfds, 255, (size_t)(nfds + 7) >> 3);
                if (socket_select(nfds, 0, 0, &exceptfds, &timeout) > 0)
                {
                    for (i = max_player + 1; --i >= 0;)
                    {
                        ip = all_players[i];
                        if (!ip)
                            continue;
                        if (FD_ISSET(ip->socket, &exceptfds))
                        {
                            DTN(("ts_data = TS_SYNCH\n"));
                            ip->ts_data = TS_SYNCH;
                            switch (ip->tn_state)
                            {
                              case TS_DATA:
                              case TS_READY:
                                ip->tn_state = TS_SYNCH;
                                ip->gobble_char = '\0';
                                DTN(("tn_state = TS_SYNCH\n"));
                            }
                        }
                    } /* for (all players) */
                }
                /* Maybe the data didn't arrive yet, so try again later.
                 * But don't waste time doing it for too long.
                 */
                else if (current_time - urgent_data_time < 600)
                {
                    urgent_data = MY_TRUE;
                }
            } /* if (urgent_data) */

#ifdef USE_PGSQL
            pg_process_all();
#endif

            /* Initialise the user scan */
            CmdsGiven = 0;
            NextCmdGiver = StartCmdGiver;

#ifdef ERQ_DEMON

            /* --- Handle data from the ERQ ---
             * TODO: This should be a function on its own.
             * TODO: Define the erq messages as structs.
             */
            if (erq_demon >= 0 && FD_ISSET(erq_demon, &readfds))
            {
                mp_int l;
                mp_int msglen;  /* Length of the current erq message */
                mp_int rest;
                int32  handle;
                char  *rp;      /* Read pointer into buf_from_erq[] */

                FD_CLR(erq_demon, &readfds);

                /* Try six times to read data from the ERQ, appending
                 * it to what is already in buf_from_erq[].
                 */
                retries = 6;
                do {
                    l = socket_read(
                      erq_demon,
                      input_from_erq,
                      (size_t)(&buf_from_erq[sizeof buf_from_erq] - input_from_erq)
                    );
                } while(l < 0 && errno == EINTR && --retries >= 0);

                /* If there is no data, stop the erq, else handle it. */

                if (l <= 0)
                {
#ifdef DEBUG_ERQ
                    fprintf(stderr, "%s read %"PRIdMPINT" bytes from erq demon\n"
                                  , time_stamp(), l);
                    if (l)
                        perror("");
#endif /* DEBUG_ERQ */
                    stop_erq_demon(MY_TRUE);
                }
                else
                {
                    input_from_erq += l;
                    l = input_from_erq - &buf_from_erq[0];
                    rp = buf_from_erq;

                    /* l is the amount of data left to consider,
                     * rp points to the data to be considered next.
                     *
                     * Loop while there are messages left in the buffer.
                     */

                    for (; l >= 8 && l >= (msglen = read_32(rp))
                         ; rp += msglen, l -= msglen)
                    {
                        Bool keep_handle;

                        /* Is the message length valid?
                         * TODO: use sizeof(struct) here
                         */
                        if (msglen < 8) {
#ifdef DEBUG_ERQ
                            fprintf( stderr
                                   , "%s invalid length of message from"
                                     "erq demon: %"PRIdMPINT" bytes\n"
                                   , time_stamp(), msglen);
#endif /* DEBUG_ERQ */
                            stop_erq_demon(MY_TRUE);
                            break;
                        }

                        handle = read_32(rp+4); /* get the handle */

                        if (handle == ERQ_HANDLE_KEEP_HANDLE
                         && msglen >= 8)
                        {
                            /* _KEEP_HANDLE wrapper are used when
                             * more messages for the (real) handle
                             * are to be expected. The real message
                             * follows after the _KEEP_HANDLE.
                             */
                            handle = read_32(rp+8); /* the real handle */
                            keep_handle = MY_TRUE;
                            msglen -= 4;  /* adjust the message parameters */
                            l -= 4;
                            rp += 4;
                        }
                        else if (handle == ERQ_HANDLE_RLOOKUP)
                        {
                            /* The result of a hostname lookup. */

                            if (msglen < 13 || rp[msglen-1]) {
#ifdef DEBUG
                              if (msglen == 12) {
                                if (d_flag > 1)
                                  debug_message("%s Host lookup failed\n"
                                               , time_stamp());
                              } else {
                                debug_message("%s Bogus reverse name lookup.\n"
                                             , time_stamp());
                              }
#endif
                            } else {
                                uint32 naddr;
                                struct in_addr net_addr;

                                memcpy((char*)&naddr, rp+8, sizeof(naddr));
#ifndef USE_IPV6
                                net_addr.s_addr = naddr;
#else
                                CREATE_IPV6_MAPPED(&net_addr, naddr);
#endif
                                add_ip_entry(net_addr, rp+12);
                            }
                            continue;
                        }
#ifdef USE_IPV6
                        else if (handle == ERQ_HANDLE_RLOOKUPV6)
                        {
                            /* The result of a hostname lookup. */

                            if (msglen < 9 || rp[msglen-1]) {
#ifdef DEBUG
                                debug_message("%s Bogus reverse name lookup.\n"
                                             , time_stamp());
#else
                                NOOP;
#endif
                            } else {
                                char * space;

                                space = strchr(rp+8, ' ');

                                if (space == NULL)
                                {
                                    debug_message("%s IP6 Host lookup failed: %s\n"
                                                 , time_stamp(), rp+8);
                                }
                                else if (strlen(space+1))
                                {
                                    *space = '\0';
                                    update_ip_entry(rp+8, space+1);
                                }
                            }
                            continue;
                        }
#endif /* USE_IPV6 */
                        else
                        {
                            /* remove the callback handle after processing
                             * the message.
                             */
                            keep_handle = MY_FALSE;
                        }

                        /* We have an ERQ message for a user supplied
                         * handle - execute it (after some sanity checks).
                         */

                        rest = msglen - 8;
                        if ((uint32)handle < MAX_PENDING_ERQ
                         && pending_erq[handle].fun.type != T_INVALID
                         && (   rest <= (mp_int)max_array_size
                             || !max_array_size
                             || pending_erq[handle].string_arg
                            )
                           )
                        {
                            svalue_t *erqp = &pending_erq[handle].fun;
                            object_t *ob;
                            wiz_list_t *user;
                            int num_arg;

                            command_giver = 0;
                            current_interactive = 0;
                            ob = !CLOSURE_MALLOCED(erqp->x.closure_type)
                                 ? erqp->u.ob
                                 : erqp->u.lambda->ob;
                            if (pending_erq[handle].string_arg)
                            {
                                string_t * str;

                                str = new_n_mstring(rp + 8, rest);
                                push_string(inter_sp, str);

                                num_arg = 1;
                            }
                            else
                            {
                                unsigned char *cp;
                                vector_t *v;
                                svalue_t *svp;

                                current_object = ob;
                                v = allocate_array(rest);
                                current_object = NULL;
                                push_array(inter_sp, v);
                                push_number(inter_sp, rest);
                                cp = (unsigned char *)rp + 8;
                                for (svp = v->item; --rest >=0; svp++)
                                {
                                    svp->u.number = *cp++;
                                }

                                num_arg = 2;
                            }

                            user = ob->user;
                            if (user->last_call_out != current_time)
                            {
                                user->last_call_out = current_time;
                                CLEAR_EVAL_COST;
                            } else {
                                assigned_eval_cost = eval_cost = user->call_out_cost;
                            }
                            RESET_LIMITS;
                            secure_callback_lambda(erqp, num_arg);
                            user->call_out_cost = eval_cost;
                            if (!keep_handle || (ob->flags & O_DESTRUCTED))
                            {
                                free_svalue(erqp);
                                erqp->type = T_INVALID;
                                erqp->u.generic = (void *)free_erq;
                                free_erq = &pending_erq[handle];
                            }
                        } /* if(valid handle) */

                        /* Messages for invalid handles are no error: e.g. the
                         * object could have gone away unexpectantly before
                         * the erq had time to answer.
                         */

                    } /* for (l,rp in buf_from_erq) */

                    /* Delete the processed data from the buffer */
                    if (rp != buf_from_erq)
                    {
                        move_memory(buf_from_erq, rp, (size_t)l);
                        input_from_erq = &buf_from_erq[l];
                    }
                } /* if (read data from erq) */
            } /* if (erq socket ready) */

#endif /* ERQ_DEMON */

            /* --- Try to get a new player --- */
            for (i = 0; i < numports; i++)
            {
                if (FD_ISSET(sos[i], &readfds))
                {
                    SOCKET_T new_socket;

                    length = sizeof addr;
                    new_socket = accept(sos[i], (struct sockaddr *)&addr
                                              , &length);
                    if ((int)new_socket != -1)
                        new_player( NULL, new_socket, &addr, (size_t)length
                                  , port_numbers[i]);
                    else if ((int)new_socket == -1
                      && errno != EWOULDBLOCK && errno != EINTR
                      && errno != EAGAIN && errno != EPROTO )
                    {
                        /* EBADF would be a valid cause for an abort,
                         * same goes for ENOTSOCK, EOPNOTSUPP, EFAULT.
                         * However, don't abort() because that tends to
                         * leave Mud admins baffled (and would opens the
                         * door for DoS attacks).
                         */
                        int errorno = errno;
                        fprintf( stderr
                               , "%s comm: Can't accept on socket %d "
                                 "(port %d): %s\n"
                               , time_stamp(), sos[i], port_numbers[i]
                               , strerror(errorno)
                               );
                        debug_message("%s comm: Can't accept on socket %d "
                                      "(port %d): %s\n"
                                     , time_stamp(), sos[i], port_numbers[i]
                                     , strerror(errorno)
                                     );
                        /* TODO: Was: perror(); abort(); */
                    }
                }
            } /* for */
            /* check for alarm signal (heart beat) */
            if (comm_time_to_call_heart_beat)
            {
                time_to_call_heart_beat = MY_TRUE;
                return MY_FALSE;
            }
        } /* if (no NextCmdGiver) */

        /* See if we got any udp messages.
         * We don't test readfds so that we can accept udp messages with
         * short latency. But for the same reason, it was necessary to
         * include the descriptor number in the set to be selected on.
         * Note for BeOS and Cygwin: since making sockets non-blocking
         *   is a bit tricky, we check if the socket is actually ready,
         *   to prevent freezing.
         * TODO: Always use the readfds information.
         */
#if !defined(CYGWIN)
        if (udp_s >= 0)
#else
        if (udp_s >= 0 && FD_ISSET(udp_s, &readfds))
#endif
        {
            char udp_buf[1024+1], *st;
            int cnt;

            length = sizeof addr;
            cnt = recvfrom(udp_s, udp_buf, sizeof(udp_buf)-1, 0
                          , (struct sockaddr *)&addr, &length);
            if (cnt != -1)
            {
                string_t *udp_data;

                udp_data = new_n_mstring(udp_buf, cnt);
                if (!udp_data)
                {
                    debug_message("%s Out of memory (%d bytes) for UDP message.\n"
                                 , time_stamp(), cnt);
                }
                else
                {
                    command_giver = NULL;
                    current_interactive = NULL;
                    current_object = NULL;
                    trace_level = 0;
#ifndef USE_IPV6
                    st = inet_ntoa(addr.sin_addr);
#else
                    st = inet6_ntoa(addr.sin_addr);
#endif
                    push_c_string(inter_sp, st);
                    push_string(inter_sp, udp_data); /* adopts the ref */
                    push_number(inter_sp, ntohs(addr.sin_port));
                    RESET_LIMITS;
                    callback_master(STR_RECEIVE_UDP, 3);
                    CLEAR_EVAL_COST;
                }
            }
        } /* if (upd_s) */

        /* --- The Scan for User Commands --- */

        for (; NextCmdGiver >= 0; IncCmdGiver)
        {
            object_t *snooper;

            ip = all_players[NextCmdGiver];

            if (ip == 0)
                continue;

#ifdef USE_TLS
            /* Special case for setting up a TLS connection: don't
             * attempt IO if the connection is still being set up.
             */
            if (ip->tls_status == TLS_HANDSHAKING)
            {
                tls_continue_handshake(ip);
                continue;
            }
#endif

            if (FD_ISSET(ip->socket, &writefds))
            {
                comm_write_pending(ip);
            }
            /* Skip players which have reached the ip->maxNumCmds limit
             * for this second. We let the data accumulate on the socket.
             */
            if (ip->last_time == current_time
             && ip->maxNumCmds >= 0
             && ip->numCmds >= ip->maxNumCmds)
                continue;

            /* Get the data (if any), at max enough to fill .text[] */

            if (FD_ISSET(ip->socket, &readfds)) {
                int l;

                l = MAX_TEXT - ip->text_end;

                /* In CHARMODE with combine-charset, the driver gets
                 * confused when receiving MAX_TEXT or more combinable
                 * characters.
                 * I couldn't quite figure out what and why, but
                 * reading less than MAX_TEXT characters works around
                 * the issue.
                 */
#ifndef SIMULATE_CHARMODE
                if ((ip->noecho & (CHARMODE_REQ|CHARMODE)) == (CHARMODE_REQ|CHARMODE))
#else
                if (ip->noecho & (CHARMODE_REQ|CHARMODE))
#endif
                {
                    l -= 2;
                }

                DTN(("text_end %hd, can read %d chars\n", ip->text_end, l));

#ifdef USE_TLS
                if (ip->tls_status != TLS_INACTIVE)
                    l = tls_read(ip, ip->text + ip->text_end, (size_t)l);
                else
#endif
                    l = socket_read(ip->socket, ip->text + ip->text_end, (size_t)l);
                DTN(("# chars read: %d\n", l));
                /* TODO: Check if the block below can be simplified by using
                 * TODO::strerror().*/
                if (l == -1) {
                    if (errno == ENETUNREACH) {
                        debug_message("%s Net unreachable detected.\n"
                                     , time_stamp());
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    if (errno == EHOSTUNREACH) {
                        debug_message("%s Host unreachable detected.\n"
                                     , time_stamp());
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    if (errno == ETIMEDOUT) {
                        debug_message("%s Connection timed out detected.\n"
                                     , time_stamp());
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    if (errno == ECONNRESET) {
                        debug_message("%s Connection reset by peer detected.\n"
                                     , time_stamp());
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    if (errno == ECONNREFUSED) {
                        debug_message("%s Connection refused detected.\n"
                                     , time_stamp());
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    if (errno == EWOULDBLOCK) {
                        debug_message("%s read would block socket %d!\n"
                                     , time_stamp(), ip->socket);
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    if (errno == EMSGSIZE) {
                        debug_message("%s read EMSGSIZE\n", time_stamp());
                        continue;
                    }
                    if (errno == ESHUTDOWN) {
                        debug_message("%s Connection to socket %d lost.\n"
                                     , time_stamp(), ip->socket);
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    if (errno == EBADF) {
                        if (ip->ob)
                            debug_message("%s Socket %d (ip %p '%s') is a bad descriptor.\n"
                                         , time_stamp(), ip->socket, ip
                                         , get_txt(ip->ob->name));
                        else
                            debug_message("%s Socket %d (ip %p) is a bad descriptor.\n"
                                         , time_stamp(), ip->socket, ip);
                        remove_interactive(ip->ob, MY_FALSE);
                        continue;
                    }
                    perror("read");
                    debug_message("%s Unexpected errno %d\n"
                                 , time_stamp(), errno);
                    remove_interactive(ip->ob, MY_FALSE);
                    continue;
                }
                if (l == 0) {
                    if (ip->closing)
                        comm_fatal(ip, "Tried to read from closing socket.\n");
                        /* This will forcefully disconnect the user */
                    else
                        remove_interactive(ip->ob, MY_FALSE);
                    continue;
                }
#ifdef COMM_STAT
                inet_packets_in++;
                inet_volume_in += l;
#endif

                ip->text_end += l;

                /* Here would be the place to send data through an
                 * outportal instead of returning it.
                 */

                telnet_neg(ip);
            } /* if (cmdgiver socket ready) */

            /* if ip->text[0] does not hold a valid character, the outcome
             * of the comparison to input_escape does not matter.
             */

            /* ----- CHARMODE -----
             * command_start is 0 at the beginning. Received chars start at
             * text[0].  After the first character is processed, command_start
             * will be 1.  Chars are in text[1] then. Only after a
             * full_newline is command_start reset to 0. This is important for
             * bang-escape, the first char in a 'line' is stored in text[0],
             * subsequent chars are in text[1].
             *
             * chars_ready is the number of chars in the text buffer. If the
             * user is slow this will be 1. If the user pastes data it could
             * be more.  The chars are processed then one at a time (or if
             * combine-charset is used that many until a non-combinable char
             * is reached).
             *
             * The processed char(s) are copied to buff and handled in the
             * backend.
             *
             * If telnet_neg() returned state READY, we want to process the
             * string end marker (which represents the \r\n) also and have to
             * add 1 to strlen() for the chars_ready.
             *
             * The remark above 'if (destix > 0 && !buff[destix-1])' is not
             * quite true (anymore). Because we process the string terminating
             * \0 as a char, we will have a destix > 0 always - even if we got
             * a new line.  Mind, that buff[destix-1] is always buff[0] in
             * that 'if', because newlines are never combinable and we always
             * start with a new buffer for it!
             *
#ifndef SIMULATE_CHARMODE
             * TODO: I dont think that it is neccesary to disable charmode if
             * TODO:: the client refuses to use it. The disadvantage of the
             * TODO:: present behaviour is a confused lpc object (which could
             * TODO:: not know if it gets linemode-lines). The charmode code
             * TODO:: does work with clients in linemode.
#endif
             */

#ifndef SIMULATE_CHARMODE
            if ((ip->noecho & (CHARMODE_REQ|CHARMODE)) == (CHARMODE_REQ|CHARMODE))
#else
            if (ip->noecho & (CHARMODE_REQ|CHARMODE))
#endif
            {
                DTN(("CHARMODE_REQ\n"));
                if (ip->text[0] != input_escape
                 || find_no_bang(ip) & IGNORE_BANG )
                {
                    /* Unescaped input.
                     * Puts the next character(s) (addressed by
                     * .command_start) into buff[0] and return the data.
                     */

                    int destix;  /* Save index */
                    Bool end_of_line = MY_FALSE;

                    DTN(("  Unescaped input\n"));

                    if (ip->tn_state != TS_READY)
                    {
                        /* .text[] contains an incomplete negotiation.
                         * Set .chars_ready the amount of pure data available
                         * and temporarily suspend the telnet machine.
                         */
                        length = (TN_START_VALID(ip->tn_state)
                                   ? ip->tn_start
                                   : ip->command_end
                                 ) - ip->command_start;
                        DTN(("    incomplete negotiation: length %ld\n"
                           , (long)length));
                        if (!length)
                            continue;
                        if (length < 0)
                        {
                            comm_fatal(ip, "comm: data length < 0: %ld\n", (long)length);
                            continue;
                        }
                        DTN(("    save machine state %hhd, set to %d (READY)\n"
                          , ip->tn_state, TS_READY));
                        ip->save_tn_state = ip->tn_state;
                        ip->chars_ready = length;
                        ip->tn_state = TS_READY;
                    }
                    else if (!ip->chars_ready)
                    {
                        /* Empty input: we received an end of line.
                         * The telnet machine is already suspended, but
                         * we have to set the state for it to return to.
                         * At the moment it is TS_INVALID, so the next
                         * character received would be thrown away.
                         */
                        DTN(("    Empty input: save machine state %d (DATA)\n"
                          , TS_DATA));
                        length = strlen(ip->text + ip->command_start) + 1;
                        ip->chars_ready = length;
                        ip->save_tn_state = TS_DATA;
                        end_of_line = MY_TRUE;
                        /* tn_state is TS_READY */
                    }

                    /* Copy as many characters from the text[] into
                     * the buff[] as possible.
                     */
                    DTN(("  %"PRId32" chars ready\n", ip->chars_ready));
                    if (end_of_line)
                    {
                        DTN(("    faking NL\n"));
                        buff[0] = '\n';
                        destix = 1;
                    }
                    else for (destix = 0; destix < ip->chars_ready; )
                    {
                        char ch;

                        ch = ip->text[ip->command_start++];
                        buff[destix++] = ch;
                        if (!(ip->combine_cset[(ch&0xff) / 8] & (1 << (ch % 8)))
                         || !ch
                           )
                        {
                            /* This character can't be combined (or it is the
                             * end of the line).
                             * If it is not the first character encountered,
                             * undo the previous store; in either case break
                             * the loop.
                             */
                            if (destix != 1)
                            {
                                destix--;
                                ip->command_start--;
                            }
                            break;
                        }
                    }

                    /* destix is now the number of characters stored in
                     * buff[], and is at least 1.
                     */

                    if (!buff[destix-1])
                    {
                        /* End of line. Reinitialise the telnet machine
                         */
                        DTN(("    end of line: reinit telnet machine\n"));
                        destix--;
                        ip->command_start = 0;
                        ip->tn_state = TS_DATA;
                        telnet_neg(ip);
                    }

                    buff[destix] = '\0';

                    if (!end_of_line)
                        ip->chars_ready -= destix;
                    DTN(("  %"PRId32" chars left ready\n", ip->chars_ready));
                    if (!ip->chars_ready)
                    {
                        /* All the pure data was read, now restore the
                         * old telnet machine state.
                         * Leave the first char in to make input escape
                         * possible
                         */
                        DTN(("    restore old telnet machine state %hhd\n"
                            , ip->save_tn_state));
                        ip->tn_state = ip->save_tn_state;
                        ip->save_tn_state = TS_INVALID;
                        ip->tn_start -= ip->command_start - 1;
                        ip->command_end -= ip->command_start - 1;

                        if (ip->command_start && ip->command_end > 0)
                        {
                            move_memory( ip->text, ip->text+ip->command_start
                                       , ip->command_end
                                       );
                        }

                        ip->command_start = 1;

                        /* When receiving a pure data line in charmode, starting
                         * with the second char, these two values may become
                         * negative. We have to correct them then to point
                         * to ip->command_start.
                         */
                        DTN(("    tn_start %hd, command_end %hd\n", ip->tn_start, ip->command_end));
                        if (ip->tn_start < 1)
                            ip->tn_start = 1;
                        if (ip->command_end < 1)
                            ip->command_end = 1;

                        ip->text_end = ip->tn_end = ip->command_end;
                    }

                    command_giver = ip->ob;
                    trace_level = ip->trace_level;
                    IncCmdGiver;
                    CmdsGiven = 0;

                    if (ip->last_time != current_time)
                    {
                        ip->last_time = current_time;
                        ip->numCmds = 0;
                    }
                    else
                        ip->numCmds++;

                    DTN(("--- return with char command %02x '%c' length %d ---\n", buff[0], buff[0], destix));

                    return MY_TRUE;
                }
                else if (ip->tn_state != TS_READY)
                {
                    DT(("'%s'   Escaped input\n", get_txt(ip->ob->name)));
                    length = (TN_START_VALID(ip->tn_state)
                              ? ip->tn_start
                              : ip->command_end
                             ) - ip->command_start;
                    DTN(("  data length %ld\n", (long)length));
                    if (length < 0)
                    {
                        comm_fatal(ip, "comm: data length < 0: %ld\n", (long)length);
                        continue;
                    }
                    if (length > ip->chars_ready)
                    {
                        comm_socket_write(ip->text + ip->chars_ready
                                        , (size_t)(length - ip->chars_ready)
                                        , ip, 0);
                        ip->chars_ready = length;
                    }
                }
            } /* if (CHARMODE_REQ) */

            /* The telnet negotiation produces the commands starting at
             * the beginning of .text[] and terminated with a '\0'. Whenever
             * a command is complete, the tn_state is TS_READY.
             */
            DTN(("tn complete, telnet machine state: %hhd\n", ip->tn_state));
            if (ip->tn_state == TS_READY)
            {
                /* We have a command: copy it into buff, handle a
                 * possible snooper and return.
                 */

                DTN(("telnet machine ready\n"));
                /* buffer overflows here are impossible even with strcpy(),
                 * because buff is allocated in backend() as MAX_TEXT+4 and
                 * ip->text is allocated as MAX_TEXT+2. Ok, as long as nobody
                 * changes buff in backend() withour changing ip->text ... */
                strcpy(buff, ip->text);
                command_giver = ip->ob;
                trace_level = ip->trace_level;
                ip->chars_ready = 0; /* for escaped charmode */

                /* Reinitialize the telnet machine, possibly already
                 * producing the next command in .text[].
                 */
                ip->tn_state = TS_DATA;
                telnet_neg(ip);

                /* If the user is not in ed, don't let him issue another command
                 * before the poll comes again.
                 */
                if (ip->input_handler
                 && ip->input_handler->type == INPUT_ED
                 && CmdsGiven < ALLOWED_ED_CMDS)
                {
                    CmdsGiven++;
                    FD_CLR(ip->socket, &readfds);
                }
                else
                {
                    IncCmdGiver;
                    CmdsGiven = 0;
                }

                /* Manage snooping - should the snooper see type ahead?
                 * Well, he doesn't here.
                 */
                if (NULL != (snooper = ip->snoop_by)
                 && !(snooper->flags & O_DESTRUCTED)
                 && !(ip->noecho & NOECHO_REQ)
                   )
                {
                    if (O_IS_INTERACTIVE(snooper))
                    {
                        command_giver = snooper;
                        add_message("%% %s\n", buff);
                    }
                    else
                    {
                        /* buff is limited to MAX_TEXT+4. Additionally,
                         * get_message() is usually not called recursively or
                         * from a very high stack depth, so alloca() is 
                         * probably ok. */
                        char *snoop_message = alloca(strlen(buff) + 4);
                        sprintf(snoop_message, "%% %s\n", buff);
                        tell_npc_str(snooper, snoop_message);
                    }
                    command_giver = ip->ob;
                }

                if (ip->last_time != current_time)
                {
                    ip->last_time = current_time;
                    ip->numCmds = 0;
                }
                else
                    ip->numCmds++;

#ifndef SIMULATE_CHARMODE
                if ((ip->noecho & (CHARMODE_REQ|CHARMODE)) == CHARMODE_REQ)
                {
                    DTN(("   clear CHARMODE as it was refused anyway\n"));
                    ip->noecho &= ~(CHARMODE_REQ|CHARMODE|CHARMODE_ACK);
                    reset_input_buffer(ip);
                }
#endif /* SIMULATE_CHARMODE */

                DTN(("--- return with line command ---\n"));
                DTN(("--- '%s'\n", buff));
                return MY_TRUE;
            } /* if (have a command) */

        } /* for (NextCmdGiver) */

        /* If we got here, we couldn't find any commands:
         * loop and select (on timeout) again.
         */

    } /* while(forever) */

    /* NOTREACHED */
#   undef StartCmdGiver
#   undef IncCmdGiver

} /* get_message() */

/*-------------------------------------------------------------------------*/
void
remove_interactive (object_t *ob, Bool force)

/* Remove the interactive user <ob> immediately.
 * If <force> is true, the user is removed under all circumstances and
 * without even flushing the outgoing buffer.
 * This function should not be called from within a LPC command execution.
 */

{
    object_t *save = command_giver;
    int i;
    interactive_t *interactive;
    object_t * curobj_save = current_object;
    int save_privilege;

    interactive = O_GET_INTERACTIVE(ob);

    /* Proper call? */
    for (i = 0; i < MAX_PLAYERS && all_players[i] != interactive; i++) NOOP;
    if (i >= MAX_PLAYERS)
    {
        fatal("Could not find and remove player %s\n", get_txt(ob->name));
        abort();
    }
    if (interactive->closing && !force)
        fatal("Double call to remove_interactive()\n");

    interactive->closing = MY_TRUE;
    current_object = ob;

    /* If the object is not destructed, save any ed buffers. */

    if ( !(ob->flags & O_DESTRUCTED) )
    {
        command_giver = ob;
        abort_input_handler(interactive);
    }

    save_privilege = malloc_privilege;

    /* If the object is still not destructed, inform the master */

    if ( !(ob->flags & O_DESTRUCTED) )
    {
        int numRemaining = interactive->text_end - interactive->command_start;

        command_giver = NULL;
        current_interactive = NULL;
        push_ref_object(inter_sp, ob, "remove_interactive");

        if (numRemaining > 0)
        {
            string_t * remaining = NULL;
            memsafe( remaining = new_n_mstring(interactive->text+interactive->command_start, numRemaining)
                   , numRemaining, "buffer for remaining data from socket");
            push_string(inter_sp, remaining);
        }
        else
            push_ref_string(inter_sp, STR_EMPTY);

        malloc_privilege = MALLOC_MASTER;
        callback_master(STR_DISCONNECT, 2);
        /* master might have used exec() */
        ob = interactive->ob;
    }

    interactive->catch_tell_activ = MY_FALSE;

    /* Untie eventual snooping relations */

    if (interactive->snoop_by)
    {
        if (O_IS_INTERACTIVE(interactive->snoop_by))
        {
            O_GET_INTERACTIVE(interactive->snoop_by)->snoop_on = NULL;
        }
        else
        {
            free_object(interactive->snoop_by, "remove_interactive");
        }
        interactive->snoop_by = NULL;
    }
    if (interactive->snoop_on)
    {
        interactive->snoop_on->snoop_by = NULL;
        interactive->snoop_on = NULL;
    }

    command_giver = ob;

#ifdef ERQ_DEMON
    /* If this object is disconnected because it was used to connect
     * a new ERQ, put the connection into place and greet the ERQ.
     */
    if (interactive->do_close & FLAG_PROTO_ERQ
     && interactive->socket == erq_proto_demon
     && !force)
    {
        static unsigned char erq_welcome[] = { IAC, TELOPT_BINARY };

        add_message(message_flush);
        erq_demon = interactive->socket;
        erq_proto_demon = -1;
        socket_write(erq_demon, erq_welcome, sizeof erq_welcome);
    }
    else
#endif
    {
        if (!force)
        {
            /* Say goodbye to the user. */
            trace_level |= interactive->trace_level;
            add_message(message_flush);
        }

        remove_flush_entry(interactive); /* To be sure */

#ifdef USE_MCCP
        if (interactive->out_compress)
            end_compress(interactive, MY_TRUE);
            /* Always force the compression end as we won't get
             * a second chance at it.
             */
#endif

        /* If there is anything left, try now. */
        comm_write_pending(interactive);

#ifdef USE_TLS
        tls_deinit_connection(interactive);
#endif
        shutdown(interactive->socket, 2);
        socket_close(interactive->socket);
    } /* if (erq or user) */

    release_host_access(interactive->access_class);
      /* One user less in this class */

    num_player--;

    /* Release all associated resources */

    while (interactive->input_handler)
    {
        input_t * ih = interactive->input_handler;

        interactive->input_handler = ih->next;
        free_input_handler(ih);
    }

    if (interactive->modify_command)
    {
        free_object(interactive->modify_command, "remove_interactive");
    }

#ifdef USE_MCCP
    if (interactive->out_compress_buf)
       xfree(interactive->out_compress_buf);
    if (interactive->out_compress)
       xfree(interactive->out_compress);
#endif
    free_svalue(&interactive->prompt);

    if (interactive->trace_prefix)
        free_mstring(interactive->trace_prefix);

    while (interactive->write_first)
    {
        struct write_buffer_s *tmp = interactive->write_first;
        interactive->write_first = tmp->next;
        xfree(tmp);
    }

    /* Unlink the interactive structure from the shadow sentence
     * of the object.
     */
    O_GET_INTERACTIVE(ob) = NULL;
    check_shadow_sent(ob);

    xfree(interactive);
    if (i < MAX_PLAYERS)
        all_players[i] = NULL;
    while (max_player && !all_players[max_player])
        max_player--;
    free_object(ob, "remove_interactive");

    command_giver = check_object(save);
    current_object = curobj_save;
    malloc_privilege = save_privilege;
} /* remove_interactive() */

/*-------------------------------------------------------------------------*/
void
refresh_access_data(void (*add_entry)(struct sockaddr_in *, int, long*) )

/* Called from access_check after the ACCESS_FILE has been (re)read, this
 * function has to call the passed callback function add_entry for every
 * user currently logged in.
 */

{
    interactive_t **user, *this;
    int n;

    user = all_players;
    for (n = max_player + 2; --n; user++)
    {
        this = *user;
        if (this)
        {
            struct sockaddr_in addr;
            int port;
            length_t length;

            length = sizeof(addr);
            getsockname(this->socket, (struct sockaddr *)&addr, &length);
            port = ntohs(addr.sin_port);
            (*add_entry)(&this->addr, port, &this->access_class);
        }
    }
}

/*-------------------------------------------------------------------------*/
static INLINE void
set_default_conn_charset (char charset[32])

/* Set the default connection charset bitmask in <charset>.
 */

{
    memset(charset, 255, 32);
    charset['\0'/8] &= ~(1 << '\0' % 8);
} /* set_default_conn_charset() */

/*-------------------------------------------------------------------------*/
static INLINE void
set_default_combine_charset (char charset[32])

/* Set the default combine charset bitmask in <charset>.
 */

{
    memset(charset, 0, 32);
    charset['\n'/8] &= ~(1 << '\n' % 8);
    charset['\0'/8] &= ~(1 << '\0' % 8);
} /* set_default_combine_charset() */

/*-------------------------------------------------------------------------*/
static void
new_player ( object_t *ob, SOCKET_T new_socket
           , struct sockaddr_in *addr, size_t addrlen
           , int login_port
           )

/* Accept (or reject) a new connection on <new_socket> from <addr> (length
 * of structure is <addrlen>), accepted on port <login_port>.
 *
 * Called when get_message() detects a new connection on one of the
 * login ports, this function checks if the user may access the mud.
 *
 * If yes and <ob> is NULL, a new interactive structure is generated and
 * bound to the master, then master->connect() is called. This call is
 * expected to return an object and the interactive structure is rebound to
 * that object.
 * If yes and <ob> is an object, a new interactive structure is generated
 * and bound to <ob>
 *
 * Finally, logon() is called in the newly-interactive object.
 * Alternatively if no <ob> is given, master->connect() may exec() the
 * connection away from the master, in which case no further action will be
 * taken after the return from that call.
 *
 * If the connection can't be accepted for some reason, a failure
 * message will be send back to the user and the socket will be
 * closed.
 */

{
    int   i;             /* Index of free slot in all_players[] */
    char *message;       /* Failure message */
    svalue_t *ret;       /* LPC call results */
    interactive_t *new_interactive;
                         /* The new interactive structure */
    long class;     /* Access class */

    /* Set some useful socket options */
    set_socket_nonblocking(new_socket);
    set_close_on_exec(new_socket);
    set_socket_own(new_socket);

    /* Check for access restrictions for this connection */
    message = allow_host_access(addr, login_port, &class);

    if (access_log != NULL)
    {
        FILE *log_file = fopen (access_log, "a");

        if (log_file) {
            FCOUNT_WRITE(log_file);
            fprintf(log_file, "%s %s: %s\n"
                   , time_stamp()
#ifndef USE_IPV6
                   , inet_ntoa(addr->sin_addr)
#else
                   , inet6_ntoa(addr->sin_addr)
#endif
                   , message ? "denied" : "granted");
            fclose(log_file);
        }
    }

    if (message)
    {
        socket_write(new_socket, message, strlen(message));
        socket_write(new_socket, "\r\n", 2);
        socket_close(new_socket);
        return;
    }

    if (d_flag)
        debug_message("%s New player at socket %d.\n"
                     , time_stamp(), new_socket);

    /* Look for an empty slot in all_players[] */
    for (i = 0; i < MAX_PLAYERS && all_players[i] != NULL; i++) NOOP;
    if (i >= MAX_PLAYERS)
    {
        /* calling closures here would need special error handling */
        if (driver_hook[H_NO_IPC_SLOT].type == T_STRING)
        {
            string_t *msg;

            msg = driver_hook[H_NO_IPC_SLOT].u.str;
            socket_write(new_socket, get_txt(msg), mstrsize(msg));
        }
        else
        {
            message = "The mud is full. Come back later.\r\n";
            socket_write(new_socket, message, strlen(message));
        }
        socket_close(new_socket);
        debug_message("%s Out of IPC slots for new connection.\n"
                     , time_stamp());
        return;
    }

    /* The master must be loaded and free to accept a login */
    assert_master_ob_loaded();
    if (O_IS_INTERACTIVE(master_ob))
    {
        message = "Cannot accept connections. Come back later.\r\n";
        socket_write(new_socket, message, strlen(message));
        socket_close(new_socket);
        debug_message("%s Master still busy with previous new connection.\n"
                     , time_stamp());
        return;
    }

    command_giver = master_ob;
    trace_level = 0;
    new_interactive = xalloc(sizeof (interactive_t));
    if (!new_interactive)
    {
        message = "Cannot accept connection (out of memory). Come back later.\r\n";
        socket_write(new_socket, message, strlen(message));
        socket_close(new_socket);
        debug_message("%s Out of memory (%zu bytes) for new connection.\n"
                     , time_stamp(), sizeof(interactive_t));
        return;
    }

    if (ob && O_IS_INTERACTIVE(ob))
    {
        /* The caller provided an object to connect to. But since
         * it is already interactive, we have to terminate that
         * old connection.
         */
        remove_interactive(ob, MY_FALSE);
    }

    /* Link the interactive to the master */

    assert_shadow_sent(master_ob);
    O_GET_INTERACTIVE(master_ob) = new_interactive;
    master_ob->flags |= O_ONCE_INTERACTIVE;
    new_interactive->ob = ref_object(master_ob, "new_player");
    new_interactive->outgoing_conn = (ob != NULL);

    /* Initialize the rest of the interactive structure */

#ifdef USE_MCCP
    new_interactive->compressing = 0;
    new_interactive->out_compress = NULL;
    new_interactive->out_compress_buf=NULL;
#endif
#ifdef USE_TLS
    new_interactive->tls_status = TLS_INACTIVE;
    new_interactive->tls_session = NULL;
    new_interactive->tls_cb = NULL;
#endif
    new_interactive->input_handler = NULL;
    put_number(&new_interactive->prompt, 0);
    new_interactive->modify_command = NULL;
    new_interactive->closing = MY_FALSE;
    new_interactive->tn_enabled = MY_TRUE;
    new_interactive->do_close = 0;
    new_interactive->noecho = 0;
    new_interactive->supress_go_ahead = MY_FALSE;
    new_interactive->gobble_char = 0;
    new_interactive->catch_tell_activ = MY_TRUE;
    new_interactive->text_end = 0;
    new_interactive->command_start = 0;
    new_interactive->command_end = 0;
    new_interactive->chars_ready = 0;
    new_interactive->save_tn_state = TS_INVALID;
    new_interactive->tn_start = 0;
    new_interactive->tn_end = 0;
    new_interactive->tn_state = TS_DATA;
    new_interactive->ts_data = TS_DATA;
    new_interactive->snoop_on = NULL;
    new_interactive->snoop_by = NULL;
    new_interactive->last_time = current_time;
    new_interactive->numCmds = 0;
    new_interactive->maxNumCmds = -1;
    new_interactive->trace_level = 0;
    new_interactive->trace_prefix = NULL;
    new_interactive->message_length = 0;
    new_interactive->quote_iac = MY_TRUE;
    set_default_conn_charset(new_interactive->charset);
    set_default_combine_charset(new_interactive->combine_cset);
    new_interactive->text[0] = '\0';
    memcpy(&new_interactive->addr, addr, addrlen);
    new_interactive->access_class = class;
    new_interactive->socket = new_socket;
    new_interactive->next_player_for_flush = NULL;
    new_interactive->previous_player_for_flush = NULL;
    new_interactive->msg_discarded = DM_NONE;

    new_interactive->write_first = new_interactive->write_last = NULL;
    new_interactive->write_size = 0;
    new_interactive->write_max_size = -2;

    /* Add the new interactive structure to the list of users */

    all_players[i] = new_interactive;
    if (i > max_player)
        max_player = i;
    num_player++;

    current_interactive = master_ob;

    if (!ob)
    {
        /* The caller did not provide an object to connect to.
         * Call master->connect() and evaluate the result.
         */
        ret = callback_master(STR_CONNECT, 0);
        if (new_interactive != O_GET_INTERACTIVE(master_ob))
            return;
        if (ret == NULL
         || ret->type != T_OBJECT
         || (ob = ret->u.ob, O_IS_INTERACTIVE(ob)))
        {
            remove_interactive(master_ob, MY_FALSE);
            return;
        }
        command_giver = master_ob;
        add_message(message_flush);
    }

    /* ob is now a non-interactive object, either passed in from the caller
     * or returned from connect().
     * Relink the interactive from the master to this as the user object.
     */

    O_GET_INTERACTIVE(master_ob) = NULL;
    master_ob->flags &= ~O_ONCE_INTERACTIVE;
    check_shadow_sent(master_ob);
    free_object(master_ob, "new_player");

    assert_shadow_sent(ob);
    O_GET_INTERACTIVE(ob) = new_interactive;
    new_interactive->ob = ref_object(ob, "new_player");
    ob->flags |= O_ONCE_INTERACTIVE;

    /* Prepare to call logon() in the new user object.
     */
    command_giver = ob;
    current_interactive = ob;
    if (new_interactive->snoop_on)
    {
        new_interactive->snoop_on->snoop_by = ob;
    }
#ifdef ERQ_DEMON
    (void) lookup_ip_entry(new_interactive->addr.sin_addr, MY_TRUE);
    /* TODO: We could pass the retrieved hostname right to login */
#endif
#ifdef USE_TLS
    /* If we're using secure connections and the connect() triggered
     * a handshake which is still going on, we call logon() as
     * the default TLS callback. This way, logon() is callled only
     * if the connection could be established, secure or not.
     */
    if (new_interactive->tls_status != TLS_HANDSHAKING)
    {
        /* Connection not secure, or already established: logon. */
        logon_object(ob);
    }
    else if (new_interactive->tls_cb == NULL)
    {
        callback_t * cb;

        /* Connection in TLS handshake, but not callback: set a callback
         * to the logon function.
         */
        if (find_function(STR_LOGON, current_interactive->prog) < 0)
        {
            errorf("Could not find %s() on the player %s\n", get_txt(STR_LOGON), get_txt(current_interactive->name));
            /* NOTREACHED */
        }

        xallocate(cb, sizeof(*cb), "logon tls-callback structure");
        setup_function_callback(cb, current_interactive, STR_LOGON, 0, NULL, MY_TRUE);
        new_interactive->tls_cb = cb;

    }
    /* else: Connection in TLS handshake and callback set by connect(). */
#else
    logon_object(ob);
#endif /* USE_TLS */
    if (!(ob->flags & O_DESTRUCTED))
        print_prompt();
    flush_all_player_mess();
} /* new_player() */

/*-------------------------------------------------------------------------*/
void
set_noecho (interactive_t *ip, char noecho, Bool local_change, Bool external)

/* Change the input mode <i>->noecho to the given <noecho>, performing all
 * necessary telnet negotiations. If the driverhook H_NOECHO is set,
 * the hook function is expected to do all the negotiations.
 * If <local_change> is TRUE, the driver will not send out any telnet
 * commands for CHARMODE/LINEMODE changes.
 */

{
    char old, confirm;
    object_t *ob;

    if (!ip->tn_enabled)
    {
        DTN(("set_noecho(%02hhx) old %02hhx: TN disabled\n"
            , noecho, ip->noecho));
    }

    old = ip->noecho;

    confirm = (char)(
      noecho | CHARMODE_REQ_TO_CHARMODE(noecho & (NOECHO_REQ|CHARMODE_REQ)));
    DTN(("set_noecho(%02hhx%s) old %02hhx %s\n"
       , noecho, local_change ? " local" : "", old, decode_noecho(old)));
    DTN(("  -> confirm: %02hhx %s\n"
       , confirm, decode_noecho(confirm)));
    DTN(("           -> %02hhx %s\n"
       , confirm | NOECHO_ACKSHIFT(confirm)
       , decode_noecho(confirm | NOECHO_ACKSHIFT(confirm))
       ));

    ip->noecho = confirm;

    confirm |= NOECHO_ACKSHIFT(confirm);
    if (((confirm ^ old) & (NOECHO_MASK|CHARMODE_MASK)) && ip->tn_enabled )
    {
        DTN(("set_noecho(): Mode changes\n"));
        ob = ip->ob;
        if (!(ob->flags & O_DESTRUCTED))
        {
            if (driver_hook[H_NOECHO].type == T_STRING
             || driver_hook[H_NOECHO].type == T_CLOSURE
               )
            {
                DTN(("set_noecho():   calling H_NOECHO\n"));
                push_number(inter_sp, noecho);
                push_ref_valid_object(inter_sp, ob, "set_no_echo");
                push_number(inter_sp,  local_change ? 1 : 0);
                if (driver_hook[H_NOECHO].type == T_STRING)
                    secure_apply_ob(driver_hook[H_NOECHO].u.str, ob, 3, external);
                else 
                {
                    if (driver_hook[H_NOECHO].x.closure_type == CLOSURE_LAMBDA)
                    {
                        free_object(driver_hook[H_NOECHO].u.lambda->ob
                                   , "set_noecho");
                        driver_hook[H_NOECHO].u.lambda->ob
                          = ref_object(ob, "set_noecho");
                    }
                    secure_call_lambda(&driver_hook[H_NOECHO], 3, external);
                }
                if (~confirm & old & CHARMODE_MASK)
                {
                    if (ip->save_tn_state != TS_INVALID)
                    {
                        DT(("'%s' set_noecho():     0 chars ready, "
                            "saved state %hhd\n", get_txt(ip->ob->name)
                                              , ip->save_tn_state));
                        ip->chars_ready = 0;
                        ip->tn_state = ip->save_tn_state;
                    }
                    reset_input_buffer(ip);
                }
            }
            else
            {
                object_t *save;

                save = command_giver;
                command_giver = ob;
#ifdef SAVE_NOECHO
                ip->noecho &= ~NOECHO_DELAYED;
#endif
                if (~confirm & old & NOECHO)
                {
                    DTN(("set_noecho():   WONT TELOPT_ECHO\n"));
                    send_wont(TELOPT_ECHO);
                }
                else if (confirm & ~old & NOECHO_MASK)
                {
#ifdef SAVE_NOECHO
                    if (confirm & ~old & CHARMODE_MASK)
                    {
                        ip->noecho |= NOECHO_DELAYED;
                        ip->noecho &= ~(NOECHO | NOECHO_REQ);
                        DTN(("set_noecho():   delaying WILL TELOPT_ECHO\n"));
                    }
                    else
                    {
#endif
                        DTN(("set_noecho():   WILL TELOPT_ECHO\n"));
                        send_will(TELOPT_ECHO);
#ifdef SAVE_NOECHO
                    }
#endif
                }
                else /* No change in NOECHO mode */ if (confirm & NOECHO)
                {
                    /* Since we stay in NOECHO mode, we need the ACK flag set. */
                    DTN(("set_noecho():   Staying in NOECHO mode\n"));
                    ip->noecho |= NOECHO_ACKSHIFT(NOECHO);
                }

                if (ip->supress_go_ahead && !(confirm & (NOECHO|CHARMODE)))
                {
                    DTN(("set_noecho():   WONT TELOPT_SGA\n"));
                    ip->supress_go_ahead = MY_FALSE;
                    send_wont(TELOPT_SGA);
                }
                /* Only using SGA for charmode is supported hardcoded.
                 * To make more sophisticated negotiations, e.g. using LINEMODE,
                 * use the H_NOECHO hook.
                 */
                if ((~confirm & old & CHARMODE_MASK)
                ||  ((~confirm & old & NOECHO_STALE) && (old & CHARMODE_MASK))
                   )
                {
                    if(~confirm & old & CHARMODE_MASK)
                    {
                        DTN(("set_noecho():   turn off charmode\n"));
                        if ((old & CHARMODE) && !local_change)
                        {
                            DTN(("set_noecho():     DONT TELOPT_SGA\n"));
                            send_dont(TELOPT_SGA);
                        }
                        if (ip->save_tn_state != TS_INVALID)
                        {
                            DTN(("set_noecho():     0 chars ready, saved state %hhd\n", ip->save_tn_state));
                            ip->chars_ready = 0;
                            ip->tn_state = ip->save_tn_state;
                        }
                    }

                    reset_input_buffer(ip);
                }
                else if (confirm & ~old & CHARMODE_MASK)
                {
                    DTN(("set_noecho():   turn on charmode\n"));
                    if (!local_change)
                    {
                        DTN(("set_noecho():     DO+WILL TELOPT_SGA\n"));
                        send_do(TELOPT_SGA);
                        /* some telnet implementations (Windows' telnet is one) mix
                         * up DO and WILL SGA, thus we send WILL SGA as well.
                         */
                        send_will(TELOPT_SGA);
                        ip->supress_go_ahead = MY_TRUE;
                    }
                    else
                        /* Since there won't be any telnet negotiation
                         * we can assume that CHARMODE is acknowledged.
                         */
                        ip->noecho |= NOECHO_ACKSHIFT(CHARMODE);

                }
                else /* No change in CHARMODE mode */ if (confirm & CHARMODE)
                {
                    /* Since we stay in CHARMODE mode, we need the ACK flag set. */
                    DTN(("set_noecho():   Staying in CHARMODE mode\n"));
                    ip->noecho |= NOECHO_ACKSHIFT(CHARMODE);
                }

                command_giver = save;
            }
        } /* if (!(ob->flags & O_DESTRUCTED)) */
    }
    else
    {
        /* No change in modes.
         * However, if we stay in NOECHO/CHARMODE, we need to set
         * the ACK flags.
         */
        if (confirm & CHARMODE)
        {
            /* Since we stay in CHARMODE mode, we need the ACK flag set. */
            DTN(("set_noecho():   Staying in CHARMODE mode\n"));
            ip->noecho |= NOECHO_ACKSHIFT(CHARMODE);
        }
        if (confirm & NOECHO)
        {
            /* Since we stay in NOECHO mode, we need the ACK flag set. */
            DTN(("set_noecho():   Staying in NOECHO mode\n"));
            ip->noecho |= NOECHO_ACKSHIFT(NOECHO);
        }
    }

} /* set_noecho() */

/*-------------------------------------------------------------------------*/
int
find_no_bang (interactive_t *ip)

/* Find the most recent input_to in *<ip> which specified "IGNORE_BANG" and
 * return its full "noecho" flags. This may be the ip->noecho itself!
 * If there is none, return 0.
 */

{
    input_t *ih;

    if (ip->noecho & IGNORE_BANG)
        return ip->noecho;

    for (ih = ip->input_handler; ih; ih = ih->next)
        if (ih->noecho & IGNORE_BANG)
            return ih->noecho;
    return 0;
} /* find_no_bang() */

/*-------------------------------------------------------------------------*/
static Bool
call_input_to (interactive_t *i, char *str, input_to_t *it)

/* Call the input_to handler <it> for this user <i> and the input <str>.
 * Return TRUE if this handler was executed successfully, and FALSE
 * if the input was not processed.
 *
 * This function is only called by call_function_interactive().
 */

{
    static input_to_t current_it;
      /* Current input_to, static so that longjmp() won't clobber it. */

    struct error_recovery_info error_recovery_info;
    object_t   *ob;   /* object holding <function> */


    /* We got the right input_to_t. Check if it's still valid. */
    ob = callback_object(&(it->fun));
    if (!ob)
    {
        /* Sorry, the object has selfdestructed ! */
        free_input_to(it);
        return MY_FALSE;
    }

    if (O_PROG_SWAPPED(ob)
     && load_ob_from_swap(ob) < 0)
    {
        free_input_to(it);
        errorf("Out of memory: unswap object '%s'.\n", get_txt(ob->name));
        return MY_FALSE;
    }

    /* Clear the input_to() reference in case the function called
     * sets up a new one.
     */
    current_it = *it;
    xfree(it);
    free_svalue(&current_it.input.prompt); /* Don't need this anymore */

    /* Activate the local error recovery context */

    error_recovery_info.rt.last = rt_context;
    error_recovery_info.rt.type = ERROR_RECOVERY_BACKEND;
    rt_context = (rt_context_t *)&error_recovery_info.rt;

    if (setjmp(error_recovery_info.con.text))
    {
        /* An error occured: free the remaining data,
         * restore the error stack and return
         */

        clear_state();
        debug_message("%s Error in input_to().\n", time_stamp());
        free_callback(&(current_it.fun));
        rt_context = error_recovery_info.rt.last;
        return MY_TRUE;
    }

    /* Call the input_to() function with the newly input string */

    push_c_string(inter_sp, str);
    (void)backend_callback(&(current_it.fun), 1);

    rt_context = error_recovery_info.rt.last;

    /* Done */
    return MY_TRUE;
}

/*-------------------------------------------------------------------------*/
Bool
call_function_interactive (interactive_t *i, char *str)

/* Execute a pending input handler for this user <i> and the input <str>
 * Return TRUE if an input_to() or ed() was pending and executed, and FALSE
 * if the input was not processed.
 *
 * This function is called by the backend as part of the input processing.
 */

{
    input_t    *ih;
    ih = i->input_handler;

    /* _Are_ there an input_to() pending? */
    if (!ih)
        return MY_FALSE;

    /* Yes, there are. Check if we have to handle input escape. */
    if (*str == input_escape && str[1])
    {
        input_t * prev;

        for (prev = NULL
            ; ih && !(ih->noecho & IGNORE_BANG)
            ; prev = ih, ih = ih->next)
            NOOP;

        if (ih)
        {
            /* Move this 'IGNORE_BANG' input_to to the top of list
             * since it's the one we're going to execute.
             */
            if (prev)
            {
                prev->next = ih->next;
                ih->next = i->input_handler;
                i->input_handler = ih;
            }

            if (!(i->noecho & NOECHO) != !(ih->noecho & NOECHO_REQ)) {
                /* !message for ECHO-context  while in NOECHO - simulate the
                 * echo by sending the (remaining) raw data we got.
                 */
                add_message("%s\n", str + i->chars_ready);
                i->chars_ready = 0;
            }

            /* Don't hide the leading input escape */
        }
        else
        {
            /* Bang-input but no matching input_to(): return */
            return MY_FALSE;
        }
    }

    switch (ih->type)
    {
    case INPUT_TO:
        {
            Bool res;

            i->input_handler = ih->next;

            /* if there is a series of noecho/charmode input, we should only
             * negotiate when we know that the state actually should change.
             * In other words: should the input_to function request NOECHO
             * again, the NOECHO_STALE bit will be cleared and we will not
             * turn NOECHO off after the call.
             */
            if (i->noecho)
            {
                i->noecho |= NOECHO_STALE;
            }

            res = call_input_to(i, str, (input_to_t*) ih);

            /* If NOECHO is no longer needed, turn it off. */

            if (i->noecho & NOECHO_STALE)
            {
                set_noecho(i, i->input_handler ? i->input_handler->noecho : 0
                            , i->input_handler ? i->input_handler->local : MY_FALSE
                            , MY_TRUE);
            }

            return res;
        }

    case INPUT_ED:
        ed_cmd(str, ih);
        return MY_TRUE;
    }

    return MY_FALSE;

} /* call_function_interactive() */

/*-------------------------------------------------------------------------*/
static Bool
set_call ( object_t *ob, input_to_t *it, char noecho
         , Bool local_change, Bool append)

/* Set a a new input_to <it> with the flags <noecho> (mainly really NOECHO,
 * but also IGNORE_BANG or not) to the interactive object <ob>.
 * If <local_change> is TRUE, the driver will not send out any telnet
 * commands for CHARMODE/LINEMODE changes.
 * If <append> is TRUE, the call is appended to the list of existing
 * input_to's, if any.
 * Return TRUE on success.
 *
 * Called for efun input_to().
 */

{
    interactive_t *ip;

    if (ob == NULL || it == NULL)
        return MY_FALSE;
    if (!(O_SET_INTERACTIVE(ip, ob))
     || ip->closing
       )
    {
        return MY_FALSE;
    }

    if (!append && ip->input_handler != NULL)
    {
        input_t * ih = ip->input_handler;

        while (ih && ih->type != INPUT_TO)
            ih = ih->next;

         if (ih && ((input_to_t*)ih)->eval_nr == eval_number)
             return MY_FALSE;
    }

    it->input.noecho = noecho;
    it->input.local = local_change;
    it->input.type = INPUT_TO;

    /* Appended input_tos never count. */
    it->eval_nr = eval_number - (append ? 1 : 0);

    add_input_handler(ip, &(it->input), append);
    return MY_TRUE;
} /* set_call() */

/*-------------------------------------------------------------------------*/
void
remove_all_players (void)

/* Destruct all user objects. This is first tried by calling master->remove()
 * for every object. If this doesn't destruct the user object,
 * destruct() is used.
 * The function is called when the game is shut down.
 */

{
    int i;

    for (i = 0; i < MAX_PLAYERS; i++) {
        if (all_players[i] == 0 || (all_players[i]->ob->flags & O_DESTRUCTED))
            continue;
        command_giver = all_players[i]->ob;
        trace_level |= all_players[i]->trace_level;
        RESET_LIMITS;
        CLEAR_EVAL_COST;
        push_ref_object(inter_sp, all_players[i]->ob, "remove_all_players");
        (void)callback_master(STR_REMOVE_PL, 1);
        if ( !(all_players[i]->ob->flags & O_DESTRUCTED) ) {
            destruct(all_players[i]->ob);
        }
    }
}

/*-------------------------------------------------------------------------*/
static void
print_prompt_string (string_t *prompt)

/* Print the string <prompt> to the current command_giver.
 * This function checks if the driver hook H_PRINT_PROMPT is set and in that
 * case passes the string through the set function. If it is not set,
 * the prompt is printed via add_message().
#ifdef USE_TLS
 * The prompt is not printed at all if the interactive is currently
 * negotiating the TLS handshake.
#endif
 */

{
    svalue_t *hook = &driver_hook[H_PRINT_PROMPT];

#ifdef USE_TLS
    {
        interactive_t *ip = O_GET_INTERACTIVE(command_giver);
        if (NULL != ip && TLS_HANDSHAKING == ip->tls_status)
            return;
    }
#endif /* USE_TLS */

    if (hook->type == T_CLOSURE)
    {
        object_t *ob;

        /* Needed for clean error recovery */

        previous_ob = 0;
        current_object = command_giver;

        /* Check if the object the closure is bound to still exists.
         * If not, erase the hook, print the prompt using add_message(),
         * then throw an error.
         */
        ob = !CLOSURE_MALLOCED(hook->x.closure_type)
             ? hook->u.ob
             : hook->u.lambda->ob;

        if (ob->flags & O_DESTRUCTED)
        {
            free_svalue(hook);
            put_number(hook, 0);
            current_object = NULL; /* So that catch_tell() can see it */
            add_message(FMT_STRING, prompt);
            errorf("H_PRINT_PROMPT for %s was a closure bound to a "
                   "now-destructed object - hook removed.\n", 
                   get_txt(command_giver->name));
            /* NOTREACHED */
        }

        push_ref_string(inter_sp, prompt);
        call_lambda(hook, 1);
        free_svalue(inter_sp--);
    }
    else if (hook->type == T_STRING)
    {
        push_ref_string(inter_sp, prompt);
        (void)sapply(hook->u.str, command_giver, 1);
    }
    else
    {
        current_object = NULL; /* So that catch_tell() can see it */
        add_message(FMT_STRING, prompt);
    }
} /* print_prompt_string() */

/*-------------------------------------------------------------------------*/
void
print_prompt (void)

/* Print the prompt of the current command_giver, unless disabled
 * by input_to. If the prompt is set to a closure, the closure
 * is called and expected to return the actual prompt string or
 * to print the prompt itself.
 */

{
    interactive_t *ip;
    svalue_t *prompt = NULL;
    object_t * save_current = current_object;
    object_t * save_previous = previous_ob;
    Bool usingDefaultPrompt = MY_FALSE;

#ifdef DEBUG
    if (command_giver == 0)
        fatal("command_giver == 0.\n");
#endif

    if (!(O_SET_INTERACTIVE(ip, command_giver)))
        fatal("print_prompt() of non-interactive object\n");

    if (ip->input_handler != NULL)
    {
        prompt = &ip->input_handler->prompt;
    }
    else
    {
        prompt = &ip->prompt;
        if (prompt->type != T_CLOSURE && prompt->type != T_STRING)
        {
            prompt = &driver_hook[H_DEFAULT_PROMPT];
            usingDefaultPrompt = MY_TRUE;
        }
    }

    if (prompt->type == T_CLOSURE)
    {
        object_t *ob;

        /* Needed for clean error recovery */

        previous_ob = 0;
        current_object = command_giver;

        /* Check if the object the closure is bound to still exists.
         * If not, restore the prompt to the default (this also works with
         * the default prompt driver hook), then throw an error.
         */
        ob = !CLOSURE_MALLOCED(prompt->x.closure_type)
             ? prompt->u.ob
             : prompt->u.lambda->ob;

        if (ob && ob->flags & O_DESTRUCTED)
        {
            free_svalue(prompt);
            put_ref_string(prompt, STR_DEFAULT_PROMPT);
            print_prompt_string(prompt->u.str);
            errorf("Prompt of %s was a closure bound to a now-destructed "
                   "object - default prompt restored.\n", 
                   get_txt(command_giver->name));
            /* NOTREACHED */
        }

        call_lambda(prompt, 0);
        prompt = inter_sp;
        if (prompt->type != T_STRING)
        {
            free_svalue(prompt);
        }
        else
        {
            /* beware: print_prompt_string() might cause an error.
             * Thus, the LPC stack has to include the prompt to free it then.
             */
            print_prompt_string(prompt->u.str);
            free_svalue(prompt);
        }
        inter_sp--;
    }
    else if (prompt->type == T_STRING)
    {
        print_prompt_string(prompt->u.str);
    }
    else if (usingDefaultPrompt)
    {
        /* No prompt nor default prompt given, and it's not an input_to:
         * print the usual prompt.
         */
        print_prompt_string(STR_DEFAULT_PROMPT);
    }

    current_object = save_current;
    previous_ob = save_previous;
} /* print_prompt() */

/*-------------------------------------------------------------------------*/
static int
set_snoop (object_t *me, object_t *you)

/* Set a snoop from <me> on the IO of <you>. If <you> is NULL, an
 * existing snoop is terminated. <me> need not to be an interactive
 * user.
 *
 * Return 1 on success, -1 if a snooping loop would be caused, 0 for
 * any other failure.
 *
 * The function calls master->valid_snoop() to test if the snoop
 * is allowed.
 */

{
    interactive_t *on = NULL; /* interactive struct of <you> */
    interactive_t *by = NULL; /* interactive struct of <me> */
    interactive_t *tmp;
    svalue_t *ret;

    /* Stop if people managed to quit before we got this far */
    if (me->flags & O_DESTRUCTED)
        return 0;
    if (you && (you->flags & O_DESTRUCTED))
        return 0;

    /* Check for permissions with valid_snoop in master */
    push_ref_object(inter_sp, me, "snoop");
    if (you == NULL)
        push_number(inter_sp, 0);
    else
        push_ref_object(inter_sp, you, "snoop");
    ret = apply_master(STR_VALID_SNOOP, 2);

    if (!ret || ret->type != T_NUMBER || ret->u.number == 0)
        return 0;

    if (me->flags & O_DESTRUCTED)
        return 0;

    /* Test is <me> is able to snoop anyway.
     * Set <by> to <me>'s interactive struct if yes.
     */
    if (O_SET_INTERACTIVE(by, me) && by->closing)
        return 0;

    if (you)
    {
        /* Test if <you> can be snooped at all.
         * Set <on> to <you>'s interactive struct if yes.
         */
        if (you->flags & O_DESTRUCTED)
            return 0;
        if (!(O_SET_INTERACTIVE(on, you)) || on->closing)
            return 0;
    }
    else
    {
        /* Stop snoop.
         * For this, set <on> to the interactive struct of the snoops
         * victim. If <by> is NULL, <me> is propably a netdead user
         * or a NPC and we have to scan the list of users for the victim.
         */

        if (!by)
        {
            int i;

            for (i = max_player+1;;)
            {
                if (--i < 0)
                    return 0;
                if (NULL != (on = all_players[i]) && on->snoop_by == me)
                    break;
            }
            if (on->closing)
                return 0;
            free_object(me, "set_snoop");
        }
        else
        {
            on = by->snoop_on;
            if (!on || on->closing)
                return 0;
            by->snoop_on = NULL;
        }
        on->snoop_by = NULL;
        return 1;
    }

    /* If we come here, a snoop on <you> by <me> is possible.
     * Now protect against snooping loops.
     */

    for (tmp = on; tmp; tmp = tmp->snoop_on)
    {
        if (tmp == by)
            return -1;
    }

    /* Terminate previous snoop, if any */
    if (on->snoop_by)
    {
        interactive_t *ip;

        if (O_SET_INTERACTIVE(ip, on->snoop_by))
        {
            if (ip->closing)
                return 0;
            ip->snoop_on = 0;
        }
        else
        {
            free_object(on->snoop_by, "set_snoop");
        }
        on->snoop_by = NULL;
    }

    /* Initialise the new snoop */
    if (by)
    {
        if (by->snoop_on)
        {
            if (by->snoop_on->closing)
                return 0;
            by->snoop_on->snoop_by = 0;
            by->snoop_on = 0;
        }
        by->snoop_on = on;
    }
    else
    {
        ref_object(me, "set_snoop");
    }

    on->snoop_by = me;
    return 1;
} /* set_snoop() */

/*=========================================================================*/
/*                      Telnet Support
 */

/* Note: when stored in char variables, IAC can be equal to EOF.
 * This can cause sprintf(), which is used in add_message(), to abort
 * output after EOF. Therefore, don't try to send anything after the IAC
 * in the same call to add_message().
 */

/*-------------------------------------------------------------------------*/
static INLINE void
send_telnet_option (char action, char option)

/* Send IAC <action> <option> */

{
    char msg[3];

    msg[0] = IAC;
    msg[1] = action;
    msg[2] = option;
    SEND_TELNET_COMMAND(
      add_message(FMT_BINARY, msg, 3);
      add_message(message_flush);
    )
} /* send_telnet_option() */

/*-------------------------------------------------------------------------*/
static void
send_wont (int option)

/* Send IAC WONT <option> */

{
    DTF(("%s TDEBUG: send IAC WONT %02x\n", time_stamp(), option));
    send_telnet_option(WONT, option);
}

/*-------------------------------------------------------------------------*/
static void
send_dont (int option)

/* Send IAC DONT <option> */

{
    DTF(("%s TDEBUG: send IAC DONT %02x\n", time_stamp(), option));
    send_telnet_option(DONT, option);
}

/*-------------------------------------------------------------------------*/
static void
send_will (int option)

/* Send IAC WILL <option> */

{
    DTF(("%s TDEBUG: send IAC WILL %02x\n", time_stamp(), option));
    send_telnet_option(WILL, option);
}

/*-------------------------------------------------------------------------*/
static void
send_do (int option)

/* Send IAC DO <option> */

{
    DTF(("%s TDEBUG: send IAC DO %02x\n", time_stamp(), option));
    send_telnet_option(DO, option);
}

/*-------------------------------------------------------------------------*/
static void
reply_nil (int option UNUSED)

/* Dummy function which does nothing. */

{
#ifdef __MWERKS__
#    pragma unused(option)
#endif
}

/*-------------------------------------------------------------------------*/
static void
reply_to_do_echo (int option)

/* Send IAC WONT <option> if we don't want noecho mode.
 * If we requested WILL ECHO this is the client's reply. Set NOECHO_ACK. Send
 * no reply.  Send WILL ECHO if we want noecho but where told not to echo
 * (reactivate noecho mode).
 */

{
    interactive_t *ip = O_GET_INTERACTIVE(command_giver);

    DTN(("reply to DO ECHO\n"));
    if (ip->noecho & NOECHO_MASK) {
        if ( !(ip->noecho & NOECHO) ) {
            /* We were previously told not to echo */
            send_will(option);
        }
        else DTN(("  we don't need to say WILL\n"));
        /* If we already said that we will echo, be quiet */
        ip->noecho |= NOECHO_MASK;
    } else {
        send_wont(option);
    }
}

/*-------------------------------------------------------------------------*/
static void
reply_to_dont_echo (int option)

/* If we requested WONT ECHO this is the client's reply. Do nothing.
 * If client requests us to not echo while we want to, send WONT ECHO and
 * delete NOECHO flag. The client may turn the option on again later.
 */

{
    interactive_t *ip = O_GET_INTERACTIVE(command_giver);

    DTN(("reply to DONT ECHO\n"));
    if (ip->noecho & NOECHO_MASK) {
        if (!~(ip->noecho | ~NOECHO_MASK)) {
            /* We were granted the option before */
            send_wont(option);
        }
        else DTN(("  we don't need to say WONT\n"));
        ip->noecho = (char)((ip->noecho & ~NOECHO) | NOECHO_ACK);
    }
}

/*-------------------------------------------------------------------------*/
static void
reply_to_do_sga (int option)

/* Send IAC WILL <option> if Suppress Go Ahead is not already active and
 * mark it as active, send IAC WONT <option> if neither in NOECHO or
 * CHARMODE mode.
 */

{
    interactive_t *ip = O_GET_INTERACTIVE(command_giver);

    DTN(("reply to DO SGA\n"));
    if (ip->noecho & (NOECHO_MASK|CHARMODE_MASK)) {
        if (!ip->supress_go_ahead) {
            ip->supress_go_ahead = MY_TRUE;
            send_will(option);
        }
        else DTN(("  we don't need to say WILL\n"));
    } else {
        send_wont(option);
    }
}

/*-------------------------------------------------------------------------*/
static void
reply_to_dont_sga (int option)

/* Send IAC WONT <option> if Suppress Go Ahead is active and mark it as
 * inactive.
 */

{
    interactive_t *ip = O_GET_INTERACTIVE(command_giver);

    DTN(("reply to DONT SGA\n"));
    if (ip->supress_go_ahead) {
        ip->supress_go_ahead = 0;
        send_wont(option);
    }
}

/*-------------------------------------------------------------------------*/
static void
reply_to_will_sga (int option)

/* Send IAC DO <option> if CHARMODE is requested but not active yet,
 * send IAC DONT <option> if CHARMODE is neither requested nor active.
 */

{
    interactive_t *ip = O_GET_INTERACTIVE(command_giver);

    DTN(("reply to WILL SGA\n"));
    if (ip->noecho & CHARMODE_MASK) {
        if ( !(ip->noecho & CHARMODE) ) {
            send_do(option);
        }
        else DTN(("  we don't need to say DO\n"));
        DTN(("  noecho: %02hhx -> %02hhx\n", ip->noecho, (char)(ip->noecho  | CHARMODE_MASK)));
        ip->noecho |= CHARMODE_MASK;
    } else {
        send_dont(option);
    }
#ifdef SAVE_NOECHO
    if (ip->noecho & NOECHO_DELAYED)
    {
        DT(("'%s' set_noecho():   sending delayed WILL TELOPT_ECHO\n",
            get_txt(ip->ob->name)));
        ip->noecho &= ~NOECHO_DELAYED;
        if (!(ip->noecho & NOECHO_MASK))
        {
            send_will(TELOPT_ECHO);
            ip->noecho |= NOECHO_REQ | NOECHO;
        }
        else DT(("'%s'   we don't need to say WILL\n", get_txt(ip->ob->name)));
    }
#endif /* SAVE_NOECHO */
} /* reply_to_will_sga() */

/*-------------------------------------------------------------------------*/
static void
reply_to_wont_sga (int option)

/* Send IAC DONT <option> if CHARMODE was granted before.
 */

{
    interactive_t *ip = O_GET_INTERACTIVE(command_giver);

    DTN(("reply to WONT SGA\n"));
    if (ip->noecho & CHARMODE_MASK) {
        if (!~(ip->noecho | ~CHARMODE_MASK)) {
            /* We were granted the option before */
            send_dont(option);
        }
        else DTN(("  we don't need to say DONT\n"));
        DTN(("  noecho: %02hhx -> %02hhx\n", ip->noecho, 
             (unsigned char)((ip->noecho & ~CHARMODE) | CHARMODE_ACK)));
        ip->noecho = (char)((ip->noecho & ~CHARMODE) | CHARMODE_ACK);
          /* Don't reset CHARMODE_REQ here: this WONT can be the answer
           * to the DO SGA we sent before, and the client can still answer
           * with DO SGA to the WILL SGA we sent as well (Windows' telnet
           * for example does this).
           * Besides, the variables are now set up to treat the input
           * in charmode, and changing the flag without the variables
           * will do Bad Things(tm).
           */
    }
} /* reply_to_wont_sga() */

/*-------------------------------------------------------------------------*/
static void
mccp_telnet_neg (int option)
{
    interactive_t *ip = O_GET_INTERACTIVE (command_giver);
    
     switch (ip->tn_state)
     {
     case TS_WILL:
         DTF(("MCCP NEG (%d) STATE (WILL)\n", option));
         break;
     case TS_WONT:
         DTF(("MCCP NEG (%d) STATE (WONT)\n", option));
         break;
     case TS_DO:
         DTF(("MCCP NEG (%d) STATE (DO)\n", option));
#ifdef USE_MCCP
         if (!ip->compressing)
             start_compress(ip, option);
#endif
         break;
     case TS_DONT:
         DTF(("MCCP NEG (%d) STATE (DONT)\n", option));
#ifdef USE_MCCP
         if (ip->compressing==option)
             end_compress(ip, MY_FALSE);
#endif
        break;
     default:
         DTF(("MCCP NEG (%d) STATE (%hhd)\n", option, ip->tn_state));
     }
} /* mccp_telnet_neg() */

/*-------------------------------------------------------------------------*/
static svalue_t *
h_telnet_neg (int n)

/* Call the H_TELNET_NEG driverhook with <n> arguments on the interpreter
 * stack. Return the result from that call, or NULL if the hook isn't
 * set. The arguments are removed from the stack in any case.
 */

{
    svalue_t *svp;

    RESET_LIMITS;
    CLEAR_EVAL_COST;
    if (driver_hook[H_TELNET_NEG].type == T_STRING)
    {
        svp =
          secure_callback(driver_hook[H_TELNET_NEG].u.str, command_giver, n);
    }
    else if (driver_hook[H_TELNET_NEG].type == T_CLOSURE)
    {
        if (driver_hook[H_TELNET_NEG].x.closure_type == CLOSURE_LAMBDA)
        {
            free_object(driver_hook[H_TELNET_NEG].u.lambda->ob, "h_telnet_neg");
            driver_hook[H_TELNET_NEG].u.lambda->ob = ref_object(command_giver, "h_telnet_neg");
        }
        svp = secure_callback_lambda(&driver_hook[H_TELNET_NEG], n);
    }
    else
    {
        while (--n >= 0)
            pop_stack();
        svp = NULL;
    }
    return svp;
}

/*-------------------------------------------------------------------------*/
static void
reply_h_telnet_neg (int option)

/* Call the H_TELNET_NEG driver hook with <tn_state> <option> as
 * arguments. If the hook is not defined, send WONT <option> if
 * the state is TS_DO, or send DONT <option> if the state is TS_WILL.
 */

{
    interactive_t *ip = O_GET_INTERACTIVE(command_giver);
    int i = 0;

    switch(ip->tn_state) {
      case TS_DO:
        DTN(("reply to telnet_neg: DO %02x\n", option));
        i = DO;
        break;
      case TS_DONT:
        DTN(("reply to telnet_neg: DONT %02x\n", option));
        i = DONT;
        break;
      case TS_WILL:
        DTN(("reply to telnet_neg: WILL %02x\n", option));
        i = WILL;
        break;
      case TS_WONT:
        DTN(("reply to telnet_neg: WONT %02x\n", option));
        i = WONT;
        break;
      default:
        debug_message("%s Invalid tn_state %hhd for interactive '%s'\n"
             , time_stamp(), ip->tn_state, get_txt(ip->ob->name));
        break;
    }
    push_number(inter_sp, i);
    push_number(inter_sp, option);
    if (!h_telnet_neg(2)) {
        DTN(("  using default methods\n"));
        switch(ip->tn_state) {
          case TS_DO:
            DTN(("    -> WONT %02x\n", option));
            send_wont(option);
            break;
          case TS_WILL:
            DTN(("    -> DONT %02x\n", option));
            send_dont(option);
            break;
        }
    }
} /* reply_h_telnet_neg() */

/*-------------------------------------------------------------------------*/
void
init_telopts (void)

/* Initialise the telopts_xxx[] tables.
 * The default setting is such that requests are ignored or rejected.
 */

{
    int i;

    for (i = NTELOPTS; --i >= 0; ) {
        telopts_do[i] = send_wont;
    }
    for (i = NTELOPTS; --i >= 0; ) {
        telopts_dont[i] = reply_nil;
    }
    for (i = NTELOPTS; --i >= 0; ) {
        telopts_will[i] = send_dont;
    }
    for (i = NTELOPTS; --i >= 0; ) {
        telopts_wont[i] = reply_nil;
    }

    telopts_do[TELOPT_ECHO] = reply_to_do_echo;
    telopts_dont[TELOPT_ECHO] = reply_to_dont_echo;

    telopts_do[TELOPT_TM] = reply_h_telnet_neg;
    telopts_dont[TELOPT_TM] = reply_h_telnet_neg;
    telopts_will[TELOPT_TM] = reply_h_telnet_neg;
    telopts_wont[TELOPT_TM] = reply_h_telnet_neg;

    telopts_do[TELOPT_NEWENV] = reply_h_telnet_neg;
    telopts_dont[TELOPT_NEWENV] = reply_h_telnet_neg;
    telopts_will[TELOPT_NEWENV] = reply_h_telnet_neg;
    telopts_wont[TELOPT_NEWENV] = reply_h_telnet_neg;

    telopts_do[TELOPT_ENVIRON] = reply_h_telnet_neg;
    telopts_dont[TELOPT_ENVIRON] = reply_h_telnet_neg;
    telopts_will[TELOPT_ENVIRON] = reply_h_telnet_neg;
    telopts_wont[TELOPT_ENVIRON] = reply_h_telnet_neg;

    telopts_do[TELOPT_XDISPLOC] = reply_h_telnet_neg;
    telopts_dont[TELOPT_XDISPLOC] = reply_h_telnet_neg;
    telopts_will[TELOPT_XDISPLOC] = reply_h_telnet_neg;
    telopts_wont[TELOPT_XDISPLOC] = reply_h_telnet_neg;

    telopts_do[TELOPT_LINEMODE] = reply_h_telnet_neg;
    telopts_dont[TELOPT_LINEMODE] = reply_h_telnet_neg;
    telopts_will[TELOPT_LINEMODE] = reply_h_telnet_neg;
    telopts_wont[TELOPT_LINEMODE] = reply_h_telnet_neg;

    telopts_do[TELOPT_NAWS] = reply_h_telnet_neg;
    telopts_dont[TELOPT_NAWS] = reply_h_telnet_neg;
    telopts_will[TELOPT_NAWS] = reply_h_telnet_neg;
    telopts_wont[TELOPT_NAWS] = reply_h_telnet_neg;

    telopts_do[TELOPT_TTYPE] = reply_h_telnet_neg;
    telopts_dont[TELOPT_TTYPE] = reply_h_telnet_neg;
    telopts_will[TELOPT_TTYPE] = reply_h_telnet_neg;
    telopts_wont[TELOPT_TTYPE] = reply_h_telnet_neg;

    telopts_do[TELOPT_TSPEED] = reply_h_telnet_neg;
    telopts_dont[TELOPT_TSPEED] = reply_h_telnet_neg;
    telopts_will[TELOPT_TSPEED] = reply_h_telnet_neg;
    telopts_wont[TELOPT_TSPEED] = reply_h_telnet_neg;

    telopts_do[TELOPT_BINARY] = reply_h_telnet_neg;
    telopts_dont[TELOPT_BINARY] = reply_h_telnet_neg;
    telopts_will[TELOPT_BINARY] = reply_h_telnet_neg;
    telopts_wont[TELOPT_BINARY] = reply_h_telnet_neg;

    /* Tinyfugue can do bad things to your health */
    telopts_do[TELOPT_EOR] = reply_h_telnet_neg;
    telopts_dont[TELOPT_EOR] = reply_h_telnet_neg;
    telopts_will[TELOPT_EOR] = reply_h_telnet_neg;
    telopts_wont[TELOPT_EOR] = reply_h_telnet_neg;

    /* Go Ahead does not make any sense when coupling multiple
     * interactive users. It is debatable if we are sending
     * Go Ahead every time it is appropriate (i.e. , never),
     * or we supress it all the time.
     * Unfortunately, SGA is also often associated with
     * character-at-a-time mode - the RFC even mandates this
     * double meaning - which we certainly don't want.
     * It might cause problems when we reject Supress Go Ahead
     * when some stupid client thinks that the ECHO option need
     * be coupled with SGA .
     * Thus, reject SGA in general, but not while
     * ip->noecho & NOECHO_MASK is true.
     */
    telopts_do[TELOPT_SGA] = reply_to_do_sga;
    telopts_dont[TELOPT_SGA] = reply_to_dont_sga;
    telopts_will[TELOPT_SGA] = reply_to_will_sga;
    telopts_wont[TELOPT_SGA] = reply_to_wont_sga;

    /* Mud specific protocols */

    telopts_do[TELOPT_COMPRESS] = mccp_telnet_neg;
    telopts_dont[TELOPT_COMPRESS] = mccp_telnet_neg;
    telopts_will[TELOPT_COMPRESS] = mccp_telnet_neg;
    telopts_wont[TELOPT_COMPRESS] = mccp_telnet_neg;

    telopts_do[TELOPT_COMPRESS2] = mccp_telnet_neg;
    telopts_dont[TELOPT_COMPRESS2] = mccp_telnet_neg;
    telopts_will[TELOPT_COMPRESS2] = mccp_telnet_neg;
    telopts_wont[TELOPT_COMPRESS2] = mccp_telnet_neg;

    telopts_do[TELOPT_MSP] = reply_h_telnet_neg;
    telopts_dont[TELOPT_MSP] = reply_h_telnet_neg;
    telopts_will[TELOPT_MSP] = reply_h_telnet_neg;
    telopts_wont[TELOPT_MSP] = reply_h_telnet_neg;

    telopts_do[TELOPT_MXP] = reply_h_telnet_neg;
    telopts_dont[TELOPT_MXP] = reply_h_telnet_neg;
    telopts_will[TELOPT_MXP] = reply_h_telnet_neg;
    telopts_wont[TELOPT_MXP] = reply_h_telnet_neg;

    telopts_do[TELOPT_STARTTLS] = reply_h_telnet_neg;
    telopts_dont[TELOPT_STARTTLS] = reply_h_telnet_neg;
    telopts_will[TELOPT_STARTTLS] = reply_h_telnet_neg;
    telopts_wont[TELOPT_STARTTLS] = reply_h_telnet_neg;
} /* init_telopts() */

/*-------------------------------------------------------------------------*/
void
mudlib_telopts (void)

/* Set all telopts_xxx[] entries to reply_h_telnet_neg().
 * This means that the mudlib does all the telnet negotiation.
 * It is called whenever driver hook H_NOECHO is set.
 */

{
    int i;

    DT(("All telnet options set to the mudlib.\n"));
    for (i = NTELOPTS; --i >= 0; ) {
        telopts_do[i] = telopts_dont[i] =
          telopts_will[i] = telopts_wont[i] = reply_h_telnet_neg;
    }
} /* mudlib_telopts() */

/*-------------------------------------------------------------------------*/
static INLINE Bool
is_charmode (interactive_t * ip)

/* Return TRUE if <ip> has charmode enabled, and FALSE if not.
 */

{
    return (ip->noecho & CHARMODE_REQ)
            && (   ip->text[0] != input_escape
                || find_no_bang(ip) & IGNORE_BANG
               )
               
    ;
} /* is_charmode() */

/*-------------------------------------------------------------------------*/
static void
telnet_neg (interactive_t *ip)

/* Process the data read from the socket, performing any telnet negotiations
 * necessary, and extract the 'pure' command text. When the function returns,
 * all new data in .text[] has been used and .text_end set back as far
 * as possible.
 *
 * The start state for the telnet machine is TS_DATA, and whenever a command
 * text has been completed, it assumes the TS_READY state.
 *
 * The function tn_end and goes on until it reaches text_end or a full newline.
 *
 * When it returns:
 *   tn_end is set to the first unprocessed character.
 * When a full newline is found:
 *   Processed commands start at command_start and are \0 terminated strings
 *    state is set to READY
 * else
 *   Processed commands start at command_start and end at command_end-1
 *   state is set to DATA (or something else if we got a fragmented
 *     telnet negotiation).
 *
 * text_end could move a bit to the start of text if we deleted chars
 * from the raw input string (e.g. because it was an IAC).
 *
 * If gobble_char is set, that char is removed from a fresh text packet.
 * Removing of unwanted chars inside of a packet is done at the appropriate
 * place (case '\r':). There is no gobbling of <LN><CR> sequences in
 * character mode (why not?). Code would have to be added at case '\n':
 * to gobble them in-packet.
 *
 * Example:
 * text = "say hello\r\nsay hi\r\n";
 * 
 * Output would be:
 * text = "say hello\0\nsay hi\r\n";
 * 
 * command_start = 0
 * command_end = 0
 * tn_end = 11 (s of 2nd say)
 * text_end stays at 19 (first unused char in text)
 * state = TS_READY
 *
 * After a second call of telnet_neg (almost always done by get_message())
 * will pre process the second command:
 *
 * text = "say hi\0lo\0\nsay hi\r\n";
 * 
 * command_start = 0
 * command_end = 0
 * tn_end = 7
 * text_end = 7
 * state = READY
 */

{
    fd_set exceptfds;
    char *from;   /* Next char to process */
    char *to;     /* Where to store the extracted command text */
    int   state;
    int   ch;     /* Current character */
    char *first;  /* Begin of the last pure command text */
    char *end;    /* End of data in text[] */

    first = ip->text;
    from = &first[ip->tn_end];
    end = &first[ip->text_end];

    DTN(("telnet_neg: state %hhd\n", ip->tn_state));

    /* Gobble the character *from if gobble_char is set.
     * Also test for the end of current buffer content.
     *
     * If we want to gobble NL, we also gobble NUL
     * (used for CR NL and CR NUL digraphs)
     */
    for (;;)
    {
        if (from >= end) {
#if 0 /* cannot happen with the current calling pattern */
            if (ip->state == TS_READY) return;
#endif
            ip->text_end = ip->tn_end = ip->command_end;
            return;
        }
        if (ip->gobble_char) {
            DTN(("t_n: gobble char %02hhx (in buf: %02x)\n"
               , ip->gobble_char, *from));
            if (*from == ip->gobble_char
                || (*from == '\0' && ip->gobble_char == '\n')
               )
            {
                from++;
            }
            ip->gobble_char = '\0';
            continue;
        }
        break;
    }
    to = &first[ip->command_end];

    /* The processing loop */

    do {
        ch = (*from++ & 0xff);
        DTN(("t_n: processing %02hhx '%c'\n"
            , (unsigned char)ch, ch));
        switch(ip->tn_state)
        {
        case TS_READY:
            DTN(("t_n: still in TS_READY - return\n"));
            /* Previous command hasn't been read yet - don't clobber it! */
            return;

        ts_data:
            /* Most state functions end with a jump here to check if they
             * exhausted their input.
             */
            if (from >= end)
            {
                ip->text_end = ip->tn_end = ip->command_end = (short)(to - first);
                 DTN(("t_n: (ts_data) from >= end by %td, text_end := %hd\n (max %d)"
                     , (ptrdiff_t)(from-end), ip->text_end, MAX_TEXT));
                *to = '\0';
                if (ip->text_end >= MAX_TEXT)
                {
                    /* this looks like a super-long command.
                     * Return the text so far as partial command and restart
                     * input from the beginning.
                     * In charmode, we must not reset command_end, otherwise
                     * it might fall under command_start.
                     */
                    ip->tn_state = TS_READY;
                    ip->tn_end = 0;
                    ip->text_end = 0;
                    if (!(ip->noecho & (CHARMODE_REQ|CHARMODE)))
                    {
                        ip->command_end = 0;
                    }
                    return;
                }
                return;
            }
            ch = (*from++ & 0xff);
            DTN(("t_n: (ts_data) processing %02hhx '%c'\n"
                , (unsigned char)ch, ch));
            /* FALLTHROUGH */

        case TS_DATA: /* --- Copy/interpret plain data --- */
            switch(ch)
            {
            case IAC:
            new_iac:
                  if (ip->tn_enabled)
                  {
                      state = TS_IAC;
        change_state:
                      DTN(("t_n: new state %d\n", state));
                      ip->tn_state = (char)state;
                      continue;
                  }
                  /* FALLTHROUGH if !tn_enabled */

            case '\b':        /* Backspace */
            case 0x7f:        /* Delete */
                /* In Linemode, just move to one char back.
                 * In Charmode with escaped input, write the data gathered
                 * so far and add a rubout sequence ('\b \b').
                 * In Charmode with unescaped input, just pass it on to
                 * the mudlib.
                 *
                 * If telnet is disabled, fallthrough to the general
                 * data handling.
                 */
                if (ip->tn_enabled)
                {
                    if ( !(ip->noecho & CHARMODE_REQ) )
                    {
                        if (to > first)
                            to--;
                        goto ts_data;
                    }

                    if (ip->text[0] == input_escape
                     && ! (find_no_bang(ip) & IGNORE_BANG) )
                    {
                        if (to > &ip->text[ip->chars_ready])
                        {
                            comm_socket_write(&ip->text[ip->chars_ready]
                                            , (size_t)(to - &ip->text[ip->chars_ready])
                                            , ip, 0);
                            ip->chars_ready = to - ip->text;
                        }
                        if (to > first)
                        {
                            comm_socket_write("\b \b", 3, ip, 0);
                            to--;
                            ip->chars_ready--;
                        }
                        goto ts_data;
                    }
                } /* if (ip->tn_enabled) */
                /* FALLTHROUGH */

            default:
                *to++ = (char)ch;
                /* FALLTHROUGH */

            case '\0':
                /* In Charmode, we should return the \0 (as with CR and LF),
                 * but for the caller the \0 has magical properties.
                 */
                goto ts_data;

            case '\r':
                /* In Charmode we have to return the \r.
                 */
                if (is_charmode(ip))
                {
                    *to++ = (char)ch;
                    goto ts_data;
                }

                if (from >= end)
                {
                    /* This might be a fragmented CR NL, CR NUL, or
                     * a broken client that ends lines with CR only.
                     * We proceed as full newline now, but gobble
                     * NL or NUL if they are sent afterwards.
                     */
                    ip->gobble_char = '\n';
                }
                else
                {
                    ch = (*from++ & 0xff);
                    /* gobble following NL and NUL */
                    if (ch && ch != '\n')
                        from--;
                }

        full_newline:
                /* Proper line end found: set telnet machine into TS_READY,
                 * terminate the command with \0 and return.
                 */
                {
                    ip->tn_state = TS_READY;
                    ip->command_end = 0;
                    ip->tn_end = (short)(from - first);

                    /* Even in charmode we append the NUL in case the client
                     * refused to use charmode, because then get_message()
                     * will treat the data as if in linemode and expect
                     * a trailing NUL.
                     */
                    *to = '\0';
                    return;
                }

            case '\n':
                /* In Charmode we have to return the \n.
                 */
                if (is_charmode(ip))
                {
                    *to++ = (char)ch;
                    goto ts_data;
                }

                ip->gobble_char = '\r';
                goto full_newline;
            } /* switch(ch) */

            /* NOTREACHED */

        ts_iac:
        case TS_IAC:
            DTN(("t_n: state IAC\n"));
            /* Begin a telnet negotiation */
            switch(ch)
            {
            case IAC:
                DTN(("t_n: got IAC\n"));
                *to++ = ch;
                ip->tn_state = state = TS_DATA;
                goto ts_data;
            case WILL:
                DTN(("t_n: got WILL\n"));
                state = TS_WILL;
                goto change_state;
            case WONT:
                DTN(("t_n: got WONT\n"));
                state = TS_WONT;
                goto change_state;
            case DO:
                DTN(("t_n: got DO\n"));
                state = TS_DO;
                goto change_state;
            case DONT:
                DTN(("t_n: got DONT\n"));
                state = TS_DONT;
                goto change_state;
            case SB:
                DTN(("t_n: got SB\n"));
                ip->tn_start = (short)(to - first);
                state = TS_SB;
                goto change_state;
            case DM:
                DTN(("t_n: got DM\n"));
            data_mark:
                if (ip->ts_data == TS_SYNCH)
                {
                    struct timeval timeout;

                    FD_ZERO(&exceptfds);
                    FD_SET(ip->socket, &exceptfds);
                    timeout.tv_sec = 0;
                    timeout.tv_usec = 0;
                    if (! socket_select(ip->socket + 1, 0, 0, &exceptfds,
                        &timeout))
                    {
                        if (d_flag)
                            debug_message("%s Synch operation finished.\n"
                                         , time_stamp());
                        ip->ts_data = TS_DATA;
                    }
                }
                break;
            case NOP:
                DTN(("t_n: got NOP\n")); break;
            case GA:
                DTN(("t_n: got GA\n")); break;
            default:
                DTN(("t_n: got %02hhx\n", ch)); break;
                break;
            } /* switch(ch) */
            state = ip->ts_data;
            goto change_state;

        case TS_WILL:
            command_giver = ip->ob;
            if (ch < NTELOPTS) {
                DTN(("t_n: state WILL got %s (%02hhx)\n"
                   , telopts[ch], ch));
                if (d_flag)
                    debug_message("%s Will %s\n", time_stamp(), telopts[ch]);
                (*telopts_will[ch])(ch);
            } else {
                debug_message("%s Unknown telnet option Will %hhd\n"
                             , time_stamp(), ch);
                send_dont(ch);
            }
            state = ip->ts_data;
            goto change_state;

        case TS_WONT:
            command_giver = ip->ob;
            if (ch < NTELOPTS) {
                DTN(("t_n: state WONT got %s (%02hhx)\n"
                   , telopts[ch], ch));
                if (d_flag)
                    debug_message("%s Wont %s\n", time_stamp(), telopts[ch]);
                (*telopts_wont[ch])(ch);
            } else {
                debug_message("%s Unknown telnet option Wont %hhd\n"
                             , time_stamp(), ch);
            }
            state = ip->ts_data;
            goto change_state;

        case TS_DO:
            command_giver = ip->ob;
            if (ch < NTELOPTS) {
                DTN(("t_n: state DO got %s (%02hhx)\n"
                   , telopts[ch], ch));
                if (d_flag)
                    debug_message("%s Do %s\n", time_stamp(), telopts[ch]);
                (*telopts_do[ch])(ch);
            } else {
                debug_message("%s Unknown telnet option Do %hhd\n"
                             , time_stamp(), ch);
                send_wont(ch);
            }
            state = ip->ts_data;
            goto change_state;

        case TS_DONT:
            command_giver = ip->ob;
            if (ch < NTELOPTS) {
                DTN(("t_n: state DONT got %s (%02hhx)\n"
                   , telopts[ch], ch));
                if (d_flag)
                    debug_message("%s Dont %s\n", time_stamp(), telopts[ch]);
                (*telopts_dont[ch])(ch);
            } else {
                debug_message("%s Unknown telnet option Dont %hhd\n"
                             , time_stamp(), ch);
            }
            state = ip->ts_data;
            goto change_state;

        case TS_SB:
            DTN(("t_n: state TS_SB got %02hhx\n", ch));
            if (ch == IAC) {
                state = TS_SB_IAC;
                goto change_state;
            }
            *to++ = (char)ch;
            continue;

        case TS_SB_IAC:
          {
            mp_int size;
            vector_t *v;

            DTN(("t_n: state TS_SB_IAC got %02hhx\n", ch));
            if (ch == IAC) {
                DTN(("t_n: that is: state TS_SB_IAC got IAC\n"));
                *to++ = (char)ch;
                state = TS_SB;
                goto change_state;
            } else if ((ch == SE || ch == SB)
                && (  (size = (to - first) - ip->tn_start - 1) <= (mp_int)max_array_size
                    || !max_array_size)
                && size >= 0
                && (current_object = ip->ob,  v = allocate_array(size)) )
            {
                unsigned char *str;
                svalue_t *svp;

                str = (unsigned char *)&ip->text[ip->tn_start];
                DTN(("t_n: that is: state TS_SB_IAC got useful SE or SB: neg SB %02hhx (%"PRIdMPINT" bytes)\n", *str, size));
                push_number(inter_sp, SB);
                push_number(inter_sp, *str++);
                svp = v->item;
                while (--size >= 0) {
                    svp->u.number = *str++;
                    svp++;
                }
                push_array(inter_sp, v);
                command_giver = ip->ob;
                h_telnet_neg(3);
            }
            to = &first[ip->tn_start];
            if (ch != SE)
                goto ts_iac;
            state = ip->ts_data;
            goto change_state;
          }

        case TS_SYNCH:
            DTN(("t_n: state TS_SYNCH got %02hhx\n", ch));
            if (ch == IAC) goto new_iac;
            if (ch == DM) goto data_mark;
            continue;

        default:
            if (d_flag)
                debug_message("%s Bad state: 0x%hhx\n", time_stamp(), ip->tn_state);
            state = TS_DATA;
            goto change_state;
        } /* switch (ip->tn_state) */

    } while(from < end);

    /* We used all the new data in .text[] but found no complete command.
     * Reset all pointers necessary to read new data.
     */

    ip->text_end = ip->tn_end = ip->command_end = (short)(to - first);
    if (ip->text_end == MAX_TEXT)
    {
        /* telnet negotiation shouldn't have such large data chunks.
         * Ignore all data altogether and return to text mode.
         */
        ip->text_end = ip->tn_end = ip->command_end = 0;
        ip->tn_start = ip->command_start = 0;
        ip->tn_state = TS_DATA;
    }
} /* telnet_neg() */

/* End of Telnet support */
/*=========================================================================*/
/*                      ERQ Support
 */

#ifdef ERQ_DEMON
/*-------------------------------------------------------------------------*/
void
start_erq_demon (const char *suffix, size_t suffixlen)

/* Start the ERQ demon from the path 'ERQFILE<suffix>' and setup
 * the pending_erq[] array.
 */

{
    erq_callback_t *erqp;
    char path[MAXPATHLEN+1];
    int sockets[2];
    int pid, i;
    char c;

    /* Create the freelist in pending_erq[] */
    pending_erq[0].fun.type = T_INVALID;
    pending_erq[0].fun.u.generic = NULL;

    erqp = pending_erq + 1;
    while (erqp < &pending_erq[MAX_PENDING_ERQ])
    {
        erqp->fun.u.generic = (void *)(erqp - 1);
        erqp->fun.type = T_INVALID;
        erqp++;
    }
    free_erq = &pending_erq[MAX_PENDING_ERQ-1];

    /* Create the sockets to talk to the ERQ */
/* TODO: Add tests to configure if the system really implements AF_UNIX or socketpair() */
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
    {
        perror("socketpair");
        return;
    }

    (void)signal(SIGCLD, SIG_IGN); /* don't create zombie processes */

    printf("%s Attempting to start erq '%s%s'.\n"
          , time_stamp(), erq_file, suffix);
    debug_message("%s Attempting to start erq '%s%s'.\n"
                 , time_stamp(), erq_file, suffix);

    /* Close inherited sockets first. */
    for (i = 0; i < numports; i++)
        if (port_numbers[i] < 0)
            set_close_on_exec(-port_numbers[i]);

    if ((pid = fork()) == 0)
    {
        /* Child */
        dup2(sockets[0], 0);
        dup2(sockets[0], 1);
        close(sockets[0]);
        close(sockets[1]);

        if (strlen(erq_file) + 1 + suffixlen <= sizeof path)
        {
            sprintf(path, "%s%.*s", erq_file, (int)suffixlen, suffix);
            if (erq_args)
                execv((char *)path, erq_args);
            else
                execl((char *)path, "erq", "--forked", (char*)0);
        }
        write(1, "0", 1);  /* indicate failure back to the driver */
        _exit(1);
    }

    close(sockets[0]);
    if (pid == -1) {
        close(sockets[1]);
        return;
    }

    /* Read the first character from the ERQ. If it's '0', the ERQ
     * didn't start.
     */
    read(sockets[1], &c, 1);
    if (c == '0') {
        close(sockets[1]);

        printf("%s Failed to start erq.\n", time_stamp());
        debug_message("%s Failed to start erq.\n", time_stamp());
        return;
    }

    /* ERQ is up and running */
    erq_demon = sockets[1];
    set_socket_nonblocking(erq_demon);
    if (socket_number(erq_demon) >= min_nfds)
        min_nfds = socket_number(erq_demon)+1;
} /* start_erq_demon() */

/*-------------------------------------------------------------------------*/
static void
shutdown_erq_demon (void)

/* Close the connection to the ERQ.
 * This method is to be used directly only on game shutdown, otherwise
 * use stop_erq_demon() instead.
 */

{
    if (erq_demon < 0)
        return;

    socket_close(erq_demon);
    erq_demon = FLAG_NO_ERQ;
    erq_pending_len = 0;
    input_from_erq = &buf_from_erq[0];
} /* shutdown_erq_demon() */

/*-------------------------------------------------------------------------*/
static void
stop_erq_demon (Bool notify)

/* Close the connection to the ERQ and inform all pending requests
 * about this. If <notify> is set, the hook H_ERQ_STOP is called.
 */

{
    erq_callback_t *erqp;
    int i;

    if (erq_demon < 0)
        return;

    shutdown_erq_demon();

    /* Inform all pending requests about the loss.
     */
    erqp = pending_erq;
    i = MAX_PENDING_ERQ;
    do {
        if (erqp->fun.type == T_CLOSURE)
        {
            *++inter_sp = erqp->fun;
            erqp->fun.type = T_INVALID;
            erqp->fun.u.generic = (void *)free_erq;
            free_erq = erqp;
            CLEAR_EVAL_COST;
            RESET_LIMITS;
            callback_master(STR_STALE_ERQ, 1);
        }
        erqp++;
    } while (--i);

    /* If desired, call H_ERQ_STOP to notify the situation.
     */
    if (notify)
    {
        RESET_LIMITS;
        CLEAR_EVAL_COST;
        if (driver_hook[H_ERQ_STOP].type == T_CLOSURE) {
            secure_callback_lambda(&driver_hook[H_ERQ_STOP], 0);
        }
    }
} /* stop_erq_demon() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_attach_erq_demon (svalue_t *sp)

/* EFUN: attach_erq_demon()
 *
 *   int attach_erq_demon(object ob, int do_close)
 *   int attach_erq_demon(string name, int do_close)
 *
 * In the first form, take away the connection from <ob> and store it as
 * _the_ erq connection. <ob> thus becomes a normal non-interactive object.
 * In the second form, try to start the ERQ demon from the path
 * 'ERQFILE<name>' (ERQFILE defaults to BINDIR/erq). <name> must not
 * contain '/..' sequences.
 *
 * If there is already an ERQ demon connected to the driver, the function
 * will fail unless <do_close> is set to 1 or any other odd integer; in
 * this case the connection to the old ERQ will be closed first.
 *
 * Return svalue.number 1 on success, 0 else.
 */

{
    object_t *ob;
    interactive_t *ip;
    string_t *suffix;

    /* Test for the first form: (object ob, int do_close) */
    if (sp[-1].type == T_OBJECT)
    {
        ob = sp[-1].u.ob;
        if (!O_SET_INTERACTIVE(ip, ob))
        {
            errorf("Bad arg 1 to attach_erq_demon(): object is not interactive.\n");
            /* NOTREACHED */
            return sp;
        }

        sp--;
        deref_object(ob, "attach_erq_demon");
        put_number(sp, 0);
        /* we need to read sp[1] below, thus don't overwrite it now. */
        if (privilege_violation4(STR_ATTACH_ERQ_DEMON,
            ob, 0, sp[1].u.number, sp+1))
        {
            if (erq_demon != FLAG_NO_ERQ) {
                if (sp[1].u.number & 1) {
                    stop_erq_demon(0);
                    erq_demon = FLAG_ERQ_STOP;
                } else {
                    return sp;
                }
            }
            erq_proto_demon = ip->socket;
            ip->do_close = FLAG_PROTO_ERQ;
            sp->u.number = 1;
        }
        return sp;
    }

    /* Otherwise the argument is a string */

    suffix = sp[-1].u.str;
    if (mstrstr(suffix, "/.."))
    {
        errorf("Bad arg 1 to attach_erq_demon(): illegal path.\n");
        /* NOTREACHED */
        return sp;
    }

    {
        int n;

        sp--;
        n = 0;
        if (privilege_violation4(STR_ATTACH_ERQ_DEMON,
            0, suffix, sp[1].u.number, sp+1))
        {
            if (erq_demon != FLAG_NO_ERQ)
            {
                if (sp[1].u.number & 1) {
                    stop_erq_demon(0);
                } else {
                    goto return_result;
                }
                erq_proto_demon = -1;
            }
            start_erq_demon(get_txt(suffix), mstrsize(suffix));
            n = 1;
        }
return_result:
        free_svalue(sp);
        put_number(sp, n);
        return sp;
    }

    /* NOTREACHED */
    return NULL;
} /* f_attach_erq_demon() */

/*-------------------------------------------------------------------------*/
static Bool
send_erq (int handle, int request, const char *arg, size_t arglen)

/* Send compose an ERQ message out of <handle>, <request> and <arg>
 * and send it to the ERQ. If all the data can't be sent now, the
 * next call to send_erq() will send the rest.
 *
 * Return FALSE if the data couldn't be sent, TRUE on success.
 *
 * SOCK_SEQPACKET is not portable enough, thus make special provisions
 * to deliver messages in an atomic fashion.
 */

{
    static char buf[ERQ_MAX_SEND], *pending;
    long wrote;

    if (erq_demon < 0)
        return MY_FALSE;

    /* Try to send the pending data */
    if (erq_pending_len)
    {
        wrote = socket_write(erq_demon, pending, erq_pending_len);
        if (wrote > 0) {
            pending += wrote;
            erq_pending_len -= wrote;
        }
        if (erq_pending_len)
            return MY_FALSE;
    }

    if (arglen + 9 > sizeof buf)
        return MY_FALSE;

    /* Create the message and add it to buf[] */
    erq_pending_len = arglen + 9;
    *(uint32*)buf = htonl(erq_pending_len);
    *(uint32*)(buf+4) = htonl(handle);
    buf[8] = (char)request;
    memcpy(buf + 9, arg, arglen);

    /* Send as much of buf[] as possible */
    pending = buf;
    wrote = socket_write(erq_demon, buf, erq_pending_len);
    if (wrote > 0) {
        pending += wrote;
        erq_pending_len -= wrote;
    }

    return MY_TRUE;
} /* send_erq() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_send_erq (svalue_t *sp)

/* EFUN: send_erq()
 *
 *   int send_erq(int request, string|int* data, closure callback)
 *
 * Send a request of type <request> and the data <data> to the ERQ>
 * If <callback> is set to a closure, it will be called with the
 * response from the ERQ.
 *
 * The function returns svalue.number 1 on success, and 0 on failure.
 *
 * The function causes a privilege violation "erq".
 */

{
    char  *arg;
    size_t arglen;
    erq_callback_t *new_erq;
    int i;
    p_int erq_request;

    /* Set arg with the data to send. */

    if (sp[-1].type == T_STRING) {
        arg = get_txt(sp[-1].u.str);
        arglen = mstrsize(sp[-1].u.str);
    }
    else /* it's a pointer */
    {
        vector_t *v;
        svalue_t *svp;
        char *cp;
        p_int j;

        v = sp[-1].u.vec;
        arglen = VEC_SIZE(v);
        cp = arg = xalloc(arglen);
        if (!arg) {
            errorf("Out of memory (%zu bytes) in send_erq() for allocating "
                   "temporary buffer.\n", arglen);
        }
        svp = &v->item[0];
        for (j = (p_int)arglen; --j >= 0; )
            *cp++ = (char)(*svp++).u.number;
    }

    erq_request = sp[-2].u.number;

    /* Test if this call is allowed. */

    if (!privilege_violation4(STR_SEND_ERQ, 0, STR_EMPTY
                             , erq_request & (~ERQ_CB_STRING)
                             , sp))
    {
        goto failure;
    }

    /* Store the callback closure. If none is given, use the
     * default callback.
     */

    new_erq = NULL;

    if (sp->type == T_NUMBER) { /* it's the number 0 */
        new_erq = &pending_erq[MAX_PENDING_ERQ];
        new_erq->fun.u.generic = (void *)free_erq;
    }
    else if (sp->type == T_CLOSURE
          && sp->x.closure_type != CLOSURE_UNBOUND_LAMBDA)
    {
        new_erq = free_erq;
    }

    /* Send the request and make up the result. */

    if (new_erq
     && 0 != (i = send_erq(new_erq - pending_erq, erq_request & (~ERQ_CB_STRING)
                          , arg, arglen))
       )
    {
        free_erq = (erq_callback_t *)new_erq->fun.u.generic;
        new_erq->fun = *sp;
        new_erq->string_arg = (erq_request & ERQ_CB_STRING) != 0;
    }
    else
    {
failure:
        i = 0;
        free_svalue(sp);
    }
    /* cleanup */
    if (sp[-1].type != T_STRING) {
        /* free arg only if sp-1 is not a string */
        xfree(arg);
    }
    free_svalue(--sp);
    (*--sp).u.number = i;

    return sp;
} /* f_send_erq() */

/*-------------------------------------------------------------------------*/
static long
read_32 (char *str)

/* Read a 32 bit value from a possibly unaligned network byte order
 * representation.
 */

{
    unsigned char *p = (unsigned char *)str;

    return (long)p[0]<<24 | (long)p[1]<<16 | (long)p[2]<<8 | p[3];
}

/*-------------------------------------------------------------------------*/
static void
add_ip_entry (struct in_addr addr, const char *name)

/* Add a new IP address <addr>/hostname <name> pair to the cache iptable[].
 * If the <addr> already exists in the table, replace the old tabled name
 * with the new one.
 */

{
    int i, ix;
    Bool new_entry;

    ix = -1;
    new_entry = MY_FALSE;
    for (i = 0; i < IPSIZE; i++)
    {
        if (!memcmp(&(iptable[i].addr.s_addr), &addr.s_addr, sizeof(iptable[i].addr.s_addr)))
        {
            ix = i;
            break;
        }
    }

    if (ix < 0)
    {
        ix = ipcur;
        new_entry = MY_TRUE;
    }

    iptable[ix].addr = addr;
    if (iptable[ix].name)
        free_mstring(iptable[ix].name);
    iptable[ix].name = new_tabled(name);

    if (new_entry)
        ipcur = (ipcur+1) % IPSIZE;
} /* add_ip_entry() */

/*-------------------------------------------------------------------------*/
#ifdef USE_IPV6

static void
update_ip_entry (const char *oldname, const char *newname)

/* Change the IP name <oldname> in the iptable[] to <newname>.
 * If the <oldname> is not in the table, nothing happens.
 */

{
    int i, ix;
    Bool new_entry;

    ix = -1;
    new_entry = MY_FALSE;
    for (i = 0; i < IPSIZE; i++)
    {
        if (iptable[i].name
         && !strncmp(get_txt(iptable[i].name), oldname, mstrsize(iptable[i].name))
           )
        {
            free_mstring(iptable[i].name);
            iptable[i].name = new_tabled(newname);
        }
    }
} /* update_ip_entry() */

/*-------------------------------------------------------------------------*/
static int
open_ipv6_conn( const char *hostname, const unsigned short int port
              , struct sockaddr_in * pTarget)

/* Create a non-blocking IPv6/IPv4 tcp connnection to the given
 * <hostname>:<port>. The <hostname> is first interpreted as IPv6
 * address, and if that fails, as IPv4 address.
 *
 * Result is the socket (with the connnection possibly still in process
 * of being opened), or -1 on a failure.
 *
 * The completed sockaddr_in is passed back in *<pTarget> as well.
 *
 * WARNING: Not threadsafe!
 */

{
    int sock;
    int con = 0;
    int fd_flags;

    struct hostent *h;
    struct protoent *p;
    struct sockaddr_in6 addr;

    p = getprotobyname("TCP");
    if(!p) return -1;

    sock = socket(AF_INET6, SOCK_STREAM, p->p_proto);
    if(sock == -1)
    {
        perror("socket");
        return -1;
    }
    endprotoent();

    fd_flags = fcntl(sock, F_GETFL, 0);
#if defined(O_NONBLOCK)
    fd_flags |= O_NONBLOCK;
#elif defined(O_NDELAY)
    fd_flags |= O_NDELAY;
#elif defined(FNDELAY)
    fd_flags |= O_FNDELAY;
#endif
    if (fcntl(sock, F_SETFL, fd_flags) == -1)
    {
        perror("setsockopt/fcntl");
        close(sock);
        return -1;
    }
    addr.sin6_port=htons(port);
    addr.sin6_family=AF_INET6;
    addr.sin6_flowinfo=0;
    addr.sin6_scope_id=IPV6_ADDR_SCOPE_GLOBAL;

    h = gethostbyname2(hostname, AF_INET6);
    if(h)
    {
        memcpy(&addr.sin6_addr, h->h_addr, h->h_length);
        con = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
        perror("con");
    }
    else if(!h || (con && con != EINPROGRESS))
    {
        h = gethostbyname2(hostname, AF_INET);
        if(h)
        {
            CREATE_IPV6_MAPPED(&addr.sin6_addr, *((u_int32_t *)h->h_addr_list[0]));
            con = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
        }
    }
    endhostent();

    if (pTarget)
        memcpy(pTarget, &addr, sizeof(*pTarget));
 
    return (!con || (con == -1 && errno == EINPROGRESS))
           ? sock
           : (close(sock),-1);
} /* open_ipv6_conn() */

#endif /* USE_IPV6 */

/*-------------------------------------------------------------------------*/
static string_t *
lookup_ip_entry (struct in_addr addr, Bool useErq)

/* Lookup the IP address <addr> and return an uncounted pointer to
 * a shared string with the hostname. The function looks first in the
 * iptable[], then, if not found there and <useErq> is true, asks the ERQ.
 * If the hostname can not be found, NULL is returned.
 */

{
    int i;
    string_t *ipname;
    struct in_addr tmp;

    /* Search for the address backwards from the last added entry,
     * hoping that its one of the more recently added ones.
     */
    i = ipcur;
    do {
        i--;
        if (i < 0)
            i += IPSIZE;

        if (!memcmp(&(iptable[i].addr.s_addr), &addr.s_addr, sizeof(iptable[i].addr.s_addr))
         && iptable[i].name)
        {
            return iptable[i].name;
        }
    } while (i != ipcur );

    /* The address is new to us.
     * Add a temporary entry into the iptable[] to bridge
     * the time until the erq has finished the lookup.
     * This also handles the case of an unresolvable hostname.
     */

    iptable[ipcur].addr = addr;
    if (iptable[ipcur].name)
        free_mstring(iptable[ipcur].name);

    memcpy(&tmp, &addr, sizeof(tmp));
#ifndef USE_IPV6
    ipname = new_tabled(inet_ntoa(tmp));
#else
    ipname = new_tabled(inet6_ntoa(tmp));
#endif

    iptable[ipcur].name = ipname;

    ipcur = (ipcur+1) % IPSIZE;

    /* If we have the erq and may use it, lookup the real hostname */
    if (erq_demon >= 0 && useErq)
    {
#ifndef USE_IPV6
        send_erq(ERQ_HANDLE_RLOOKUP, ERQ_RLOOKUP, (char *)&addr.s_addr, sizeof(addr.s_addr));
#else
        send_erq(ERQ_HANDLE_RLOOKUPV6, ERQ_RLOOKUPV6, get_txt(ipname)
                , mstrsize(ipname));
#endif
    }

    return iptable[ipcur].name;
}

#endif /* ERQ_DEMON */

/* End of ERQ Support */
/*=========================================================================*/

/*-------------------------------------------------------------------------*/
void
remove_stale_player_data (void)

/* GC and statistics support: Remove all input_to and prompt infos
 * referencing destructed objects.
 */

{
    int i;

    for(i = 0 ; i < MAX_PLAYERS; i++)
    {
        input_t * ih, * prev;
        object_t *ob;

        if (all_players[i] == NULL)
            continue;

        /* Remove stale input_to data */
        for ( prev = NULL, ih = all_players[i]->input_handler; ih != NULL; )
            if (ih->type == INPUT_TO)
            {
                input_to_t *tmp = (input_to_t*) ih;
                ob = callback_object(&(tmp->fun));
                if (ob)
                {
                    prev = ih;
                    ih = ih->next;
                }
                else
                {
                    /* The object has selfdestructed */

                    if (prev == NULL)
                    {
                        set_noecho(all_players[i]
                                  , ih->next ? ih->next->noecho : 0
                                  , ih->next ? ih->next->local : MY_FALSE
                                  , MY_TRUE);
                        all_players[i]->input_handler = ih->next;
                    }
                    else
                    {
                        prev->next = ih->next;
                    }

                    ih = ih->next;

                    free_input_to(tmp);
                }
            }
            else
            {
                prev = ih;
                ih = ih->next;
            }

        /* Remove stale snooping monsters */
        ob = all_players[i]->snoop_by;
        if (ob && !O_IS_INTERACTIVE(ob) && !check_object(ob))
        {
            free_object(ob, "remove_stale_player_data");
            all_players[i]->snoop_by = NULL;
        }

        /* Remove a stale modify_command object */
        ob = all_players[i]->modify_command;
        if (ob && !check_object(ob))
        {
            free_object(ob, "remove_stale_player_data");
            all_players[i]->modify_command = NULL;
        }
    } /* for (i) */
} /* remove_stale_player_data() */

/*-------------------------------------------------------------------------*/
size_t
show_comm_status (strbuf_t * sbuf, Bool verbose UNUSED)

/* Return the amount of memory used by the comm module.
 */

{
#if defined(__MWERKS__)
#    pragma unused(verbose)
#endif
    size_t sum;
    int i;

    remove_stale_player_data();

    sum = 0;

    for (i = 0; i <= max_player; i++)
    {
        interactive_t *pl;
        input_t *ih;

        pl = all_players[i];
        if (!pl)
            continue;

        sum += sizeof(*pl);

        for (ih = pl->input_handler; ih != NULL; ih = ih->next)
            switch(ih->type)
            {
            case INPUT_TO:
                sum += sizeof(input_to_t);
                break;
            case INPUT_ED:
                sum += ed_buffer_size(ih);
                break;
            }
    }

    if (sbuf)
        strbuf_addf(sbuf, "Comm structures\t\t\t\t %9zu\n", sum);
    return sum;
} /* show_comm_status() */

#ifdef GC_SUPPORT

/*-------------------------------------------------------------------------*/
void
clear_comm_refs (void)

/* GC support: Clear all refs the module might have.
 */

{
    int i;

    for (i = 0; i < MAX_OUTCONN; i++)
    {
        if (outconn[i].status != ocNotUsed)
        {
            if (outconn[i].curr_obj)
                clear_object_ref(outconn[i].curr_obj);
        }
    }

#ifdef ERQ_DEMON
    for (i = sizeof (pending_erq) / sizeof (*pending_erq); --i >= 0;)
    {
        clear_ref_in_vector(&pending_erq[i].fun, 1);
    }
#endif /* ERQ_DEMON */
} /* clear_comm_refs() */

/*-------------------------------------------------------------------------*/
void
count_comm_refs (void)

/* GC support: count any ref the module has.
 */

{
    int i;

    for (i = 0; i < MAX_OUTCONN; i++)
    {
        if (outconn[i].status != ocNotUsed)
        {
            if (outconn[i].curr_obj)
            {
                svalue_t sv;

                put_object(&sv, outconn[i].curr_obj);
                count_ref_in_vector(&sv, 1);
            }
        }
    }

#ifdef ERQ_DEMON
    for(i = 0; i < IPSIZE; i++) {
        if (iptable[i].name)
            count_ref_from_string(iptable[i].name);
    }

    for (i = sizeof (pending_erq) / sizeof (*pending_erq); --i >= 0;)
    {
        count_ref_in_vector(&pending_erq[i].fun, 1);
    }
#endif /* ERQ_DEMON */
} /* count_comm_refs() */

/*-------------------------------------------------------------------------*/
void
clear_input_refs (input_t *i)

/* GC Support: Clear all references from input_t <i>. 
 */

{
    switch (i->type)
    {
    case INPUT_TO:
        {
            input_to_t *it = (input_to_t*) i;

            clear_ref_in_callback(&(it->fun));
            clear_ref_in_vector(&(it->input.prompt), 1);

            break;
        }
    case INPUT_ED:
       clear_ed_buffer_refs(i);
       break;
    }
} /* clear_input_refs() */

/*-------------------------------------------------------------------------*/
void
count_input_refs (input_t *i)

/* GC Support: Count  all references from input_t <i>. 
 */

{
    switch (i->type)
    {
    case INPUT_TO:
        {
            input_to_t *it = (input_to_t*) i;

            count_ref_in_callback(&(it->fun));
            count_ref_in_vector(&(it->input.prompt), 1);

            break;
        }
    case INPUT_ED:
        count_ed_buffer_refs(i);
        break;
    }
} /* count_input_refs() */

#endif /* GC_SUPPORT */


/*=========================================================================*/

/*-------------------------------------------------------------------------*/
static svalue_t *
query_ip_name (svalue_t *sp, Bool lookup)

/* Lookup the IP address (<lookup> is false) or IP hostname (<lookup> is
 * true) of object <sp> and return it. If <sp> is the number 0 or a
 * non-interactive object, the number 0 is returned.
 *
 * The hostname is read from the iptable[], so if it hasn't been
 * resolved yet, we return the number in any case.
 *
 * If <sp> is a reference to an interactive object, it will be replaced
 * on return with an array of integers with the full sockaddr_in:
 *    array[0.. 1]: sin_family
 *    array[2.. 3]: sin_port
 *    array[4.. 7]: sin_addr
 *    array[8..15]: undefined (ideally 0).
 *
 * The function is used to implement the efuns query_ip_number() and
 * query_ip_name().
 */

{
    object_t *ob;
    int i;
    interactive_t *ip;
    string_t *str;

    /* Set <ob> to the object passed on the stack. */

    if (sp->type != T_OBJECT)
    {
        svalue_t *svp;

        if (sp->type == T_NUMBER && !sp->u.number)
            return sp;
        svp = sp;
        while (svp->type == T_LVALUE || svp->type == T_PROTECTED_LVALUE)
            svp = svp->u.lvalue;
        if (svp->type != T_OBJECT)
        {
            errorf("Bad arg 1 to query_ip_number(): expected object/object&, got %s&.\n"
                 , typename(svp->type));
            /* NOTREACHED */
        }
        ob = svp->u.ob;
    }
    else
    {
        ob = sp->u.ob;
        deref_object(ob, "query_ip_name");
        sp->type = T_INVALID;
    }

    /* Return 0 for non-interactive objects */
    if (!(O_SET_INTERACTIVE(ip, ob)))
    {
        free_svalue(sp);
        put_number(sp, 0);
        return sp;
    }

    /* If the object was passed as reference, replace it with an array
     * with the full sockaddr_in.
     */
    if (sp->type == T_LVALUE)
    {
        svalue_t array, *svp;
        vector_t *v;
        char *cp;

        v = allocate_array(sizeof ip->addr);
        if (v)
        {
            put_array(&array, v);
            i = sizeof ip->addr;
            svp = v->item;
            cp = (char *)&ip->addr;
            do {
                svp->u.number = *cp++;
                svp++;
            } while(--i);
            transfer_svalue(sp, &array);
        }
        else
        {
            assign_svalue(sp, &const0);
        }

        return sp;
    }

    /* If the hostname is requested and we indeed have it in our table,
     * return it.
     */
    if (lookup)
    {
#ifdef ERQ_DEMON
        string_t * hname;

        hname = lookup_ip_entry(ip->addr.sin_addr, MY_FALSE);
        if (hname)
        {
            put_ref_string(sp, hname);
            return sp;
        }
#else
        /* The if(lookup) gets rid of a 'lookup unused' warning. */
#endif
    }

    /* Return the IP address as string.
     */

#ifndef USE_IPV6
    str = new_mstring(inet_ntoa(ip->addr.sin_addr));
#else
    str = new_mstring(inet6_ntoa(ip->addr.sin_addr));
#endif
    if (!str)
    {
        inter_sp = sp - 1;
        errorf("Out of memory for IP address\n");
    }
    put_string(sp, str);
    return sp;
} /* query_ip_number() */

/*-------------------------------------------------------------------------*/
char *
query_host_name (void)

/* Return the hostname (and just the hostname, not the full domain name).
 * The result is a pointer to a static array!
 * Called by lex.c, main.c and swap.c .
 */

{
    return host_name;
} /* query_host_name() */

/*-------------------------------------------------------------------------*/
char *
get_host_ip_number (void)

/* Return the IP address of the host.
 * The result is a newly allocated string.
 * Called by lex.c .
 */

{
#ifndef USE_IPV6
    char buf[INET_ADDRSTRLEN+3];

    sprintf(buf, "\"%s\"", inet_ntoa(host_ip_number));
#else
    char buf[INET6_ADDRSTRLEN+3];

    sprintf(buf, "\"%s\"", inet6_ntoa(host_ip_number));
#endif
    return string_copy(buf);
} /* query_host_ip_number() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_query_snoop (svalue_t *sp)

/* EFUN: query_snoop()
 *
 *   object query_snoop(object victim)
 *
 * Return the object which is snooping <victim>, or 0 if there is none.
 * The call must be allowed by master->valid_query_snoop().
 */

{
    svalue_t *arg1;
    object_t *ob;

    /* Do some test and set ob to the snooper (if any) */
    switch (0) /* try {...} */
    {
      default:
        ob = sp->u.ob;
        if ((ob->flags & (O_DESTRUCTED|O_SHADOW)) != O_SHADOW
         || O_GET_SHADOW(ob)->ip == NULL)
        {
            zero_object_svalue(sp);
            return sp;
        }
        inter_sp = sp;
        assert_master_ob_loaded();
        if (current_object != master_ob)
        {
            assign_eval_cost();
            arg1 = apply_master(STR_VALID_QSNOOP, 1);
            if (arg1 == 0 || arg1->type != T_NUMBER || !arg1->u.number)
            {
                ob = NULL;
                break;
            }
        }
        else
        {
            deref_object(ob, "query_snoop");
        }
        ob = O_GET_INTERACTIVE(ob)->snoop_by;
    }

    /* Return the result */
    if (ob)
        put_ref_object(sp, ob, "query_snoop");
    else
        put_number(sp, 0);
    return sp;
} /* f_query_snoop() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_query_idle (svalue_t *sp)

/* EFUN: query_idle()
 *
 *   int query_idle(object ob)
 *
 * Return how many seconds a user object has been idle.
 */

{
    int i;
    object_t *ob;

    ob = sp->u.ob;
    if (!O_IS_INTERACTIVE(ob))
    {
        inter_sp = sp;
        errorf("query_idle() of non-interactive object.\n");
        return sp;
    }

    i = current_time - O_GET_INTERACTIVE(ob)->last_time;
    deref_object(ob, "query_idle");
    put_number(sp, i);
    return sp;
} /* f_query_idle() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_remove_interactive (svalue_t *sp)

/* EFUN: remove_interactive()
 *
 *   void remove_interactive(object ob)
 *
 * Close the connection to the interactive object ob.
 *
 * In fact, the connection is only flushed and marked for closing,
 * as a remove_interactive() here can upset some other code.
 * The actual remove will be done by get_message().
 */

{
    interactive_t *victim;

    if (O_SET_INTERACTIVE(victim, sp->u.ob)
     && !victim->closing
     && !victim->do_close)
    {
        if (victim->message_length) {
            command_giver = victim->ob;
            add_message(message_flush);

            /* message_flush takes always directly effect on the
             * socket. No apply() is involved.
             */
        }
        victim->do_close = FLAG_DO_CLOSE;
    }
    free_svalue(sp);
    return sp - 1;
} /* f_remove_interactive() */

/*-------------------------------------------------------------------------*/
#ifdef DEBUG

void
count_comm_extra_refs (void)

/* Count all the refs to verify the normal refcounting. */

{
    int i;

#ifdef ERQ_DEMON
    for (i = sizeof(pending_erq) / sizeof(*pending_erq); --i >= 0; )
        count_extra_ref_in_vector(&pending_erq[i].fun, 1);
#endif /* ERQ_DEMON */

    for (i = 0; i < MAX_PLAYERS; i++)
    {
        object_t *ob;
        input_t *ih;

        if (all_players[i] == 0)
            continue;
        all_players[i]->ob->extra_ref++;
        if ( NULL != (ob = all_players[i]->snoop_by) ) {
            interactive_t *ip;

            if (!(O_SET_INTERACTIVE(ip, ob)))
            {
                /* snooping monster */
                ob->extra_ref++;
            }
        } /* end of snoop-processing */

        for ( ih = all_players[i]->input_handler; ih; ih = ih->next)
            switch(ih->type)
            {
            case INPUT_TO:
                {
                    input_to_t *it = (input_to_t*) ih;
                    count_callback_extra_refs(&(it->fun));
                    count_extra_ref_in_vector(&it->input.prompt, 1);
                    break;
                }
            case INPUT_ED:
                count_ed_buffer_extra_refs(ih);
                break;
            }

        if ( NULL != (ob = all_players[i]->modify_command) )
            count_extra_ref_in_object(ob);
        count_extra_ref_in_vector(&all_players[i]->prompt, 1);
    }
} /* count_comm_extra_refs() */

#endif /* DEBUG */

/*-------------------------------------------------------------------------*/
svalue_t *
f_send_udp (svalue_t *sp)

/* EFUN: send_udp()
 *
 *   int send_udp(string host, int port, string message)
 *   int send_udp(string host, int port, int * message)
 *
 * Sends The message in an UDP packet to the given host and port
 * number. Causes a privilege violation.
 * The message can be given either as string, or as array of
 * bytes. The latter variant allows to send binary data as well.
 * Returns 1 on success, 0 on failure.
 *
 * Note: On some machines a failed send_imp() will not be registered
 * until the next send_imp() - the latter one might return '0' even
 * if itself was successful.
 */

{
    char *to_host = NULL;
    int to_port;
    char *msg;
    size_t msglen;
#ifndef USE_IPV6
    int ip1, ip2, ip3, ip4;
#endif /* USE_IPV6 */
    struct sockaddr_in name;
    struct hostent *hp;
    int ret = 0;
    svalue_t *firstarg; /* store the first argument */
    
    firstarg = sp-2;  /* hostname */
    
    switch(0) { default: /* try {...} */

        /* Set msg/msglen to the data of the message to send */

        if (sp->type == T_STRING)
        {
            msg = get_txt(sp->u.str);
            msglen = mstrsize(sp->u.str);
        }
        else /* it's an array */
        {
            vector_t *v;
            svalue_t *svp;
            char *cp;
            p_int j;

            v = sp->u.vec;
            msglen = VEC_SIZE(v);
            /* allocate memory and push error handler onto stack */
            cp = msg = xalloc_with_error_handler(msglen);
            if (!msg) {
                errorf("Out of memory (%zu bytes) in send_udp() for "
                       "temporary buffer.\n", msglen);
            }
            sp = inter_sp;
            
            svp = &v->item[0];
            for (j = (p_int)msglen; --j >= 0; )
                *cp++ = (char)(*svp++).u.number;
        }

        /* Is this call valid? */

        if (!privilege_violation(STR_SEND_UDP, firstarg, firstarg + 2))
            break;

        if (udp_s < 0)
            break;

        /* Determine the destination address */

        {
            size_t adrlen;

            adrlen = mstrsize(firstarg->u.str);
            /* as there are no runtime error raised below, we just xallocate
             * and don't bother with an error handler. */
            to_host = xalloc(adrlen+1);
            if (!to_host)
            {
                errorf("Out of memory (%zu bytes) in send_udp() for host address\n"
                     , (adrlen+1));
                /* NOTREACHED */
            }
            memcpy(to_host, get_txt(firstarg->u.str), adrlen);
            to_host[adrlen] = '\0';
        }
        to_port = (sp-1)->u.number;

#ifndef USE_IPV6
        if (sscanf(to_host, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4) == 4)
        {
            name.sin_addr.s_addr = inet_addr(to_host);
            name.sin_family = AF_INET;
        }
        else
        {
            /* TODO: Uh-oh, blocking DNS in the execution thread */
            hp = gethostbyname(to_host);
            if (hp == 0)
                break;
            memcpy(&name.sin_addr, hp->h_addr, (size_t)hp->h_length);
            name.sin_family = AF_INET;
        }

#else /* USE_IPV6 */

        /* TODO: Uh-oh, blocking DNS in the execution thread */
        hp = gethostbyname2(to_host, AF_INET6);
        if (hp == 0) hp = gethostbyname2(to_host, AF_INET);
        if (hp == 0) break;
        memcpy(&name.sin_addr, hp->h_addr, (size_t)hp->h_length);

        if (hp->h_addrtype == AF_INET)
        {
            CREATE_IPV6_MAPPED(&name.sin_addr, (u_int32_t)hp->h_addr_list[0]);
        }
        name.sin_family = AF_INET6;
#endif /* USE_IPV6 */

        name.sin_port = htons(to_port);

        /* Send the message. */
#ifndef SENDTO_BROKEN
        if (sendto(udp_s, msg, msglen, 0,
               (struct sockaddr *)&name, sizeof(name)) != (int)msglen)
#endif
            break;
        ret = 1;
    }
    /* Cleanup - an allocated buffer for the message will be on the stack 
     * above the arguments, therefore clean everything from the first argument
     * (including) to sp.
     */
    sp = pop_n_elems((sp-firstarg)+1, sp);
    xfree(to_host);
    
    /*Return the result */
    sp++;
    put_number(sp, ret);
    return sp;
    
} /* f_send_udp() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_set_buffer_size (svalue_t *sp)

/* EFUN: set_buffer_size()
 *
 *   int set_buffer_size(int size)
 *
 * Changes the socket buffer size for this_interactive() to size,
 * up to a preconfigured maximum, result is the old buffer size
 * (or -1 on systems which aren't able to change the socket
 * buffer).
 * Modifying the buffer size may result in a better IO
 * throughput, but can also worsen it.
 */

{
    int new;

    /* Get the desired buffer size */

    if (sp->u.number > SET_BUFFER_SIZE_MAX)
    {
        errorf("Bad arg 1 to set_buffer_size(): value %"PRIdPINT" exceeds maximum %ld\n"
             , sp->u.number, (long) SET_BUFFER_SIZE_MAX);
        /* NOTREACHED */
        return sp;
    }
    new = sp->u.number;

    sp->u.number = -1; /* Default result */

#ifdef SO_SNDBUF
    {
        int old;
        length_t optlen;
        interactive_t *ip;

        if (!(O_SET_INTERACTIVE(ip, current_object))
         || ip->do_close)
        {
            return sp;
        }


        optlen = sizeof old;
        if (getsockopt(ip->socket, SOL_SOCKET, SO_SNDBUF, (char *)&old, &optlen) < 0)
            return sp;
        if (setsockopt(ip->socket, SOL_SOCKET, SO_SNDBUF, (char *)&new, sizeof new) < 0)
            return sp;
        sp->u.number = old;
    }
#endif /* SO_SNDBUF */

    return sp;
} /* f_set_buffer_size() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_binary_message (svalue_t *sp)

/* EFUN: binary_message()
 *
 *   int binary_message(int *|string message, int flags)
 *
 * Flush output and send output directly with write WITHOUT IAC QUOTING.
 * The message may contain zeroes if given as int *.
 * The messages goes to this_object(), but only if interactive.
 * Returned value: number of characters actually written.
 * Any 'allowed charset' setting is ignored.
 *
 * Flag settings are interpreted bitwise and may be ored
 * together:
 *
 *   Bit 0 (value 1): when set, add_message() is used instead of
 *     write(). Thus no previous flushing of the buffer is
 *     needed, but the output is not immediate, nor can the
 *     number of bytes actually sent be determined - the return
 *     value is undefined.
 *   Bit 1 (value 2): The buffer is flushed _after_ adding the
 *     message. Useful only in conjunction with Bit 0.
 *
 * The idea behind the flag settings is that sending command
 * codes for colours and other things needs to bypass the allowed
 * charset filters, but isn't important enough to waste bandwith
 * on a synchronous transmission.
 */

{
    string_t *msg;
    mp_int wrote = 0, i;
    svalue_t *svp;
    interactive_t *ip;
    object_t *save_command_giver;

    /* Set message to the data to be sent, and size to its length. */

    if (sp[-1].type == T_POINTER)
    {
        char *p;
        size_t size;

        size = VEC_SIZE(sp[-1].u.vec);
        msg = alloc_mstring(size);
        if (!msg)
          errorf("Out of memory (%zu bytes) in binary_message()",size);
        for (i = (mp_int)size, svp = sp[-1].u.vec->item, p = get_txt(msg)
            ; --i >= 0; svp++)
        {
            if (svp->type != T_NUMBER)
            {
                free_mstring(msg);
                errorf("Bad arg 1 to binary_message(): got %s*, "
                      "expected string/int*.\n", typename(svp->type));
                /* NOTREACHED */
                return sp;
            }
            *p++ = (char)svp->u.number;
        }
    }
    else /* it's a string */
    {
        msg = ref_mstring(sp[-1].u.str);
    }

    /* Send the message */

    i = 0;
    if (O_SET_INTERACTIVE(ip, current_object)
     && !ip->do_close)
    {
        save_command_giver = command_giver;
        command_giver = current_object;

        if (sp->u.number & 1)
        {
            /* Write before flush... */

            sending_telnet_command = MY_TRUE; /* turn of IAC quoting */

            add_message(FMT_STRING, msg);

            sending_telnet_command = MY_FALSE;

            if (sp->u.number & 2)
                add_message(message_flush);

            wrote = mstrsize(msg);
        }
        else
        {
            /* Flush, then write. */
            add_message(message_flush);

            /* Since all pending data was flushed, we can write directly
             * to the socket now.
             */

            if (comm_socket_write(get_txt(msg), mstrsize(msg), ip, 0))
                wrote = mstrsize(msg);

        } /* if (type of write) */

        command_giver = save_command_giver;
    } /* end if interactive */

    sp--;
    free_mstring(msg);
    free_svalue(sp);
    put_number(sp, wrote);
    return sp;
} /* f_binary_message() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_exec (svalue_t *sp)

/* EFUN exec()
 *
 *   object exec(object new, object old)
 *
 * Switch the network connection from <old> to <new>. If <new> is already
 * interactive, its connection will be switched to <old>.
 *
 * It is used to load different "user objects" or to reconnect
 * link dead users.
 *
 * If <old> was command_giver, <news> will be the new command_giver.
 *
 * The call is validated by master->valid_exec() and returns 0 on
 * failure, and 1 on success.
 */

{
    int rc;
    object_t *ob;
    object_t *obfrom;

    rc = 0;

    ob = sp[-1].u.ob;
    obfrom = sp[0].u.ob;

    do {
        svalue_t *v;
        interactive_t *stale_interactive, *ip;
        object_t *save_command;

        /* Ask the master if this exec() is ok. */
        push_ref_string(inter_sp, current_prog->name);
          /* TODO: FinalFrontier suggests 'current_object->prog->name' */
        push_ref_object(inter_sp, ob, "exec");
        push_ref_object(inter_sp, obfrom, "exec");
        v = apply_master(STR_VALID_EXEC, 3);
        if (!v || v->type != T_NUMBER || v->u.number == 0)
            break;

        /* stale_interactive becomes the former interactive _if_ it
         * still is an interactive_t.
         */
        if (!(O_SET_INTERACTIVE(stale_interactive, ob)))
        {
            stale_interactive = NULL;
        }

        if (!(O_SET_INTERACTIVE(ip, obfrom)))
            errorf("Bad argument 2 to exec(): not interactive.\n");

        /* When we have to have an out of memory error, have it before pointers
         * get changed.
         */
        assert_shadow_sent(ob);

        save_command = command_giver;

        /* If <ob> has a connection, flush it */
        if (stale_interactive)
        {
            if (stale_interactive->message_length)
            {
                command_giver = ob;
                add_message(message_flush);
            }
        }

        /* Flush the connection of <obfrom> */

        if (ip->message_length) {
            command_giver = obfrom;
            add_message(message_flush);
        }
        command_giver = save_command;

        /* Switch a possible snooper */

        if (ip->snoop_on)
            ip->snoop_on->snoop_by = ob;

        /* Switch the interactive */

        O_GET_INTERACTIVE(ob) = ip;
        O_GET_INTERACTIVE(obfrom) = NULL;
        ob->flags |= O_ONCE_INTERACTIVE;
        ip->ob = ob;
        ip->catch_tell_activ = MY_TRUE;

        if (stale_interactive)
        {
            /* Tie <ob>s stale connection to <obfrom>. */

            O_GET_INTERACTIVE(obfrom) = stale_interactive;
            stale_interactive->ob = obfrom;
            if (stale_interactive->snoop_on)
                stale_interactive->snoop_on->snoop_by = obfrom;
            stale_interactive->catch_tell_activ = MY_TRUE;
        }
        else
        {
            /* Clean up <obfrom> after the loss of connection */

            obfrom->flags &= ~O_ONCE_INTERACTIVE;
            check_shadow_sent(obfrom);

            ref_object(ob, "exec");
            free_object(obfrom, "exec");
        }

        /* If this_player() or this_interactive() point to one of the
         * involved objects, switch it too.
         */
        if (obfrom == command_giver)
            command_giver = ob;
        else if (ob == command_giver)
            command_giver = obfrom;

        if (obfrom == current_interactive)
            current_interactive = ob;
        else if (ob == current_interactive)
            current_interactive = obfrom;

        rc = 1;
    }while(0);

    free_svalue(sp--);
    free_svalue(sp); /* object might have been destructed */
    put_number(sp, rc);

    return sp;
} /* f_exec() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_interactive (svalue_t *sp)

/* EFUN interactive()
 *
 *   int interactive(object ob)
 *
 * Return non-zero if ob, or when the argument is omitted, this
 * object(), is an interactive user. Will return 1 if the
 * object is interactive, else 0.
 */

{
    int i;
    object_t *ob;
    interactive_t *ip;

    ob = sp->u.ob;
    (void)O_SET_INTERACTIVE(ip, ob);
    i = ip && !ip->do_close;
    deref_object(ob, "interactive");
    put_number(sp, i);

    return sp;
} /* f_interactive() */

/*-------------------------------------------------------------------------*/
svalue_t *
v_input_to (svalue_t *sp, int num_arg)

/* EFUN input_to()
 *
 *   void input_to(string fun)
 *   void input_to(string fun, int flag, ...)
 *
 * Enable next line of user input to be sent to the local
 * function fun as an argument. The input line will not be
 * parsed, only when it starts with a "!" (like a kind of shell
 * escape) (this feature may be disabled).
 * The function <fun> may be static, but must not be private (or
 * it won't be found).
 *
 * Note that fun is not called immediately but after pressing the
 * RETURN key.
 *
 * If input_to() is called more than once in the same execution,
 * only the first call has any effect.
 *
 * The optional 3rd and following args will be passed as second and
 * subsequent args to the function fun. (This feature is was
 * added only recently, to avoid the need for global variables)
 */

{
    svalue_t *arg;       /* Pointer to the arguments of the efun */
    svalue_t *extra_arg; /* Pointer to the extra arguments of the efun */
    int iflags;          /* The flags passed to input_to() */
    int flags;           /* The flags as required for .noecho */
    input_to_t *it;
    int extra;           /* Number of extra arguments */
    int error_index;

    arg = sp - num_arg + 1;

    /* Extract the arguments */

    iflags = 0;
    extra = 0;
    extra_arg = arg + 1;

    if (arg[0].type != T_CLOSURE && arg[0].type != T_STRING)
    {
        vefun_bad_arg(1, sp);
        /* NOTREACHED */
        return NULL;
    }

    if (num_arg > 1)
    {
        if (arg[1].type != T_NUMBER)
        {
            vefun_bad_arg(2, sp);
            /* NOTREACHED */
            return NULL;
        }

        iflags = arg[1].u.number;
        extra = num_arg - 2;
        extra_arg = arg + 2;
    }

    {
        interactive_t *ip;

        if (command_giver != NULL
         && O_SET_INTERACTIVE(ip, command_giver)
         && !ip->tn_enabled
           )
        {
            if (!(iflags & INPUT_NOECHO) != !(ip->noecho & NOECHO_MASK))
            {
                warnf("input_to(): Change in NOECHO mode requested for object '%s' "
                      "with telnet disabled.\n"
                     , get_txt(command_giver->name)
                     );
            }

            if (!(iflags & INPUT_CHARMODE) != !(ip->noecho & CHARMODE_MASK)
             && (iflags & INPUT_NO_TELNET) == 0
               )
            {
                warnf("input_to(): Change in CHARMODE mode requested for object '%s' "
                      "with telnet disabled.\n"
                     , get_txt(command_giver->name)
                     );
            }
        }
    }

    /* Setup the flags required for 'noecho' */
    flags =   ((iflags & INPUT_NOECHO)      ? NOECHO_REQ   : 0)
            | ((iflags & INPUT_CHARMODE)    ? CHARMODE_REQ : 0)
            | ((iflags & INPUT_IGNORE_BANG) ? IGNORE_BANG  : 0)
          ;

    /* Check the arguments */

    if (iflags & INPUT_PROMPT)
    {
        if (num_arg <= 2)
        {
            errorf("Missing prompt argument to input_to().\n");
            /* NOTREACHED */
        }

        if (arg[2].type != T_STRING && arg[2].type != T_CLOSURE)
        {
            vefun_bad_arg(3, sp);
            /* NOTREACHED */
        }
    }

    if ((flags & IGNORE_BANG)
     && !privilege_violation4(STR_INPUT_TO, command_giver, 0, flags, sp))
    {
        do
        {
            free_svalue(sp--);
        } while (--num_arg);
        
        put_number(arg, 0); /* arg should equal sp+1 */
        return arg;
    }

    /* There is a chance that the privilege_violation() method destructed
     * the current object or the command_giver - return as if the call was
     * denied.
     */
    if (!check_object(current_object) || !check_object(command_giver))
    {
	do
	{
	    free_svalue(sp--);
	} while (--num_arg);
	
	put_number(arg, 0); /* arg should equal sp+1 */
	return arg;
    }

    /* Allocate and setup the input_to structure */

    xallocate(it, sizeof *it, "new input_to");
    init_empty_callback(&(it->fun));
    put_number(&(it->input.prompt), 0);

    /* If SET_PROMPT was specified, collect it */

    if (iflags & INPUT_PROMPT)
    {
        transfer_svalue(&(it->input.prompt), arg+2);
        extra--;
        extra_arg++;
    }

    /* Parse the extra args for the call */

    if (arg[0].type == T_STRING)
    {
        error_index = setup_function_callback(&(it->fun), current_object
                                             , arg[0].u.str
                                             , extra, extra_arg
                                             , MY_TRUE
                                             );
        free_string_svalue(arg);
    }
    else if (arg[0].type == T_CLOSURE)
        error_index = setup_closure_callback(&(it->fun), arg
                                            , extra, extra_arg
                                            , MY_TRUE
                                            );
    else
        error_index = 1;

    if (error_index >= 0)
    {
        free_input_to(it);
        vefun_bad_arg(error_index + num_arg - extra + 1, arg-1);
        /* NOTREACHED */
        return arg-1;
    }

    /* At this point the call back should be bound to an existing object - but
     * as a sanity check we test it.
     */
    if (NULL == callback_object(&(it->fun)))
    {
        free_input_to(it);
	put_number(arg, 0); /* arg should equal sp+1 */
	return arg;
    }

    /* Try setting the input_to. On success, return 1. */

    if (set_call( command_giver, it, (char)flags
                , (iflags & INPUT_NO_TELNET) != 0
                , (iflags & INPUT_APPEND) != 0
                )
       )
    {
        put_number(arg, 1);
        return arg;
    }

    /* input_to() was not allowed - return 0. */

    free_input_to(it);
    put_number(arg, 0);
    return arg;
} /* v_input_to() */

/*-------------------------------------------------------------------------*/
static void
free_input_to (input_to_t *it)

/* Deallocate the input_to structure <it> and all referenced memory.
 */

{
    free_callback(&(it->fun));
    free_svalue(&(it->input.prompt));
    xfree(it);
} /* free_input_to() */

/*-------------------------------------------------------------------------*/
static void
free_input_handler (input_t *ih)

/* Deallocate the input_t structure <ih> and all referenced memory.
 */

{
    switch (ih->type)
    {
    case INPUT_TO:
        free_input_to((input_to_t*) ih);
        break;

    case INPUT_ED:
        free_ed_buffer(ih);
        break;
    }

} /* free_input_handler() */

/*-------------------------------------------------------------------------*/
void
abort_input_handler (interactive_t *ip)

/* Called from destruct_object to finish some input handlers,
 * specifically save all ed sessions.
 */
{
    input_t ** ptr = &(ip->input_handler);

    while (*ptr)
    {
        switch ((*ptr)->type)
        {
        case INPUT_ED:
            {
                input_t * ed_buf = *ptr;

                *ptr = (*ptr)->next;
                save_ed_buffer(ed_buf);
                break;
            }

        default:
            ptr = &((*ptr)->next);
            break;
        }
    }
} /* abort_input_handler() */

/*-------------------------------------------------------------------------*/
void
add_input_handler (interactive_t *ip, input_t *ih, Bool append)

/* Put the input handler <ih> in front of the input handler list of
 * the interactive <ip>.
 * If <append> is TRUE, the handler is appended to the list.
 */
{
    if (!append || ip->input_handler == NULL)
    {
        ih->next = ip->input_handler;
        ip->input_handler = ih;
    }
    else
    {
        input_t * ptr = ip->input_handler;

        while (ptr->next != NULL)
            ptr = ptr->next;

        ptr->next = ih;
        ih->next = NULL;
    }

    if (ih->noecho || ip->noecho)
        set_noecho(ip, ih->noecho, ih->local, MY_FALSE);

} /* add_input_handler() */

/*-------------------------------------------------------------------------*/
void
remove_input_handler (interactive_t *ip, input_t *ih)

/* Remove the input handler <ih> from the input handler list of
 * the interactive <ip>. <ih> is not freed.
 */
{
    input_t * ptr = ip->input_handler;

    if (ptr == ih)
    {
        ip->input_handler = ih->next;
        return;
    }

    while (ptr)
    {
        if (ptr->next == ih)
        {
            ptr->next = ih->next;
            break;
        }
        ptr = ptr->next;
    }

} /* remove_input_handler() */

/*-------------------------------------------------------------------------*/
input_t *
get_input_handler (interactive_t *ip, input_type_t type)

/* Returns the first input handler from <ip> of type <type>.
 */
{
    input_t *ih;

    for (ih = ip->input_handler; ih; ih = ih->next)
        if (ih->type == type)
            return ih;

    return NULL;
} /* get_input_handler */

/*-------------------------------------------------------------------------*/
svalue_t *
f_query_input_pending (svalue_t *sp)

/* EFUN query_input_pending()
 *
 *   object query_input_pending(object ob)
 *
 * If ob is interactive and currently has an input_to() pending,
 * the object that has called the input_to() is returned,
 * else 0.
 */

{
    object_t *ob, *cb;
    interactive_t *ip;

    ob = sp->u.ob;
    if (O_SET_INTERACTIVE(ip, ob) && ip->input_handler)
    {
        input_t *ih = ip->input_handler;

        while (ih && ih->type != INPUT_TO)
            ih = ih->next;

        if (ih)
        {
            cb = callback_object(&(((input_to_t*)ih)->fun));
            if (cb)
                sp->u.ob = ref_object(cb, "query_input_pending");
            else
                put_number(sp, 0);
        }
        else
            put_number(sp, 0);
    }
    else
    {
        put_number(sp, 0);
    }

    deref_object(ob, "query_input_pending");

    return sp;
} /* f_query_input_pending() */

/*-------------------------------------------------------------------------*/
svalue_t *
v_find_input_to (svalue_t *sp, int num_arg)

/* EFUN: find_input_to()
 *
 *   int find_input_to (object player, string|closure|object fun)
 *   int find_input_to (object player, object ob, string fun)
 *
 * Find the input_to most recently added to the interactive <player> object
 * matching the <fun> argument:
 *  - <fun> is a string: the input_to functionname has to match
 *  - <fun> is an object: the object the input_to function is bound to has
 *                        to match
 *  - <fun> is a closure: the input_to closure has to match.
 * If both <ob> and <fun> are specified, both the object and the function name
 * have to match.
 *
 * Return -1 if not found, or the position in the input_to stack (0 being
 * _least_ recently added input_to).
 */

{
    svalue_t *arg;  /* Pointer to the arguments of the efun */
    int       rc;   /* Resultvalue */

    arg = sp - num_arg + 1;

    if (num_arg > 2)
    {
        if (arg[1].type == T_OBJECT && num_arg > 2 && arg[2].type != T_STRING)
        {
            vefun_bad_arg(3, sp);
            /* NOTREACHED */
            return NULL;
        }

        if (arg[1].type != T_OBJECT
           )
        {
            vefun_bad_arg(2, sp);
            /* NOTREACHED */
            return NULL;
        }
    }

    /* Process the command, terminating out when possible */
    do
    {
        input_t       *ih;
        interactive_t *ip;

        /* Get the interactive object.
         * If there is none, or if it is closing down or doesn't have
         * an input_to set, fail.
         */
        if (!(O_SET_INTERACTIVE(ip, arg[0].u.ob))
         || ip->closing || ip->input_handler == NULL
           )
        {
            rc = -1;
            break;
        }

        /* Search for the right input_to */

        for ( ih = ip->input_handler
            ; ih != NULL
            ; ih = ih->next)
        {
            input_to_t *it;
            Bool found = MY_FALSE;

            if (ih->type != INPUT_TO)
                continue;
            it = (input_to_t*) ih;

            switch (arg[1].type)
            {
            case T_STRING:
                if (!it->fun.is_lambda
                 && mstreq(it->fun.function.named.name, arg[1].u.str))
                    found = MY_TRUE;
                break;

            case T_OBJECT:
                if (num_arg > 2)
                {
                    if (callback_object(&(it->fun)) == arg[1].u.ob
                     && !it->fun.is_lambda
                     && it->fun.function.named.name == arg[2].u.str
                       )
                        found = MY_TRUE;
                }
                else
                {
                    if (callback_object(&(it->fun)) == arg[1].u.ob)
                        found = MY_TRUE;
                }
                break;

            case T_CLOSURE:
                if (it->fun.is_lambda
                 && closure_eq(&(it->fun.function.lambda), arg+1))
                    found = MY_TRUE;
                break;

            default:
                /* TODO: Wouldn't errorf() suffice? */
                fatal("Unsupported argument type %d\n", arg[1].type);
                break;
            }

            if (found)
                break;
        }

        if (ih != NULL)
        {
            /* We found the input_to: now count at which position it is */
            for ( rc = 0; ih->next != NULL; ih = ih->next)
                if (ih->type == INPUT_TO)
                    rc++;
            break;
        }

        /* At this point, we didn't find the input_to */
        rc = -1;
    } while (0);

    /* Return the result */
    sp = pop_n_elems(num_arg, sp);
    sp++;
    put_number(sp, rc);

    return sp;
} /* f_find_input_to() */

/*-------------------------------------------------------------------------*/
svalue_t *
v_remove_input_to (svalue_t *sp, int num_arg)

/* EFUN: remove_input_to()
 *
 *   int remove_input_to (object player)
 *   int remove_input_to (object player, string|closure|object fun)
 *   int remove_input_to (object player, object ob, string fun)
 *
 * Remove a pending input_to from the interactive <player> object.
 * If the optional <fun> is not given, the most recently added input_to
 * is removed.
 *
 * If the optional <fun> is given, the efun tries to find and remove the
 * most recently added input_to matching the <fun> argument:
 *  - <fun> is a string: the input_to functionname has to match
 *  - <fun> is an object: the object the input_to function is bound to has
 *                        to match
 *  - <fun> is a closure: the input_to closure has to match.
 * If both <ob> and <fun> are specified, both the object and the function name
 * have to match.
 *
 * Return 1 on success, or 0 on failure (no input_to found, object is not
 * interactive or has no input_to pending).
 */

{
    svalue_t      *arg;  /* Pointer to the arguments of the efun */
    int            rc;   /* Resultvalue */
    interactive_t *ip;
    Bool           removedFirst;

    /* Get the arguments */
    arg = sp - num_arg + 1;

    if (num_arg > 2)
    {
        if (arg[1].type == T_OBJECT && arg[2].type != T_STRING)
        {
            vefun_bad_arg(3, sp);
            /* NOTREACHED */
            return NULL;
        }
        if (arg[1].type != T_OBJECT)
        {
            vefun_bad_arg(2, sp);
            /* NOTREACHED */
            return NULL;
        }
    }


    /* Process the command, bailing out whenever necessary */
    do
    {
        input_t * prev;
        input_t    *ih;

        removedFirst = MY_FALSE;

        /* Get the interactive object.
         * If there is none, or if it is closing down or doesn't have
         * an input_to set, fail.
         */
        if (!(O_SET_INTERACTIVE(ip, arg[0].u.ob))
         || ip->closing || ip->input_handler == NULL
           )
        {
            rc = 0;
            break;
        }

        /* Search for the right input_to */

        for (prev = NULL, ih = ip->input_handler
            ; ih != NULL
            ; prev = ih, ih = ih->next)
        {
            input_to_t *it;
            Bool found = MY_FALSE;

            if (ih->type != INPUT_TO)
                continue;
            it = (input_to_t*) ih;

            /* If no filter argument has been given, just remove
             * the first input to.
             */
            if (num_arg < 2)
                break;

            switch (arg[1].type)
            {
            case T_STRING:
                if (!it->fun.is_lambda
                 && mstreq(it->fun.function.named.name, arg[1].u.str))
                    found = MY_TRUE;
                break;

            case T_OBJECT:
                if (num_arg > 2)
                {
                    if (callback_object(&(it->fun)) == arg[1].u.ob
                     && !it->fun.is_lambda
                     && it->fun.function.named.name == arg[2].u.str
                       )
                        found = MY_TRUE;
                }
                else
                {
                    if (callback_object(&(it->fun)) == arg[1].u.ob)
                        found = MY_TRUE;
                }
                break;

            case T_CLOSURE:
                if (it->fun.is_lambda
                 && closure_eq(&(it->fun.function.lambda), arg+1))
                    found = MY_TRUE;
                break;

            default:
                /* TODO: Wouldn't errorf() suffice? */
                fatal("Unsupported argument type %d\n", arg[1].type);
                break;
            }

            if (found)
                break;
        }

        if (ih != NULL)
        {
            /* We found the input_to: remove it */
            if (prev == NULL)
            {
                ip->input_handler = ih->next;
                removedFirst = MY_TRUE;
            }
            else
                prev->next = ih->next;

            free_input_to((input_to_t*)ih);
            rc = 1;
            break;
        }

        /* At this point, nothing worked: failure */
        rc = 0;
    } while (0);

    if (rc && removedFirst)
    {
        if (ip->noecho)
            ip->noecho |= NOECHO_STALE;
        set_noecho(ip, ip->input_handler ? ip->input_handler->noecho : ip->noecho
                     , ip->input_handler ? ip->input_handler->local : MY_FALSE
                     , MY_FALSE
                  );
    }

    /* Return the result */
    sp = pop_n_elems(num_arg, sp);
    push_number(sp, rc);

    return sp;
} /* v_remove_input_to() */

/*-------------------------------------------------------------------------*/
svalue_t  *
f_input_to_info (svalue_t *sp)

/* EFUN: input_to_info()
 *
 *   mixed * input_to_info (object player)
 *
 * Construct an array of all input_to's pending for this interactive <player>.
 * The first entry in the array is the least recently added input_to, the
 * last element the most recently added one.
 * Every item in the array is itself an array of 2 or more entries:
 *  0:   The object (only if the function is a string).
 *  1:   The function (string or closure).
 *  2..: The argument(s).
 */
{
    vector_t      *v;
    int            num_pending;
    input_t       *ih;
    interactive_t *ip;

    /* Get the interactive object.
     * If there is none, or if it is closing down or doesn't have
     * an input_to set, the efun will return the empty array.
     */
    if (!(O_SET_INTERACTIVE(ip, sp->u.ob))
     || ip->closing || ip->input_handler == NULL
       )
    {
        num_pending = 0;
    }
    else
    {
        /* Count the number of pending input_tos.
         */
        for ( num_pending = 0, ih = ip->input_handler
            ; ih != NULL
            ; ih = ih->next)
            if (ih->type == INPUT_TO)
                num_pending++;
    }

    /* Allocate the result arrray and fill it in */
    v = allocate_array(num_pending);

    if (num_pending > 0)
    {
        int i;

        for (i = num_pending, ih = ip->input_handler
            ; --i >= 0
            ; ih = ih->next
            )
        {
            vector_t   *vv;
            object_t   *ob;
            input_to_t *it;

            if (ih->type != INPUT_TO)
            {
                i++;
                continue;
            }

            it = (input_to_t*) ih;

            ob = callback_object(&(it->fun));
            if (!ob)
                continue;

            /* Get the subarray */

            vv = allocate_array(2 + it->fun.num_arg);

            if (it->fun.is_lambda)
            {
                if (it->fun.function.lambda.x.closure_type == CLOSURE_LFUN)
                    put_ref_object( vv->item
                                  , it->fun.function.lambda.u.lambda->function.lfun.ob
                                  , "input_to_info");
                else
                    put_ref_object(vv->item, ob, "input_to_info");
                assign_svalue_no_free(&vv->item[1], &it->fun.function.lambda);
            }
            else
            {
                put_ref_object(vv->item, ob, "input_to_info");
                put_ref_string(vv->item + 1, it->fun.function.named.name);
            }

            if (it->fun.num_arg > 0)
            {
                svalue_t *source, *dest;
                int nargs;

                nargs = it->fun.num_arg;
                if (nargs > 1)
                    source = it->fun.arg.u.lvalue;
                else
                    source = &(it->fun.arg);
                dest = &vv->item[2];
                do {
                    assign_svalue_no_free(dest++, source++);
                } while (--nargs);
            }

            put_array(v->item + i, vv);
        }
    }

    /* Return the result */
    free_svalue(sp);
    put_array(sp, v);

    return sp;
} /* f_input_to_info() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_query_ip_name (svalue_t *sp)

/* EFUN query_ip_name()
 *
 *   string query_ip_name(object ob)
 *
 * Give the ip-name for user the current user or for the optional
 * argument ob. An asynchronous process 'erq' is used to find
 * out these names in parallel. If there are any failures to find
 * the ip-name, then the ip-number is returned instead.
 */

{
    return query_ip_name(sp, MY_TRUE);
} /* f_query_ip_name() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_query_ip_number (svalue_t *sp)

/* EFUN query_ip_number()
 *
 *   string query_ip_number(object  ob)
 *   string query_ip_number(mixed & ob)
 *
 * Give the ip-number for the current user or the optional
 * argument ob.
 *
 * If ob is given as reference (and it must be a valid object
 * then), it will upon return be set to the struct sockaddr_in of
 * the queried object, represented by an array of integers, one
 * integer per address byte:
 *   ob[0.. 1]: sin_family
 *   ob[2.. 3]: sin_port
 *   ob[4.. 7]: sin_addr
 *   ob[8..15]: undefined.
 */

{
    return query_ip_name(sp, MY_FALSE);
} /* f_query_ip_number() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_query_mud_port (svalue_t *sp)

/* EFUN: query_mud_port()
 *
 * Returns the port number the parser uses for user connections.
 *
 *   int query_mud_port(void)
 *
 * If no argument is given, the port for this_player() is
 * returned. If this_player() is not existing or not interactive,
 * the first port number open for connections is returned.
 *
 *   int query_mud_port(object user)
 *   int query_mud_port(int num)
 *
 * If an user object is given, the port used for its connection
 * is returned.
 * If a positive number is given, the <num>th port number the
 * parser uses for connections is returned (given that there are
 * that many ports).
 * If -1 is given, the number of ports open for connections is
 * returned.
 */

{
    object_t *ob;
    interactive_t *ip;
    struct sockaddr_in addr;
    length_t length;

    length = sizeof(addr);

    if (sp->type == T_NUMBER)
    {
        if (sp->u.number < -1 || sp->u.number >= numports)
        {
            errorf("Bad arg 1 to query_mud_port(): value %"PRIdPINT" out of range.\n"
                 , sp->u.number);
            /* NOTREACHED */
        }
        sp->u.number = sp->u.number < 0 ? numports : port_numbers[sp->u.number];
        return sp;
    }

    ob = sp->u.ob;
    deref_object(ob, "query_ip_port");

    if ( !(O_SET_INTERACTIVE(ip, ob))) {
        put_number(sp, port_numbers[0]);
        return sp;
    }

    getsockname(ip->socket, (struct sockaddr *)&addr, &length);
    put_number(sp, ntohs(addr.sin_port));
    return sp;
} /* f_query_mud_port() */

/*-------------------------------------------------------------------------*/


static inline void translate_bit(char c, int i, int length, string_t *rc, unsigned int bitno)
/* static helper function to translatin bits to characters in get_charset */
{
  if (c & (1 << bitno))
    get_txt(rc)[length++] = (char)(i * 8 + bitno);
} /* translate_bit */

static void
get_charset (svalue_t * sp, p_int mode, char charset[32])

/* Translate the <charset> into an svalue and store it into <sp>:
 *   <mode> == CHARSET_VECTOR: result is a bitvector array
 *   <mode> == CHARSET_STRING: result is a string.
 */

{
    put_number(sp, 0);
    switch (mode)
    {
    default:
        fatal("(get_charset): Illegal mode value %"PRIdPINT"\n", mode);
        /* NOTREACHED */
        break;

    case CHARSET_VECTOR:
      {
        vector_t * rc;
        int i;

        rc = allocate_uninit_array(32);
        if (!rc)
        {
            outofmemory("result array");
            /* NOTREACHED */
            break;
        }

        for (i = 0; i < 32; i++)
            put_number(rc->item+i, (unsigned char)charset[i]);

        put_array(sp, rc);
        break;
      }

    case CHARSET_STRING:
      {
        string_t * rc;
        int length, i;

        /* Count the number of bits set in the charset */
        for (i = length = 0; i < 32; i++)
        {
            char c = charset[i];
            length +=   ((c & 0x80) ? 1 : 0)
                      + ((c & 0x40) ? 1 : 0)
                      + ((c & 0x20) ? 1 : 0)
                      + ((c & 0x10) ? 1 : 0)
                      + ((c & 0x08) ? 1 : 0)
                      + ((c & 0x04) ? 1 : 0)
                      + ((c & 0x02) ? 1 : 0)
                      + ((c & 0x01) ? 1 : 0);
        }

        rc = alloc_mstring(length);
        if (!rc)
        {
            outofmemory("result string");
            /* NOTREACHED */
            break;
        }

        /* Translate the bits into characters */
        for (i = length = 0; i < 32; i++)
        {
            char c = charset[i];

            translate_bit(c, i, length, rc, 0);
            translate_bit(c, i, length, rc, 1);
            translate_bit(c, i, length, rc, 2);
            translate_bit(c, i, length, rc, 3);
            translate_bit(c, i, length, rc, 4);
            translate_bit(c, i, length, rc, 5);
            translate_bit(c, i, length, rc, 6);
            translate_bit(c, i, length, rc, 7);
        }

        put_string(sp, rc);
        break;
      } /* case CHARSET_STRING */
    } /* switch(mode) */
} /* get_charset() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_get_combine_charset (svalue_t *sp)

/* TEFUN: get_combine_charset()
 *
 *   mixed get_combine_charset (int mode)
 *
 * Return the combine charset of the current interactive in the form requested
 * by <mode>:
 *   <mode> == CHARSET_VECTOR: return as bitvector
 *   <mode> == CHARSET_STRING: return as string
 *
 * The bitvector is interpreted as an array of 8-bit-values and might
 * contain up to 32 elements. Character n is "combinable"
 * if sizeof(bitvector) > n/8 && bitvector[n/8] & (1 << n%8) .
 *
 * If there is no current interactive, the function returns 0. 
 */

{
    p_int mode;
    interactive_t *ip;

    mode = sp->u.number;
    if (mode != CHARSET_VECTOR && mode != CHARSET_STRING)
    {
        errorf("Bad arg 1 to get_combine_charset(): %"PRIdPINT", "
              "expected CHARSET_VECTOR (%d) or CHARSET_STRING (%d)\n"
             , mode, CHARSET_VECTOR, CHARSET_STRING);
        /* NOTREACHED */
        return sp;
    }

    if (current_interactive && O_SET_INTERACTIVE(ip, current_interactive))
        get_charset(sp, mode, ip->combine_cset);
    else
        put_number(sp, 0);

    return sp;
} /* f_get_combine_charset() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_set_combine_charset (svalue_t *sp)

/* EFUN: set_combine_charset()
 *
 *   void set_combine_charset (int* bitvector)
 *   void set_combine_charset (string chars)
 *   void set_combine_charset (0)
 *
 * Set the set of characters which can be combined into a single string
 * when received en-bloc in charmode from the current interactive user.
 * Non-combinable characters and single received characters are returned
 * in separate strings as usual. The function must be called with the
 * interactive user being the command giver.
 *
 * The newline '\n' and the NUL character '\0' are always non-combinable.
 *
 * The charset can be given either directly as a string, or indirectly
 * as a bitvector. If the charset is given as the number 0, the default
 * charset is re-established.
 *
 * The bitvector is interpreted as an array of 8-bit-values and might
 * contain up to 32 elements. Character n is "combinable"
 * if sizeof(bitvector) > n/8 && bitvector[n/8] & (1 << n%8) .
 */

{
    mp_int i;
    svalue_t *svp;
    char *p;
    interactive_t *ip;

    i = 0;
    if (sp->type == T_POINTER && (i = (mp_int)VEC_SIZE(sp->u.vec)) > 32)
    {
        errorf("Bad arg 1 to set_combine_charset(): int[] too long (%"PRIdMPINT")\n"
             , i);
        /* NOTREACHED */
        return sp;
    }

    if (current_interactive && O_SET_INTERACTIVE(ip, current_interactive))
    {
        if (sp->type == T_NUMBER)
        {
            set_default_combine_charset(ip->combine_cset);
        }
        else if (sp->type == T_STRING)
        {
            memset(ip->combine_cset, 0, sizeof ip->combine_cset);
            for ( i = mstrsize(sp->u.str), p = get_txt(sp->u.str)
                ; i > 0
                ; i--, p++)
                ip->combine_cset[(*p & 0xff) / 8] |= 1 << (*p % 8);
        }
        else
        {
            /* i was set in the typecheck above */
            for ( svp = sp->u.vec->item, p = ip->combine_cset
                ; --i >= 0
                ; svp++, p++)
            {
                if (svp->type == T_NUMBER)
                    *p = (char)svp->u.number;
            }
            memset(p, 0, (size_t)(&ip->combine_cset[sizeof ip->combine_cset] - p));
        }

        ip->combine_cset['\n'/8] &= ~(1 << '\n' % 8);
        ip->combine_cset['\0'/8] &= ~(1 << '\0' % 8);
    }
    free_svalue(sp);
    sp--;
    return sp;
} /* f_set_combine_charset() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_get_connection_charset (svalue_t *sp)

/* TEFUN: get_connection_charset()
 *
 *   mixed get_connection_charset (int mode)
 *
 * Return the combine charset of the current interactive in the form requested
 * by <mode>:
 *   <mode> == CHARSET_VECTOR: return as bitvector
 *   <mode> == CHARSET_STRING: return as string
 *
 * Alternatively, the status of the IAC quoting can be returned:
 *   <mode> == CHARSET_QUOTE_IAC: return 0 if IACs are not quoted,
 *                                return 1 if they are.
 *
 * The bitvector is interpreted as an array of 8-bit-values and might
 * contain up to 32 elements. Character n is "combinable"
 * if sizeof(bitvector) > n/8 && bitvector[n/8] & (1 << n%8) .
 *
 * If there is no current interactive, the function returns 0. 
 */

{
    p_int mode;
    interactive_t *ip;

    mode = sp->u.number;
    if (mode != CHARSET_VECTOR && mode != CHARSET_STRING
     && mode != CHARSET_QUOTE_IAC)
    {
        errorf("Bad arg 1 to get_connection_charset(): %"PRIdPINT", "
              "expected CHARSET_VECTOR (%d), _STRING (%d), "
              "or _QUOTE_IAC (%d)\n"
             , mode, CHARSET_VECTOR, CHARSET_STRING, CHARSET_QUOTE_IAC);
        /* NOTREACHED */
        return sp;
    }

    if (current_interactive && O_SET_INTERACTIVE(ip, current_interactive))
    {
        if (mode == CHARSET_QUOTE_IAC)
            put_number(sp, ip->quote_iac != 0);
        else
            get_charset(sp, mode, ip->charset);
    }
    else
        put_number(sp, 0);

    return sp;
} /* f_get_connection_charset() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_set_connection_charset (svalue_t *sp)

/* EFUN: set_connection_charset()
 *
 *   void set_connection_charset (int* bitvector, int quote_iac)
 *   void set_connection_charset (string charset, int quote_iac)
 *   void set_connection_charset (0, int quote_iac)
 *
 * Set the set of characters that can be output to the interactive user
 * (this does not apply to binary_message() ). The function must be called
 * by the interactive user object itself.
 *
 * The charset can be given either directly as a string, or indirectly
 * as a bitvector. If the charset is given as 0, the default connection
 * charset is re-established.
 *
 * The bitvector is interpreted as an array of 8-bit-values and might
 * contain up to 32 elements. Character n is allowed to be output
 * if sizeof(bitvector) > n/8 && bitvector[n/8] & (1 << n%8) .
 *
 * If quote_iac is 0 and char 255 is allowed to be output, IAC
 * will be output unmodified.
 * If quote_iac is 1 and char 255 is allowed to be output,
 * char 255 will be quoted so that it is not interpreted as IAC
 * by the telnet protocol.
 */

{
    mp_int i;
    svalue_t *svp;
    char *p;
    interactive_t *ip;

    i = 0;
    if (sp[-1].type == T_POINTER && (i = (mp_int)VEC_SIZE(sp[-1].u.vec)) > 32)
    {
        errorf("Bad arg 1 to set_connection_charset(): array too big (%"
             PRIdMPINT")\n", i);
        /* NOTREACHED */
        return sp;
    }

    if (O_SET_INTERACTIVE(ip, current_object))
    {
        if (sp[-1].type == T_NUMBER)
        {
            set_default_conn_charset(ip->charset);
        }
        else if (sp[-1].type == T_STRING)
        {
            memset(ip->charset, 0, sizeof ip->charset);
            for ( i = mstrsize((sp-1)->u.str), p = get_txt(sp[-1].u.str)
                ; i > 0
                ; i--, p++)
                ip->charset[(*p & 0xff) / 8] |= 1 << (*p % 8);
        }
        else
        {
            /* i was set in the typecheck above */
            for ( svp = sp[-1].u.vec->item, p = ip->charset
                ; --i >= 0
                ; svp++, p++)
            {
                if (svp->type == T_NUMBER)
                    *p = (char)svp->u.number;
            }
            memset(p, 0, (size_t)(&ip->charset[sizeof ip->charset] - p));
        }

        ip->quote_iac = (char)sp->u.number;
    }
    sp--;
    free_svalue(sp);
    sp--;
    return sp;
} /* f_set_connection_charset() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_set_prompt (svalue_t *sp)

/* EFUN set_prompt()
 *
 *       string set_prompt(mixed prompt, object ob)
 *
 * Set the prompt given by the first argument for the interactive object
 * instead of the default ``> ''. If the second argument is omitted,
 * this_player() is used as default. The first arg can be a string or a
 * closure. If the <prompt> arg is 0, the prompt is not changed.
 * TODO: Remove the acceptance of -1 here.
 *
 * The result returned is the old prompt.
 */

{
    svalue_t *prompt;
    interactive_t *ip;

    /* Make sure the object is interactive */
    if (!(O_SET_INTERACTIVE(ip, sp->u.ob))
     || ip->closing)
    {
        errorf("Bad arg 2 to set_prompt(): object not interactive.\n");
        return sp;
    }

    /* Get the address of the prompt svalue */
    prompt = &O_GET_INTERACTIVE(sp->u.ob)->prompt;

    free_object_svalue(sp);
    sp--;

    if (sp->type == T_STRING || sp->type == T_CLOSURE)
    {
        if (sp->type == T_CLOSURE && sp->x.closure_type == CLOSURE_UNBOUND_LAMBDA)
        {
            inter_sp = sp;
            errorf("Bad arg 1 for set_prompt(): lambda closure not bound\n");
            /* NOTREACHED */
        }

        if (sp->type == T_STRING)
        {
            string_t *str = make_tabled_from(sp->u.str);

            if (!str)
            {
                inter_sp = sp;
                errorf("(set_prompt) Out of memory (%zu bytes) for prompt\n"
                     , mstrsize(sp->u.str));
            }
            else
            {
                free_mstring(sp->u.str);
                sp->u.str = str;
            }
        }

        /* Three-way exchange to set the new prompt and put
         * the old one onto the stack.
         */
        sp[1] = *prompt;
        *prompt = *sp;
        *sp = sp[1];
    }
    else /* It's a number */
    {
        if (sp->u.number == 0 || sp->u.number == -1)
            assign_svalue(sp, prompt);
        else
        {
            errorf("Bad int arg 1 to set_prompt(): got %"PRIdPINT", expected 0 or -1.\n"
                 , sp->u.number);
            /* NOTREACHED */
            return sp;
        }
    }

    return sp;
} /* f_set_prompt() */

/*-------------------------------------------------------------------------*/
svalue_t *
v_snoop (svalue_t *sp, int num_arg)

/* EFUN snoop()
 *
 *   object snoop(object snooper)
 *   object snoop(object snooper, object snoopee)
 *
 * Starts a snoop from 'snooper' on 'snoopee', or if 'snoopee' is not
 * given, terminates any snoop from 'snooper'.
 * On success, 'snoopee' is returned, else 0.
 *
 * The snoop is checked with the master object for validity.
 * It will also fail if the 'snoopee' is being snooped already or
 * if a snoop would result in a recursive snoop action.
 */

{
    int i;

    if (num_arg == 1)
    {
        i = set_snoop(sp->u.ob, 0);
    }
    else
    {
        i = set_snoop((sp-1)->u.ob, sp->u.ob);
        free_svalue(sp--);
    }
    free_svalue(sp);
    put_number(sp, i);

    return sp;
} /* v_snoop() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_users (svalue_t *sp)

/* EFUN users()
 *
 * Return a (possibly empty) vector of all interactive user objects.
 */

{
    object_t *ob;
    int n, num;
    vector_t *ret;
    interactive_t **user;
    svalue_t *svp;

    /* Count the active users */
    num = 0;
    user = all_players;
    for (n = max_player + 2; --n; user++)
    {
        if (*user && !((*user)->ob->flags & O_DESTRUCTED))
            num++;
    }

    /* Get the result array and fill it */

    ret = allocate_array(num);
    svp = ret->item;
    user = all_players;
    for (n = max_player + 2; --n; user++)
    {
        if (*user && !((ob = (*user)->ob)->flags & O_DESTRUCTED))
        {
            put_ref_object(svp, ob, "users");
            svp++;
        }
    }

    push_array(sp, ret);

    return sp;
} /* f_users() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_query_udp_port (svalue_t *sp)

/* EFUN query_udp_port()
 *
 *   int query_udp_port(void)
 *
 * Returns the port number that is used for the inter mud
 * protocol.
 */

{
    push_number(sp, udp_port);

    return sp;
} /* f_query_udp_port() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_get_max_commands (svalue_t *sp)

/* TEFUN: get_max_commands()
 *
 *   int get_max_commands ()
 *   int get_max_commands (object obj)
 *
 * Return the max number of commands (read: line resp. char inputs) the
 * interactive <obj> (default is the current interactive) is allowed to
 * execute per second. A negative result means 'unlimited'.
 * For non-interactive objects the result is 0.
 */

{
    p_int rc;
    interactive_t *ip;

    rc = 0;
    if (O_SET_INTERACTIVE(ip, sp->u.ob))
        rc = ip->maxNumCmds;

    free_svalue(sp);
    put_number(sp, rc);

    return sp;
} /* f_get_max_commands() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_set_max_commands (svalue_t *sp)

/* TEFUN: set_max_commands()
 *
 *   void set_max_commands (int num)
 *   void set_max_commands (int num, object obj)
 *
 * Set the max number of commands (read: line resp. char inputs) the
 * interactive <obj> (default is the current interactive) is allowed to
 * execute per second to <num>. A negative result means 'unlimited'.
 * For non-interactive objects the function raises an error.
 *
 * The function raises a privilege_violation ("set_max_commands", obj, num).
 * If the privilege is denied, the call is ignored.
 */

{
    p_int num;
    interactive_t *ip;

    num = sp[-1].u.number;
    if (num < 0)
        num = -1;

    if (!O_SET_INTERACTIVE(ip, sp->u.ob))
    {
        errorf("Bad arg 2 to set_max_commands(): Object is not interactive.\n");
        /* NOTREACHED */
    }

    if (privilege_violation4(STR_SET_MAX_CMDS, sp->u.ob, NULL, num, sp))
        ip->maxNumCmds = num;

    free_svalue(sp--);
    free_svalue(sp--);
    return sp;
} /* f_set_max_commands() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_enable_telnet (svalue_t *sp)

/* TEFUN: enable_telnet()
 *
 *   int enable_telnet (int num)
 *   int enable_telnet (int num, object obj)
 *
 * Enable or disable the telnet machine for the interactive object <obj>.
 * Return the previous state of the telnet machine as result.
 *
 * <num> > 0 : enable telnet machine (default)
 *       = 0 : disable telnet machine
 *       < 0 : just query the current state of the telnet machine.
 * <obj> : the interactive object, default is the current interactive.
 *         For non-interactive objects the function raises an error.
 *
 * The function raises a privilege_violation ("enable_telnet", obj, num)
 * if <num> is >= 0. If the privilege is denied, the call is ignored.
 *
 * WARNING: Careless use of this efun can cause great confusion for both
 * driver and clients!
 */

{
    p_int num;
    p_int rc;
    interactive_t *ip;

    num = sp[-1].u.number;
    if (num < 0)
        num = -1;

    if (!O_SET_INTERACTIVE(ip, sp->u.ob))
    {
        errorf("Bad arg 2 to enable_telnet(): Object '%s' is not interactive.\n"
             , get_txt(sp->u.ob->name)
             );
        /* NOTREACHED */
        return sp; /* flow control hint */
    }

    rc = (ip->tn_enabled != 0);
    if (num >= 0
     && privilege_violation4(STR_ENABLE_TELNET, sp->u.ob, NULL, num, sp))
        ip->tn_enabled = (num != 0);

    free_svalue(sp--);
    free_svalue(sp);

    put_number(sp, rc);
    return sp;
} /* f_enable_telnet() */
 
/*-------------------------------------------------------------------------*/
void
check_for_out_connections (void)

/* Check the list of pending outgoing connections if the connections
 * are still pending.
 * Activate those who succeeded, remove those which failed.
 *
 * To be called regularily from the backend.
 */

{
    int i, ret;
    object_t *user;

    for (i = 0; i < MAX_OUTCONN; i++)
    {
        if (outconn[i].status == ocNotUsed)
            continue;

        if (!outconn[i].curr_obj) /* shouldn't happen */
        {
            socket_close(outconn[i].socket);
            outconn[i].status = ocNotUsed;
            continue;
        }

        if (outconn[i].status == ocLoggingOn)
        {
            /* LPC logon threw an error - clean up */
            debug_message("%s Error in net_connect(): logon "
                          "object '%s' threw an error.\n"
                         , time_stamp()
                         , outconn[i].curr_obj
                           ? get_txt(outconn[i].curr_obj->name)
                           : "<null>"
                         );

            outconn[i].status = ocNotUsed;
            if (outconn[i].curr_obj)
            {
                if (O_IS_INTERACTIVE(outconn[i].curr_obj))
                    remove_interactive(outconn[i].curr_obj, MY_FALSE);
                free_object(outconn[i].curr_obj, "net_connect");
            }
            socket_close(outconn[i].socket);
            continue;
        }

        if (outconn[i].curr_obj && (outconn[i].curr_obj->flags & O_DESTRUCTED))
        {
            socket_close(outconn[i].socket);
            free_object(outconn[i].curr_obj, "net_connect");
            outconn[i].status = ocNotUsed;
            continue;
        }

        ret = connect(outconn[i].socket, (struct sockaddr*) &outconn[i].target
                     , sizeof(outconn[i].target));
        if (ret == -1)
        {
            switch(errno)
            {
            case EALREADY: /* still trying */
                continue;
            case EISCONN: /* we are connected! */
                break;
            default:
                /* Error with connection, call logon() with the failure flag
                 */
                outconn[i].status = ocLoggingOn;
                push_number(inter_sp, -1);
                logon_object(outconn[i].curr_obj);

                outconn[i].status = ocNotUsed;
                free_object(outconn[i].curr_obj, "net_connect");
                socket_close(outconn[i].socket);

                continue;
            }
        }

        /* connection successful */
        outconn[i].status = ocLoggingOn;

        user = command_giver;
        new_player( outconn[i].curr_obj, outconn[i].socket
                  , &outconn[i].target, sizeof(outconn[i].target), 0);
        command_giver = user;

        free_object(outconn[i].curr_obj, "net_connect");
        outconn[i].status = ocNotUsed;
    }
} /* check_for_out_connections() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_net_connect (svalue_t *sp)

/* EFUN net_connect()
 *   
 *   int net_connect(string host, int port)
 *
 * Open a non-blocking TCP network connection to <host> and <port>.
 * On success, the connection is bound to the current object and the
 * lfun logon() is called in the object.
 *
 * If the connection can't be established immediately, the efun returns
 * 'success' and the driver will check in the background for the progress
 * of the connection. When it is established, logon() will be called in
 * the object. If the connection fails, logon(-1) will be called in the
 * object.
 *
 * The efun raises a privilege violation ("net_connect", host, port).
 *
 * Return 0 on success, and a Unix ERRNO on failure.
 */

{
    char * host;
    int    port;
    int    rc;

    /* get the arguments */
    
    host = get_txt(sp[-1].u.str);
    port = sp->u.number;

    if (!privilege_violation4(STR_NET_CONNECT, NULL, sp[-1].u.str, port, sp))
    {
        sp = pop_n_elems(2, sp);
        push_number(sp, -1);
        return sp;
    }

    /* Try the connection */
    rc = 0;
    do{
        int d, n, ret;
        object_t *user;
        struct sockaddr_in target;

#ifndef USE_IPV6

        struct hostent *h;

#endif

        /* Find a place to store the pending connection,
         * store the index in n
         */
        Bool stored = MY_FALSE;
        for (n = 0; n < MAX_OUTCONN; n++)
        {
            if (outconn[n].status == ocNotUsed)
            {
                stored = MY_TRUE;
                break;
            }
        }

        if (!stored)
        {
            rc = EMFILE;
            break;
        }

#ifndef USE_IPV6

        /* Attempt the connection */

        target.sin_port = htons(port);
        /* TODO: Uh-oh, blocking DNS in the execution thread.
         * TODO:: Better would be to start an ERQ lookup and fill in the
         * TODO:: data in the background.
         */
        h = gethostbyname(host);
        target.sin_addr.s_addr = h ? ** (uint32 **) (h -> h_addr_list)
                                   : inet_addr(host);
        if (!target.sin_addr.s_addr)
        {
            rc = -1;
            break;
        }

        target.sin_family = h ? h -> h_addrtype : AF_INET;
        d = socket (target.sin_family, SOCK_STREAM, 0);
        if (d == -1) {
            perror ("socket");
            rc = errno;
            break;
        }

        set_socket_nonblocking(d);

        /* On multihomed machines it is important to bind the socket to
         * the proper IP address.
         */
        ret = bind(d, (struct sockaddr *) &host_ip_addr_template, sizeof(host_ip_addr_template));
        if (ret == -1) {
            perror("bind during net_connect");
            rc = errno;
            break;
        } 

        ret = connect(d, (struct sockaddr *) &target, sizeof(target));
#else
        d = ret = open_ipv6_conn(host, port, &target);
#endif
        if (ret == -1 && errno != EINPROGRESS)
        {
            /* error with connection */
            perror("net_connect");
            socket_close(d);
            rc = errno;
            break;
        }

        rc = 0;

        /* Store the connection in the outconn[] table even if
         * we can complete it immediately. For the reason see below.
         */
        outconn[n].socket = d;
        outconn[n].target = target;
        outconn[n].curr_obj = ref_object(current_object, "net_conect");

        if (errno == EINPROGRESS)
        {
            /* Can't complete right now */
            outconn[n].status = ocUsed;
            break;
        }

        /* Attempt the logon. By setting the outconn[].status to
         * ocLoggingOn, any subsequent call to check_for_out_connections()
         * will clean up for us.
         */
        outconn[n].status = ocLoggingOn;

        user = command_giver;
        inter_sp = sp;
        new_player(current_object, d, &target, sizeof(target), 0);
        command_giver = user;

        /* All done - clean up */
        outconn[n].status = ocNotUsed;
        free_object(outconn[n].curr_obj, "net_connect");
    }while(0);

    /* Return the result */
    sp = pop_n_elems(2, sp);
    push_number(sp, rc);

    return sp;
} /* f_net_connect() */

/*-------------------------------------------------------------------------*/
svalue_t *
f_configure_interactive (svalue_t *sp)

/* EFUN configure_interactive()
 *
 *   void configure_interactive(object ob, int what, mixed data)
 *
 * Sets the option <what> to the value <data> on the interactive <ob>
 * or the default for all interactives if <ob> is 0.
 *
 * <what> == IC_MAX_WRITE_BUFFER_SIZE
 *
 * If the first argument <ob> is not this_object(), the privilege violation
 * ("configure_interactive", this_object(), ob, what, data) occurs.
 */

{
    object_t *ob;
    interactive_t *ip;

    if (sp[-2].type == T_OBJECT)
    {
        ob = sp[-2].u.ob;

        if (!O_SET_INTERACTIVE(ip, ob))
        {
            errorf("Bad arg 1 to configure_interactive(): "
                   "Object '%s' is not interactive.\n"
                 , get_txt(ob->name)
                 );
            return sp; /* NOTREACHED */
        }
    }
    else
    {
        ob = NULL;
        ip = NULL;
    }

    if (ob != current_object
     && !privilege_violation_n(STR_CONFIGURE_INTERACTIVE, ob, sp, 2))
    {
        sp = pop_n_elems(3, sp);
        return sp;
    }

    switch(sp[-1].u.number)
    {
    default:
        errorf("Illegal value %"PRIdPINT" for configure_interactive().\n", sp[-1].u.number);
        return sp; /* NOTREACHED */

    case IC_MAX_WRITE_BUFFER_SIZE:
        {
            int max;

            if (sp->type != T_NUMBER)
                efun_exp_arg_error(3, TF_NUMBER, sp->type, sp);

            max = sp->u.number;
            if (max < 0)
                max = -1;

            if (!ip)
                write_buffer_max_size = max;
            else
                ip->write_max_size = max;
            break;
        }
    }

    sp = pop_n_elems(3, sp);
    return sp;
} /* f_configure_interactive() */

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