tmi2/
tmi2/bin/
tmi2/etc/
tmi2/lib/
tmi2/lib/adm/
tmi2/lib/adm/daemons/languages/
tmi2/lib/adm/daemons/network/I3/
tmi2/lib/adm/daemons/virtual/template/
tmi2/lib/adm/obj/master/
tmi2/lib/adm/priv/
tmi2/lib/adm/shell/
tmi2/lib/adm/tmp/
tmi2/lib/cmds/
tmi2/lib/d/
tmi2/lib/d/Conf/
tmi2/lib/d/Conf/adm/
tmi2/lib/d/Conf/boards/
tmi2/lib/d/Conf/cmds/
tmi2/lib/d/Conf/data/
tmi2/lib/d/Conf/logs/
tmi2/lib/d/Conf/obj/
tmi2/lib/d/Conf/text/help/
tmi2/lib/d/Fooland/adm/
tmi2/lib/d/Fooland/data/
tmi2/lib/d/Fooland/data/attic/
tmi2/lib/d/Fooland/items/
tmi2/lib/d/TMI/
tmi2/lib/d/TMI/adm/
tmi2/lib/d/TMI/boards/
tmi2/lib/d/TMI/data/
tmi2/lib/d/TMI/rooms/
tmi2/lib/d/grid/
tmi2/lib/d/grid/adm/
tmi2/lib/d/grid/data/
tmi2/lib/d/std/
tmi2/lib/d/std/adm/
tmi2/lib/data/adm/
tmi2/lib/data/adm/daemons/
tmi2/lib/data/adm/daemons/doc_d/
tmi2/lib/data/adm/daemons/emoted/
tmi2/lib/data/adm/daemons/network/http/
tmi2/lib/data/adm/daemons/network/services/mail_q/
tmi2/lib/data/adm/daemons/network/smtp/
tmi2/lib/data/adm/daemons/news/archives/
tmi2/lib/data/attic/connection/
tmi2/lib/data/attic/user/
tmi2/lib/data/std/connection/b/
tmi2/lib/data/std/connection/l/
tmi2/lib/data/std/user/a/
tmi2/lib/data/std/user/b/
tmi2/lib/data/std/user/d/
tmi2/lib/data/std/user/f/
tmi2/lib/data/std/user/l/
tmi2/lib/data/std/user/x/
tmi2/lib/data/u/d/dm/working/doc_d/
tmi2/lib/data/u/l/leto/doc_d/
tmi2/lib/data/u/l/leto/smtp/
tmi2/lib/doc/
tmi2/lib/doc/driverdoc/applies/
tmi2/lib/doc/driverdoc/concepts/
tmi2/lib/doc/driverdoc/driver/
tmi2/lib/doc/driverdoc/efuns/arrays/
tmi2/lib/doc/driverdoc/efuns/buffers/
tmi2/lib/doc/driverdoc/efuns/compile/
tmi2/lib/doc/driverdoc/efuns/ed/
tmi2/lib/doc/driverdoc/efuns/floats/
tmi2/lib/doc/driverdoc/efuns/functions/
tmi2/lib/doc/driverdoc/efuns/general/
tmi2/lib/doc/driverdoc/efuns/numbers/
tmi2/lib/doc/driverdoc/efuns/parsing/
tmi2/lib/doc/driverdoc/lpc/constructs/
tmi2/lib/doc/driverdoc/lpc/preprocessor/
tmi2/lib/doc/driverdoc/lpc/types/
tmi2/lib/doc/driverdoc/platforms/
tmi2/lib/doc/mudlib/
tmi2/lib/ftp/
tmi2/lib/log/
tmi2/lib/obj/net/
tmi2/lib/obj/shells/
tmi2/lib/std/board/
tmi2/lib/std/body/
tmi2/lib/std/fun/
tmi2/lib/std/living/
tmi2/lib/std/object/
tmi2/lib/std/shop/
tmi2/lib/std/socket/
tmi2/lib/std/virtual/
tmi2/lib/student/
tmi2/lib/student/kalypso/
tmi2/lib/student/kalypso/armor/
tmi2/lib/student/kalypso/rooms/
tmi2/lib/student/kalypso/weapons/
tmi2/lib/u/l/leto/
tmi2/lib/u/l/leto/cmds/
tmi2/lib/www/errors/
tmi2/lib/www/gateways/
tmi2/lib/www/images/
tmi2/old/
tmi2/v21.7a10/
tmi2/v21.7a10/ChangeLog.old/
tmi2/v21.7a10/compat/simuls/
tmi2/v21.7a10/include/
tmi2/v21.7a10/testsuite/
tmi2/v21.7a10/testsuite/clone/
tmi2/v21.7a10/testsuite/command/
tmi2/v21.7a10/testsuite/data/
tmi2/v21.7a10/testsuite/etc/
tmi2/v21.7a10/testsuite/include/
tmi2/v21.7a10/testsuite/inherit/
tmi2/v21.7a10/testsuite/inherit/master/
tmi2/v21.7a10/testsuite/log/
tmi2/v21.7a10/testsuite/u/
tmi2/v21.7a10/tmp/
/*
 *  comm.c -- communications functions and more.
 *            Dwayne Fontenot (Jacques@TMI)
 *  Windows 95 support by George Reese (Descartes of Borg)
 */
#include "std.h"
#include "network_incl.h"
#include "lpc_incl.h"
#include "applies.h"
#include "main.h"
#include "comm.h"
#include "socket_efuns.h"
#include "backend.h"
#include "socket_ctrl.h"
#include "eoperators.h"
#include "debug.h"
#include "ed.h"
#include "file.h"

#define TELOPTS

int total_users = 0;

/*
 * local function prototypes.
 */
static int flush_message PROT((interactive_t *));
static int copy_chars PROT((unsigned char *, unsigned char *, int, interactive_t *));

#ifdef SIGNAL_FUNC_TAKES_INT
static void sigpipe_handler PROT((int));
#else
static void sigpipe_handler PROT((void));
#endif
static void hname_handler PROT((void));
static void get_user_data PROT((interactive_t *));
static char *get_user_command PROT((void));
static char *first_cmd_in_buf PROT((interactive_t *));
static int cmd_in_buf PROT((interactive_t *));
static void next_cmd_in_buf PROT((interactive_t *));
static int call_function_interactive PROT((interactive_t *, char *));
static void print_prompt PROT((void));
static void telnet_neg PROT((char *, char *));
static void query_addr_name PROT((object_t *));
static void got_addr_number PROT((char *, char *));
static void add_ip_entry PROT((long, char *));
#ifndef NO_ADD_ACTION
static void clear_notify PROT((void));
#endif
static void new_user_handler PROT((int));
static void receive_snoop PROT((char *, object_t * ob));

/*
 * public local variables.
 */
fd_set readmask, writemask;
int num_user;
int num_hidden;			/* for the O_HIDDEN flag.  This counter must
				 * be kept up to date at all times!  If you
				 * modify the O_HIDDEN flag in an object,
				 * make sure that you update this counter if
				 * the object is interactive. */
int add_message_calls = 0;
int inet_packets = 0;
int inet_volume = 0;
interactive_t **all_users = 0;
int max_users = 0;

/*
 * private local variables.
 */
static int addr_server_fd = -1;

static void
receive_snoop P2(char *, buf, object_t *, snooper)
{
/* command giver no longer set to snooper */
#ifdef RECEIVE_SNOOP
    push_constant_string(buf);
    apply(APPLY_RECEIVE_SNOOP, snooper, 1, ORIGIN_DRIVER);
#else
    /* snoop output is now % in all cases */
    add_message(snooper, "%");
    add_message(snooper, buf);
#endif
}

/*
 * Initialize new user connection socket.
 */
void init_user_conn()
{
    struct sockaddr_in sin;
    int sin_len;
    int optval;
    int i;

#ifdef WINSOCK
    WSADATA WSAData;

#define CLEANUP WSACleaup()
    
    WSAStartup(MAKEWORD(1, 1), &WDSData);
#else
#define CLEANUP
#endif

    for (i=0; i < 5; i++) {
	if (!external_port[i].port) continue;
	/*
	 * create socket of proper type.
	 */
	if ((external_port[i].fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
	    debug_perror("init_user_conn: socket", 0);
	    exit(1);
	}
	/*
	 * enable local address reuse.
	 */
	optval = 1;
	if (setsockopt(external_port[i].fd, SOL_SOCKET, SO_REUSEADDR,
		       (char *) &optval, sizeof(optval)) == -1) {
	    debug_perror("init_user_conn: setsockopt", 0);
	    CLEANUP;
	    exit(2);
	}
	/*
	 * fill in socket address information.
	 */
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons((u_short) external_port[i].port);
	/*
	 * bind name to socket.
	 */
	if (bind(external_port[i].fd, (struct sockaddr *) & sin,
		 sizeof(sin)) == -1) {
	    debug_perror("init_user_conn: bind", 0);
	    CLEANUP;
	    exit(3);
	}
	/*
	 * get socket name.
	 */
	sin_len = sizeof(sin);
	if (getsockname(external_port[i].fd, (struct sockaddr *) & sin,
			&sin_len) == -1) {
	    debug_perror("init_user_conn: getsockname", 0);
	    CLEANUP;
	    exit(4);
	}
	/*
	 * set socket non-blocking,
	 */
	if (set_socket_nonblocking(external_port[i].fd, 1) == -1) {
	    debug_perror("init_user_conn: set_socket_nonblocking 1", 0);
	    CLEANUP;
	    exit(8);
	}
	/*
	 * listen on socket for connections.
	 */
	if (listen(external_port[i].fd, SOMAXCONN) == -1) {
	    debug_perror("init_user_conn: listen", 0);
	    CLEANUP;
	    exit(10);
	}
    }
    /*
     * register signal handler for SIGPIPE.
     */
#if !defined(LATTICE) && defined(SIGPIPE)
    if (signal(SIGPIPE, sigpipe_handler) == SIGNAL_ERROR) {
	debug_perror("init_user_conn: signal SIGPIPE",0);
	exit(5);
    }
#endif
}

/*
 * Shut down new user accept file descriptor.
 */
void ipc_remove()
{
    int i;

    for (i = 0; i < 5; i++) {
	if (!external_port[i].port) continue;
	if (OS_socket_close(external_port[i].fd) == -1) {
	    debug_perror("ipc_remove: close", 0);
	}
    }

    debug_message("closed external ports\n");
}

void init_addr_server P2(char *, hostname, int, addr_server_port)
{
    struct sockaddr_in server;
    struct hostent *hp;
    int server_fd;
    int optval;
    long addr;

    if (!hostname) return;

    /*
     * get network host data for hostname.
     */
    if (hostname[0] >= '0' && hostname[0] <= '9' &&
          (addr = inet_addr(&hostname[0])) != -1) {
        hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
    } else {
        hp = gethostbyname(hostname);
    }
    if (hp == NULL) {
	debug_perror("init_addr_server: gethostbyname", 0);
	return;
    }

    /*
     * set up address information for server.
     */
    server.sin_family = AF_INET;
    server.sin_port = htons((u_short) addr_server_port);
    server.sin_addr.s_addr = inet_addr(hostname);
    memcpy((char *) &server.sin_addr, (char *) hp->h_addr, hp->h_length);
    /*
     * create socket of proper type.
     */
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {	/* problem opening socket */
	debug_perror("init_addr_server: socket", 0);
	return;
    }
    /*
     * enable local address reuse.
     */
    optval = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optval,
		   sizeof(optval)) == -1) {
	debug_perror("init_addr_server: setsockopt", 0);
	return;
    }
    /*
     * connect socket to server address.
     */
    if (connect(server_fd, (struct sockaddr *) & server, sizeof(server)) == -1) {
#ifdef WINSOCK
	if (errno == WSAECONNREFUSED)
#else
	if (errno == ECONNREFUSED)
#endif
	    debug_message("Connection to address server (%s %d) refused.\n",
			  hostname, addr_server_port);
	else 
	    debug_perror("init_addr_server: connect", 0);
	OS_socket_close(server_fd);
	return;
    }
    addr_server_fd = server_fd;
    debug_message("Connected to address server on %s port %d\n", hostname,
	    addr_server_port);
    /*
     * set socket non-blocking.
     */
    if (set_socket_nonblocking(server_fd, 1) == -1) {
	debug_perror("init_addr_server: set_socket_nonblocking 1", 0);
	return;
    }
}

/*
 * Send a message to an interactive object. If that object is shadowed,
 * special handling is done.
 */
void add_message P2(object_t *, who, char *, data)
{
    interactive_t *ip;
    char *cp;

    /*
     * if who->interactive is not valid, write message on stderr.
     * (maybe)
     */
    if (!who || (who->flags & O_DESTRUCTED) || !who->interactive ||
	(who->interactive->iflags & (NET_DEAD | CLOSING))) {
#ifdef NONINTERACTIVE_STDERR_WRITE
	putc(']', stderr);
	fprintf(stderr, data);
#endif
#ifdef LATTICE
	fflush(stderr);
#endif
	return;
    }
    ip = who->interactive;
#ifndef NO_SHADOWS
    /*
     * shadow handling.
     */
    if (shadow_catch_message(who, data)) {
	/*
	 * snoop handling.
	 */
#ifdef SNOOP_SHADOWED
	if (ip->snoop_by) 
	    receive_snoop(data, ip->snoop_by->ob);
#endif
	return;
    }
#endif				/* NO_SHADOWS */

    /*
     * write message into ip->message_buf.
     */
    for (cp = data; *cp != '\0'; cp++) {
	if (ip->message_length == MESSAGE_BUF_SIZE) {
	    if (!flush_message(ip)) {
		debug_message("Broken connection during add_message.\n");
		return;
	    }
	    if (ip->message_length == MESSAGE_BUF_SIZE)
		break;
	}
	if (*cp == '\n') {
	    if (ip->message_length == (MESSAGE_BUF_SIZE - 1)) {
		if (!flush_message(ip)) {
		    debug_message("Broken connection during add_message.\n");
		    return;
		}
		if (ip->message_length == (MESSAGE_BUF_SIZE - 1))
		    break;
	    }
	    ip->message_buf[ip->message_producer] = '\r';
	    ip->message_producer = (ip->message_producer + 1)
		% MESSAGE_BUF_SIZE;
	    ip->message_length++;
	}
	ip->message_buf[ip->message_producer] = *cp;
	ip->message_producer = (ip->message_producer + 1) % MESSAGE_BUF_SIZE;
	ip->message_length++;
    }
    if (ip->message_length != 0) {
	if (!flush_message(ip)) {
	    debug_message("Broken connection during add_message.\n");
	    return;
	}
    }
    /*
     * snoop handling.
     */
    if (ip->snoop_by)
	receive_snoop(data, ip->snoop_by->ob);
    
    add_message_calls++;
}				/* add_message() */

void add_vmessage P2V(object_t *, who, char *, format)
{
    interactive_t *ip;
    char *cp, new_string_data[LARGEST_PRINTABLE_STRING];
    va_list args;
    V_DCL(char *format);
    V_DCL(object_t *who);

    V_START(args, format);
    V_VAR(object_t *, who, args);
    V_VAR(char *, format, args);
    /*
     * if who->interactive is not valid, write message on stderr.
     * (maybe)
     */
    if (!who || (who->flags & O_DESTRUCTED) || !who->interactive || 
	(who->interactive->iflags & (NET_DEAD | CLOSING))) {
#ifdef NONINTERACTIVE_STDERR_WRITE
	putc(']', stderr);
	vfprintf(stderr, format, args);
#endif
	va_end(args);
#ifdef LATTICE
	fflush(stderr);
#endif
	return;
    }
    ip = who->interactive;
    new_string_data[0] = '\0';
    /*
     * this is dangerous since the data may not all fit into new_string_data
     * but how to tell if it will without trying it first?  I suppose one
     * could rewrite vsprintf to accept a maximum length (like strncpy) --
     * have fun!
     */
    vsprintf(new_string_data, format, args);
    va_end(args);
#ifndef NO_SHADOWS
    /*
     * shadow handling.
     */
    if (shadow_catch_message(who, new_string_data)) {
	/*
	 * snoop handling.
	 */
#ifdef SNOOP_SHADOWED
	if (ip->snoop_by)
	    receive_snoop(new_string_data, ip->snoop_by->ob);
#endif
	return;
    }
#endif				/* NO_SHADOWS */

    /*
     * write message into ip->message_buf.
     */
    for (cp = new_string_data; *cp != '\0'; cp++) {
	if (ip->message_length == MESSAGE_BUF_SIZE) {
	    if (!flush_message(ip)) {
		debug_message("Broken connection during add_message.\n");
		return;
	    }
	    if (ip->message_length == MESSAGE_BUF_SIZE)
		break;
	}
	if (*cp == '\n') {
	    if (ip->message_length == (MESSAGE_BUF_SIZE - 1)) {
		if (!flush_message(ip)) {
		    debug_message("Broken connection during add_message.\n");
		    return;
		}
		if (ip->message_length == (MESSAGE_BUF_SIZE - 1))
		    break;
	    }
	    ip->message_buf[ip->message_producer] = '\r';
	    ip->message_producer = (ip->message_producer + 1)
		% MESSAGE_BUF_SIZE;
	    ip->message_length++;
	}
	ip->message_buf[ip->message_producer] = *cp;
	ip->message_producer = (ip->message_producer + 1) % MESSAGE_BUF_SIZE;
	ip->message_length++;
    }
    if (ip->message_length != 0) {
	if (!flush_message(ip)) {
	    debug_message("Broken connection during add_message.\n");
	    return;
	}
    }
    /*
     * snoop handling.
     */
    if (ip->snoop_by)
	receive_snoop(new_string_data, ip->snoop_by->ob);

    add_message_calls++;
}				/* add_message() */

/*
 * Flush outgoing message buffer of current interactive object.
 */
static int flush_message P1(interactive_t *, ip)
{
    int length, num_bytes;

    /*
     * if ip is not valid, do nothing.
     */
    if (!ip || (ip->iflags & CLOSING)) {
	debug_message("flush_message: invalid target!\n");
	return 0;
    }
    /*
     * write ip->message_buf[] to socket.
     */
    while (ip->message_length != 0) {
	if (ip->message_consumer < ip->message_producer) {
	    length = ip->message_producer - ip->message_consumer;
	} else {
	    length = MESSAGE_BUF_SIZE - ip->message_consumer;
	}
/* Need to use send to get out of band data
   num_bytes = write(ip->fd,ip->message_buf + ip->message_consumer,length);
 */
	num_bytes = send(ip->fd, ip->message_buf + ip->message_consumer,
			 length, ip->out_of_band);
	if (num_bytes == -1) {
#ifdef EWOULDBLOCK
	    if (errno == EWOULDBLOCK) {
		debug(512, ("flush_message: write: Operation would block\n"));
		return 1;
#else
#  ifdef WINSOCK
	    if (errno == WSAEWOULDBLOCK) {
		debug(512, ("flush_message: write: Operation would block\n"));
		return 1;
#  else
	    if (0) {
		;
#  endif
#endif
#ifdef linux
	    } else if (errno == EINTR) {
		debug(512, ("flush_message: write: Interrupted system call"));
		return 1;
#endif
	    } else {
		debug_perror("flush_message: write", 0);
		ip->iflags |= NET_DEAD;
		return 0;
	    }
	}
	ip->message_consumer = (ip->message_consumer + num_bytes) %
	    MESSAGE_BUF_SIZE;
	ip->message_length -= num_bytes;
	ip->out_of_band = 0;
	inet_packets++;
	inet_volume += num_bytes;
    }
    return 1;
}				/* flush_message() */

#define TS_DATA         0
#define TS_IAC          1
#define TS_WILL         2
#define TS_WONT         3
#define TS_DO           4
#define TS_DONT         5
#define TS_SB		6
#define TS_SB_IAC       7
/*
 * Copy a string, replacing newlines with '\0'. Also add an extra
 * space and back space for every newline. This trick will allow
 * otherwise empty lines, as multiple newlines would be replaced by
 * multiple zeroes only.
 *
 * Also handle the telnet stuff.  So instead of this being a direct
 * copy it is a small state thingy.
 *
 * In fact, it is telnet_neg conglomerated into this.  This is mostly
 * done so we can sanely remove the telnet sub option negotation stuff
 * out of the input stream.  Need this for terminal types.
 * (Pinkfish change)
 */
/* the codes we send ... */
#define SCHAR SIGNED char

static char telnet_break_response[] = 
    { 28, (SCHAR)IAC, (SCHAR)WILL, TELOPT_TM, 0 };
static char telnet_interrupt_response[] = 
    { 127, (SCHAR)IAC, (SCHAR)WILL, TELOPT_TM, 0 };
static char telnet_abort_response[] = 
    { (SCHAR)IAC, (SCHAR)DM, 0 };
static char telnet_do_tm_response[] = 
    { (SCHAR)IAC, (SCHAR)WILL, TELOPT_TM, 0 };
static char telnet_do_naws[] =
    { (SCHAR)IAC, (SCHAR)DO, TELOPT_NAWS, 0 };
static char telnet_do_ttype[] =
    { (SCHAR)IAC, (SCHAR)DO, TELOPT_TTYPE, 0 };
static char telnet_term_query[] = 
    { (SCHAR)IAC, (SCHAR)SB, TELOPT_TTYPE, TELQUAL_SEND, 
	  (SCHAR)IAC, (SCHAR)SE, 0 };
static char telnet_no_echo[] = 
    { (SCHAR)IAC, (SCHAR)WONT, TELOPT_ECHO, 0 };
static char telnet_no_single[] = 
    { (SCHAR)IAC, (SCHAR)WONT, TELOPT_SGA, 0 };
static char telnet_yes_echo[] = 
    { (SCHAR)IAC, (SCHAR)WILL, TELOPT_ECHO, 0 };
static char telnet_yes_single[] = 
    { (SCHAR)IAC, (SCHAR)WILL, TELOPT_SGA, 0 };
static char telnet_ga[] = 
    { (SCHAR)IAC, (SCHAR)GA, 0 };

static int copy_chars P4(unsigned char *, from, unsigned char *, to, int, n, interactive_t *, ip)
{
    int i;
    unsigned char *start = to;

    for (i = 0; i < n; i++) {
	switch (ip->state) {
	case TS_DATA:
	    switch (from[i]) {
	    case IAC:
		ip->state = TS_IAC;
		break;
	    case '\r':
		if (ip->iflags & SINGLE_CHAR)
		    *to++ = from[i];
		break;
	    case '\n':
		if (ip->iflags & SINGLE_CHAR)
		    *to++ = from[i];
		else {
		    *to++ = ' ';
		    *to++ = '\b';
		    *to++ = '\0';
		}
		break;
	    default:
		*to++ = from[i];
		break;
	    }
	    break;
	case TS_SB_IAC:
	    if (from[i] == IAC) {
		/* IAC IAC is a quoted IAC char */
		ip->sb_buf[ip->sb_pos++] = IAC;
		ip->state = TS_SB;
		break;
	    }
	    /* old MudOS treated IAC during IAC SB ... IAC SE as return
	       to data mode.  That's probably wrong, but ... fallthrough */
	case TS_IAC:
	    switch (from[i]) {
	    case IAC:
		/* IAC IAC is a quoted IAC char */
		*to++ = IAC;
		ip->state = TS_DATA;
		break;
	    case DO:
		ip->state = TS_DO;
		break;
	    case DONT:
		ip->state = TS_DONT;
		break;
	    case WILL:
		ip->state = TS_WILL;
		break;
	    case WONT:
		ip->state = TS_WONT;
		break;
	    case BREAK:
/* Send back a break character. */
		add_message(ip->ob, telnet_break_response);
		flush_message(ip);
		break;
	    case IP:
/* Send back an interupt process character. */
		add_message(ip->ob, telnet_interrupt_response);
		break;
	    case AYT:
/* Are you there signal.  Yep we are. */
		add_message(ip->ob, "\n[Yes]\n");
		break;
	    case AO:
/* Abort output. Do a telnet sync operation. */
		ip->out_of_band = MSG_OOB;
		add_message(ip->ob, telnet_abort_response);
		flush_message(ip);
		break;
	    case SB:
		ip->state = TS_SB;
		ip->sb_pos = 0;
		break;
/* SE counts as going back into data mode */
	    case SE:
/*
 * Ok...  need to call a function on the interactive object, passing the
 * buffer as a paramater.
 */
		ip->sb_buf[ip->sb_pos] = 0;
		if (ip->sb_buf[0]==TELOPT_TTYPE && ip->sb_buf[1]=='I') {
		    /* TELOPT_TTYPE TELQUAL_IS means it's telling us it's
		       terminal type */
		    push_constant_string(ip->sb_buf + 2);
		    apply(APPLY_TERMINAL_TYPE, ip->ob, 1, ORIGIN_DRIVER);
		    ip->iflags |= USING_TELNET;
		} else
		if (ip->sb_buf[0]==TELOPT_NAWS) {
		    int w, h, i, c[4];

		    /* (char)0 is stored as 'I'; convert back */
		    for (i = 0; i < 4; i++)
			c[i] = (ip->sb_buf[i+1] == 'I' ? 0 : ip->sb_buf[i+1]);
		    
		    w = ((unsigned char)c[0]) * 256
			+ ((unsigned char)c[1]);
		    h = ((unsigned char)c[2]) * 256
			+ ((unsigned char)c[3]);
		    push_number(w);
		    push_number(h);
		    apply(APPLY_WINDOW_SIZE, ip->ob, 2, ORIGIN_DRIVER);
		} else {
		    push_constant_string(ip->sb_buf);
		    apply(APPLY_TELNET_SUBOPTION, ip->ob, 1, ORIGIN_DRIVER);
		}
		ip->state = TS_DATA;
		break;
	    case DM:
	    default:
		ip->state = TS_DATA;
		break;
	    }
	    break;
	case TS_DO:
	    if (from[i] == TELOPT_TM) {
		add_message(ip->ob, telnet_do_tm_response);
		flush_message(ip);
	    }
	    ip->state = TS_DATA;
	    break;
	case TS_WILL:
	    if (from[i] == TELOPT_TTYPE) {
		add_message(ip->ob, telnet_term_query);
		flush_message(ip);
	    }
	case TS_DONT:
	case TS_WONT:
	    ip->state = TS_DATA;
	    break;
	case TS_SB:
	    if ((unsigned char) from[i] == IAC) {
		ip->state = TS_SB_IAC;
		break;
	    }
/* Ok, put all the suboption stuff into the buffer on the interactive */
	    if (ip->sb_pos >= SB_SIZE)
		break;		/* Ignore stuff outside the range */
	    if (from[i])
		ip->sb_buf[ip->sb_pos++] = from[i];
	    else
		ip->sb_buf[ip->sb_pos++] = 'I';	/* Turn 0's into I's */
	    break;
	}
    }
    return (to - start);
}				/* copy_chars() */

/*
 * SIGPIPE handler -- does very little for now.
 */
#ifdef SIGNAL_FUNC_TAKES_INT
static void sigpipe_handler P1(int, sig)
#else
static void sigpipe_handler()
#endif
{
    debug_message("SIGPIPE received.\n");
    signal(SIGPIPE, sigpipe_handler);
}				/* sigpipe_handler() */

/*
 * SIGALRM handler.
 */
#ifdef SIGNAL_FUNC_TAKES_INT
void sigalrm_handler P1(int, sig)
#else
void sigalrm_handler()
#endif
{
    heart_beat_flag = 1;
    debug(512, ("sigalrm_handler: SIGALRM\n"));
}				/* sigalrm_handler() */

INLINE void make_selectmasks()
{
    int i;

    /*
     * generate readmask and writemask for select() call.
     */
    FD_ZERO(&readmask);
    FD_ZERO(&writemask);
    /*
     * set new user accept fd in readmask.
     */
    for (i = 0; i < 5; i++) {
	if (!external_port[i].port) continue;
	FD_SET(external_port[i].fd, &readmask);
    }
    /*
     * set user fds in readmask.
     */
    for (i = 0; i < max_users; i++) {
	if (!all_users[i] || (all_users[i]->iflags & (CLOSING | CMD_IN_BUF)))
	    continue;
	/*
	 * if this user needs more input to make a complete command, set his
	 * fd so we can get it.
	 */
	FD_SET(all_users[i]->fd, &readmask);
	if (all_users[i]->message_length != 0)
	    FD_SET(all_users[i]->fd, &writemask);
    }
    /*
     * if addr_server_fd is set, set its fd in readmask.
     */
    if (addr_server_fd >= 0) {
	FD_SET(addr_server_fd, &readmask);
    }
#ifdef PACKAGE_SOCKETS
    /*
     * set fd's for efun sockets.
     */
    for (i = 0; i < MAX_EFUN_SOCKS; i++) {
	if (lpc_socks[i].state != CLOSED) {
	    if ((lpc_socks[i].flags & S_WACCEPT) == 0)
		FD_SET(lpc_socks[i].fd, &readmask);
	    if (lpc_socks[i].flags & S_BLOCKED)
		FD_SET(lpc_socks[i].fd, &writemask);
	}
    }
#endif
}				/* make_selectmasks() */

/*
 * Process I/O.
 */
INLINE void process_io()
{
    int i;

    debug(256, ("@"));
    /*
     * check for new user connection.
     */
    for (i = 0; i < 5; i++) {
	if (!external_port[i].port) continue;
	if (FD_ISSET(external_port[i].fd, &readmask)) {
	    debug(512, ("process_io: NEW_USER\n"));
	    new_user_handler(i);
	}
    }
    /*
     * check for data pending on user connections.
     */
    for (i = 0; i < max_users; i++) {
	if (!all_users[i] || (all_users[i]->iflags & (CLOSING | CMD_IN_BUF)))
	    continue;
	if (all_users[i]->iflags & NET_DEAD) {
	    remove_interactive(all_users[i]->ob);
	    continue;
	}
	if (FD_ISSET(all_users[i]->fd, &readmask)) {
	    debug(512, ("process_io: USER %d\n", i));
	    get_user_data(all_users[i]);
	    if (!all_users[i])
		continue;
	}
	if (FD_ISSET(all_users[i]->fd, &writemask))
	    flush_message(all_users[i]);
    }
#ifdef PACKAGE_SOCKETS
    /*
     * check for data pending on efun socket connections.
     */
    for (i = 0; i < MAX_EFUN_SOCKS; i++) {
	if (lpc_socks[i].state != CLOSED)
	    if (FD_ISSET(lpc_socks[i].fd, &readmask))
		socket_read_select_handler(i);
	if (lpc_socks[i].state != CLOSED)
	    if (FD_ISSET(lpc_socks[i].fd, &writemask))
		socket_write_select_handler(i);
    }
#endif
    /*
     * check for data pending from address server.
     */
    if (addr_server_fd >= 0) {
	if (FD_ISSET(addr_server_fd, &readmask)) {
	    debug(512, ("process_io: IP_DAEMON\n"));
	    hname_handler();
	}
    }
}

/*
 * This is the new user connection handler. This function is called by the
 * event handler when data is pending on the listening socket (new_user_fd).
 * If space is available, an interactive data structure is initialized and
 * the user is connected.
 */
static void new_user_handler P1(int, which)
{
    int new_socket_fd;
    struct sockaddr_in addr;
    int length;
    int i;
    object_t *ob;
    svalue_t *ret;

    length = sizeof(addr);
    debug(512, ("new_user_handler: accept on fd %d\n", new_user_fd));
    new_socket_fd = accept(external_port[which].fd,
			   (struct sockaddr *) & addr, (int *) &length);
    if (new_socket_fd < 0) {
#ifdef WINSOCK
	if (errno == WSAEWOULDBLOCK) {
#else
	if (errno == EWOULDBLOCK) {
#endif
	    debug(512, ("new_user_handler: accept: Operation would block\n"));
	} else {
	    debug_perror("new_user_handler: accept", 0);
	}
	return;
    }
#ifdef linux
    /*
     * according to Amylaar, 'accepted' sockets in Linux 0.99p6 don't
     * properly inherit the nonblocking property from the listening socket.
     */
    if (set_socket_nonblocking(new_socket_fd, 1) == -1) {
	debug_perror("new_user_handler: set_socket_nonblocking 1", 0);
	exit(8);
    }
#endif				/* linux */
    for (i = 0; i < max_users; i++)
	if (!all_users[i]) break;

    if (i == max_users) {
	if (all_users) {
	    all_users = RESIZE(all_users, max_users + 10, interactive_t *,
			       TAG_USERS, "new_user_handler");
	} else {
	    all_users = CALLOCATE(10, interactive_t *,
				  TAG_USERS, "new_user_handler");
	}
	while (max_users < i + 10)
	    all_users[max_users++] = 0;
    }

    command_giver = master_ob;
    master_ob->interactive =
	(interactive_t *)
	    DXALLOC(sizeof(interactive_t), TAG_INTERACTIVE,
		    "new_user_handler");
    total_users++;
#ifndef NO_ADD_ACTION
    master_ob->interactive->default_err_message.s = 0;
#endif
    master_ob->interactive->connection_type = external_port[which].kind;
    master_ob->flags |= O_ONCE_INTERACTIVE;
    /*
     * initialize new user interactive data structure.
     */
    master_ob->interactive->ob = master_ob;
    master_ob->interactive->input_to = 0;
    master_ob->interactive->iflags = 0;
    master_ob->interactive->text[0] = '\0';
    master_ob->interactive->text_end = 0;
    master_ob->interactive->text_start = 0;
    master_ob->interactive->carryover = NULL;
    master_ob->interactive->snoop_on = 0;
    master_ob->interactive->snoop_by = 0;
    master_ob->interactive->last_time = current_time;
#ifdef TRACE
    master_ob->interactive->trace_level = 0;
    master_ob->interactive->trace_prefix = 0;
#endif
#ifdef OLD_ED
    master_ob->interactive->ed_buffer = 0;
#endif
    master_ob->interactive->message_producer = 0;
    master_ob->interactive->message_consumer = 0;
    master_ob->interactive->message_length = 0;
    master_ob->interactive->num_carry = 0;
    master_ob->interactive->state = TS_DATA;
    master_ob->interactive->out_of_band = 0;
    all_users[i] = master_ob->interactive;
    all_users[i]->fd = new_socket_fd;
#ifdef F_QUERY_IP_PORT
    all_users[i]->local_port = external_port[which].port;
#endif
    set_prompt("> ");
    
    memcpy((char *) &all_users[i]->addr, (char *) &addr, length);
    debug(512, ("New connection from %s.\n", inet_ntoa(addr.sin_addr)));
    num_user++;
    /*
     * The user object has one extra reference. It is asserted that the
     * master_ob is loaded.
     */
    add_ref(master_ob, "new_user");
    push_number(external_port[which].port);
    ret = apply_master_ob(APPLY_CONNECT, 1);
    if (ret == 0 || ret == (svalue_t *)-1 || ret->type != T_OBJECT) {
	remove_interactive(master_ob);
	debug_message("Connection from %s aborted.\n", inet_ntoa(addr.sin_addr));
	return;
    }
    /*
     * There was an object returned from connect(). Use this as the user
     * object.
     */
    ob = ret->u.ob;
    if (ob->flags & O_HIDDEN)
	num_hidden++;
    ob->interactive = master_ob->interactive;
    ob->interactive->ob = ob;
    ob->flags |= O_ONCE_INTERACTIVE;
    /*
     * assume the existance of write_prompt and process_input in user.c
     * until proven wrong (after trying to call them).
     */
    ob->interactive->iflags |= (HAS_WRITE_PROMPT | HAS_PROCESS_INPUT);
    
    master_ob->flags &= ~O_ONCE_INTERACTIVE;
    master_ob->interactive = 0;
    free_object(master_ob, "reconnect");
    add_ref(ob, "new_user");
    command_giver = ob;
    if (addr_server_fd >= 0) {
	query_addr_name(ob);
    }
    
    if (external_port[which].kind == PORT_TELNET) {
	/* Ask permission to ask them for their terminal type */
	add_message(ob, telnet_do_ttype);
	/* Ask them for their window size */
	add_message(ob, telnet_do_naws);
    }
    
    logon(ob);
    debug(512, ("new_user_handler: end\n"));
    command_giver = 0;
}				/* new_user_handler() */

/*
 * This is the user command handler. This function is called when
 * a user command needs to be processed.
 * This function calls get_user_command() to get a user command.
 * One user command is processed per execution of this function.
 */
int process_user_command()
{
    char *user_command;
    static char buf[MAX_TEXT], *tbuf;
    object_t *save_current_object = current_object;
    object_t *save_command_giver = command_giver;
    svalue_t *ret;

    buf[MAX_TEXT - 1] = '\0';
    if ((user_command = get_user_command())) {
	if (command_giver->flags & O_DESTRUCTED) {
	    command_giver = save_command_giver;
	    current_object = save_current_object;
	    return (1);
	}
#ifndef NO_ADD_ACTION
	clear_notify();		/* moved from user_parser() */
#endif
	update_load_av();
	current_object = 0;
	current_interactive = command_giver;
	debug(512, ("process_user_command: command_giver = %s\n",
		    command_giver->name));
	tbuf = user_command;
	if ((user_command[0] == '!') && (
#ifdef OLD_ED
	      command_giver->interactive->ed_buffer ||
#endif
	      (command_giver->interactive->input_to
	      && !(command_giver->interactive->iflags & NOESC)))) {

            if (command_giver->interactive->iflags & SINGLE_CHAR) {
                /* only 1 char ... switch to line buffer mode */
                command_giver->interactive->iflags |= WAS_SINGLE_CHAR;
                command_giver->interactive->iflags &= ~SINGLE_CHAR;
                add_message(command_giver, telnet_no_single);
                /* come back later */
            } else {
                if (command_giver->interactive->iflags & WAS_SINGLE_CHAR) {
                    /* we now have a string ... switch back to char mode */
                    command_giver->interactive->iflags &= ~WAS_SINGLE_CHAR;
                    command_giver->interactive->iflags |= SINGLE_CHAR;
                    add_message(command_giver, telnet_yes_single);
                }

	        if (command_giver->interactive->iflags & HAS_PROCESS_INPUT) {
		    push_constant_string(user_command + 1);	/* not malloc'ed */
		    ret = apply(APPLY_PROCESS_INPUT, command_giver, 1, ORIGIN_DRIVER);
		    if (!command_giver || (command_giver->flags & O_DESTRUCTED)) {
			command_giver = save_command_giver;
			current_object = save_current_object;
			return 1;
		    }
		    if (!ret && command_giver->interactive)
		        command_giver->interactive->iflags &= ~HAS_PROCESS_INPUT;
#ifndef NO_ADD_ACTION
		    if (ret && ret->type == T_STRING) {
		        strncpy(buf, ret->u.string, MAX_TEXT - 1);
			parse_command(buf, command_giver);
		    } else if (!ret || ret->type != T_NUMBER || !ret->u.number) {
			parse_command(tbuf+1, command_giver);
		    }
#endif
	        }
#ifndef NO_ADD_ACTION
	        else parse_command(tbuf + 1, command_giver);
#endif
            }
#ifdef OLD_ED
	} else if (command_giver->interactive->ed_buffer) {
	    ed_cmd(user_command);
#endif				/* ED */
	} else if (call_function_interactive(command_giver->interactive,
					     user_command)) {
	    ;			/* do nothing */
	} else {
	    /*
	     * send a copy of user input back to user object to provide
	     * support for things like command history and mud shell
	     * programming languages.
	     */
	    if (command_giver->interactive->iflags & HAS_PROCESS_INPUT) {
		push_constant_string(user_command);	/* not malloc'ed */
		ret = apply(APPLY_PROCESS_INPUT, command_giver, 1, ORIGIN_DRIVER);
		if (!command_giver || command_giver->flags & O_DESTRUCTED) {
		    command_giver = save_command_giver;
		    current_object = save_current_object;
		    return 1;
		}
		if (!ret && command_giver->interactive)
		    command_giver->interactive->iflags &= ~HAS_PROCESS_INPUT;
#ifndef NO_ADD_ACTION
		if (ret && ret->type == T_STRING) {
		    strncpy(buf, ret->u.string, MAX_TEXT - 1);
		    parse_command(buf, command_giver);
		} else if (!ret || ret->type != T_NUMBER || !ret->u.number) {
		    parse_command(tbuf, command_giver);
		}
#endif
	    }
#ifndef NO_ADD_ACTION
	    else parse_command(tbuf, command_giver);
#endif
	}
	/*
	 * Print a prompt if user is still here.
	 */
	if (command_giver->interactive)
	    print_prompt();
	current_object = save_current_object;
	command_giver = save_command_giver;
	return (1);
    }
    current_object = save_current_object;
    command_giver = save_command_giver;
    current_interactive = 0;
    return (0);
}				/* process_user_command() */

#define HNAME_BUF_SIZE 200
/*
 * This is the hname input data handler. This function is called by the
 * master handler when data is pending on the hname socket (addr_server_fd).
 */

static void hname_handler()
{
    static char hname_buf[HNAME_BUF_SIZE];
    int num_bytes;
    int tmp;
    char *pp, *q;
    long laddr;

    if (addr_server_fd < 0) {
	return;
    }
    num_bytes = OS_socket_read(addr_server_fd, hname_buf, HNAME_BUF_SIZE);
    switch (num_bytes) {
    case -1:
	switch (errno) {
#ifdef EWOULDBLOCK
	case EWOULDBLOCK:
	    debug(512, ("hname_handler: read on fd %d: Operation would block.\n",
			addr_server_fd));
	    break;
#endif
#ifdef WSAEWOULDBLOCK
	case WSAEWOULDBLOCK:
	    debug(512, ("hname_handler: read on fd %d: Operation would block.\n",
			addr_server_fd));
	    break;
#endif
	default:
	    debug_message("hname_handler: read on fd %d\n", addr_server_fd);
	    debug_perror("hname_handler: read", 0);
	    tmp = addr_server_fd;
	    addr_server_fd = -1;
	    OS_socket_close(tmp);
	    return;
	}
	break;
    case 0:
	debug_message("hname_handler: closing address server connection.\n");
	tmp = addr_server_fd;
	addr_server_fd = -1;
	OS_socket_close(tmp);
	return;
    default:
	hname_buf[num_bytes] = '\0';
	debug(512, ("hname_handler: address server replies: %s", hname_buf));
	if (hname_buf[0] >= '0' && hname_buf[0] <= '9') {
	    laddr = inet_addr(hname_buf);
	    if (laddr != -1) {
		pp = strchr(hname_buf, ' ');
		if (pp) {
		    *pp = 0;
		    pp++;
		    q = strchr(pp, '\n');
		    if (q) {
			*q = 0;
			if (strcmp(pp, "0"))
			    add_ip_entry(laddr, pp);
			got_addr_number(pp, hname_buf);	/* Recognises this as
							 * failure. */
		    }
		}
	    }
	} else {
	    char *r;

/* This means it was a name lookup... */
	    pp = strchr(hname_buf, ' ');
	    if (pp) {
		*pp = 0;
		pp++;
		r = strchr(pp, '\n');
		if (r)
		    *r = 0;
		got_addr_number(pp, hname_buf);
	    }
	}
	break;
    }
}				/* hname_handler() */

/*
 * Read pending data for a user into user->interactive->text.
 * This also does telnet negotiation.
 */
static void get_user_data P1(interactive_t *, ip)
{
    static char buf[MAX_TEXT];
    int text_space;
    int num_bytes;
#ifdef DEBUG_COMM_FREEZE
    int i;
#endif
    
    /*
     * this /3 is here because of the trick copy_chars() uses to allow empty
     * commands. it needs to be fixed right. later.
     */
    if (ip->connection_type == PORT_TELNET) {
	text_space = (MAX_TEXT - ip->text_end - 1) / 3;
    } else {
	text_space = sizeof(buf) - 1;
    }
    /*
     * Check if we need more space.
     */
    if (text_space < MAX_TEXT/16) {
	int l = ip->text_end - ip->text_start;
	
	memmove(ip->text, ip->text + ip->text_start, l + 1);
	ip->text_start = 0;
	ip->text_end = l;
	text_space = (MAX_TEXT - ip->text_end - 1) / 3;
	if (text_space < MAX_TEXT/16) {
	    /* almost 2k data without a newline.  Flush it, otherwise
	       text_space will eventually go to zero and dest the user. */
	    ip->text_start = 0;
	    ip->text_end = 0;
	    text_space = MAX_TEXT / 3;
	}
    }
    /*
     * read user data.
     */
    debug(512, ("get_user_data: read on fd %d\n", ip->fd));
    num_bytes = OS_socket_read(ip->fd, buf, text_space);
#ifdef DEBUG_COMM_FREEZE
    /* slow, but it's debugging code */
    for (i=0; i<1024; i++) {
	ip->debug_block[i] = buf[i];
    }
    ip->debug_block_size = num_bytes;
#endif
    switch (num_bytes) {
    case 0:
	if (ip->iflags & CLOSING)
	    debug_message("get_user_data: tried to read from closing fd.\n");
	remove_interactive(ip->ob);
	return;
    case -1:
#ifdef EWOULDBLOCK
	if (errno == EWOULDBLOCK) {
	    debug(512, ("get_user_data: read on fd %d: Operation would block.\n",
			ip->fd));
	} else
#endif
#ifdef WSAEWOULDBLOCK
	if (errno == WSAEWOULDBLOCK) {
	    debug(512, ("get_user_data: read on fd %d: Operation would block.\n",
			ip->fd));
	} else
#endif
	{
	    debug_message("get_user_data: read on fd %d\n", ip->fd);
	    debug_perror("get_user_data: read", 0);
	    remove_interactive(ip->ob);
	    return;
	}
	break;
    default:
	buf[num_bytes] = '\0';
	switch (ip->connection_type) {
	case PORT_TELNET:
	    /*
	     * replace newlines with nulls and catenate to buffer. Also do all
	     * the useful telnet negotation at this point too. Rip out the sub
	     * option stuff and send back anything non useful we feel we have
	     * to.
	     */
	    ip->text_end += copy_chars((unsigned char *)buf, (unsigned char *)ip->text + ip->text_end, num_bytes, ip);
	    /*
	     * now, text->end is just after the last char read. If last char
	     was a nl, char *before* text_end will be null.
	     */
	    ip->text[ip->text_end] = '\0';
	    /*
	     * handle snooping - snooper does not see type-ahead. seems like
	     * that would be very inefficient, for little functional gain.
	     */
	    if (ip->snoop_by && !(ip->iflags & NOECHO))
		receive_snoop(buf, ip->snoop_by->ob);

	    /*
	     * set flag if new data completes command.
	     */
	    if (cmd_in_buf(ip))
		ip->iflags |= CMD_IN_BUF;
	    break;
	case PORT_ASCII:
	    {
		char temp[2 * MESSAGE_BUF_SIZE];
		int old_num = ip->text_end - ip->text_start;
		char *p, *nl;
		svalue_t *ret;

		memcpy(temp, ip->text + ip->text_start, old_num);
		memcpy(temp + old_num, buf, num_bytes);
		temp[num_bytes + old_num] = 0;
		p = temp;
		while ((nl = strchr(p, '\n'))) {
		    *nl = 0;
		    if (!(ip->ob->flags & O_DESTRUCTED)) {
			push_string(p, STRING_MALLOC);
			ret = apply(APPLY_PROCESS_INPUT, ip->ob,
				    1, ORIGIN_DRIVER);
		    }
		    p = nl + 1;
		}
		num_bytes = strlen(p);
		ip->text_start = 0;
		ip->text_end = num_bytes;
		memcpy(ip->text, p, num_bytes);
		break;
	    }
	case PORT_BINARY:
	    {
		buffer_t *buffer;
		svalue_t *ret;
		
		buffer = allocate_buffer(num_bytes);
		memcpy(buffer->item, buf, num_bytes);
		
		push_refed_buffer(buffer);
		ret = apply(APPLY_PROCESS_INPUT, ip->ob, 1, ORIGIN_DRIVER);
		break;
	    }
	}
    }
}				/* get_user_data() */

/*
 * Return the first cmd of the next user in sequence that has a complete cmd
 * in their buffer.
 * CmdsGiven is used to allow users in ED to send more cmds (if they have
 * them queued up) than users not in ED.
 * This should also return a value if there is something in the
 * buffer and we are supposed to be in single character mode.
 */
#define StartCmdGiver   (max_users-1)
#define IncCmdGiver     NextCmdGiver = (NextCmdGiver == 0? StartCmdGiver: \
                                        NextCmdGiver - 1)

static int NextCmdGiver = 0;

#ifdef DEBUG_COMM_FREEZE
static char *debug_dump P2(char *, block, int, size) {
    char buffer[4096];
    char *bufp = buffer;
    int i=0;

    if (size > 1023) size = 1023;
    while (i<size) {
	if (block[i]<32 || block[i]>127) {
	    *bufp++='\\';
	    if (block[i]>99)
		*bufp++ = (block[i]/100) + '0';
	    if (block[i]>9)
		*bufp++ = (block[i] %100)/10 + '0';
	    *bufp++ = block[i]%10 + '0';
	} else {
	    switch(block[i]) {
	      case '\\':
		*bufp++ = '\\';
	      default:
		*bufp++ = block[i];
	    }
	}
    }
    *bufp = 0;
    return buffer;
}
#endif

static char *get_user_command()
{
    int i;
    interactive_t *ip;
    char *user_command = NULL;
    static char buf[MAX_TEXT];

    /*
     * find and return a user command.
     */
    for (i = 0; i < max_users; i++) {
	ip = all_users[NextCmdGiver];
	if (ip && (ip->iflags & CMD_IN_BUF)) {
	    user_command = first_cmd_in_buf(ip);
	    if (user_command) break;
#ifdef DEBUG_COMM_FREEZE
	    else {
		debug_message("*********************\nFrozen user found.\n");
		debug_message("Last Block = %s\n\n", 
			      debug_dump(ip->debug_block,
			      ip->debug_block_size));
		ip->iflags &= ~CMD_IN_BUF;
	    }		
#else
	    else
		ip->iflags &= ~CMD_IN_BUF;
#endif
	}
	IncCmdGiver;
    }
    /*
     * no cmds found; return(NULL).
     */
    if (!ip || !user_command)
	return ((char *) NULL);
    /*
     * we have a user cmd -- return it. If user has only one partially
     * completed cmd left after this, move it to the start of his buffer; new
     * stuff will be appended.
     */
    debug(512, ("get_user_command: user_command = (%s)\n", user_command));
    command_giver = ip->ob;
    /*
     * telnet option parsing and negotiation.
     */
    telnet_neg(buf, user_command);
    /*
     * move input buffer pointers to next command.
     */
    next_cmd_in_buf(ip);
    if (!cmd_in_buf(ip))
	ip->iflags &= ~CMD_IN_BUF;

    IncCmdGiver;

    if (ip->iflags & NOECHO) {
	/*
	 * Must not enable echo before the user input is received.
	 */
	add_message(command_giver, telnet_no_echo);
	ip->iflags &= ~NOECHO;
    }
    ip->last_time = current_time;
    return (buf);
}				/* get_user_command() */

/*
 * find the first character of the next complete cmd in a buffer, 0 if no
 * completed cmd.  There is a completed cmd if there is a null between
 * text_start and text_end.  Zero length commands are discarded (as occur
 * between <cr> and <lf>).  Update text_start if we have to skip leading
 * nulls.
 * This should return true when in single char mode and there is
 * Anything at all in the buffer.
 */
static char *first_cmd_in_buf P1(interactive_t *, ip)
{
    char *p, *q;

    p = ip->text + ip->text_start;

    /*
     * skip null input.
     */
    while ((p < (ip->text + ip->text_end)) && !*p)
	p++;

    ip->text_start = p - ip->text;

    if (ip->text_start >= ip->text_end) {
	ip->text_start = ip->text_end = 0;
	ip->text[0] = '\0';
	return ((char *) NULL);
    }
    /* If we got here, must have something in the array */
    if (ip->iflags & SINGLE_CHAR) {
	/* We need to return true here... */
	return (ip->text + ip->text_start);
    }
    /*
     * find end of cmd.
     */
    while ((p < (ip->text + ip->text_end)) && *p)
	p++;
    /*
     * null terminated; was command.
     */
    if (p < ip->text + ip->text_end)
	return (ip->text + ip->text_start);
    /*
     * have a partial command at end of buffer; move it to start, return
     * null. if it can't move down, truncate it and return it as cmd.
     */
    p = ip->text + ip->text_start;
    q = ip->text;
    while (p < (ip->text + ip->text_end))
	*(q++) = *(p++);

    ip->text_end -= ip->text_start;
    ip->text_start = 0;
    if (ip->text_end > MAX_TEXT - 2) {
	ip->text[ip->text_end - 2] = '\0';	/* nulls to truncate */
	ip->text[ip->text_end - 1] = '\0';	/* nulls to truncate */
	ip->text_end--;
	return (ip->text);
    }
    /*
     * buffer not full and no newline - no cmd.
     */
    return ((char *) NULL);
}				/* first_command_in_buf() */

/*
 * return(1) if there is a complete command in ip->text, otherwise return(0).
 */
static int cmd_in_buf P1(interactive_t *, ip)
{
    char *p;

    p = ip->text + ip->text_start;

    /*
     * skip null input.
     */
    while ((p < (ip->text + ip->text_end)) && !*p)
	p++;

    if ((p - ip->text) >= ip->text_end) {
	return (0);
    }
    /* If we get here, must have something in the buffer */
    if (ip->iflags & SINGLE_CHAR) {
	return (1);
    }
    /*
     * find end of cmd.
     */
    while ((p < (ip->text + ip->text_end)) && *p)
	p++;
    /*
     * null terminated; was command.
     */
    if (p < ip->text + ip->text_end)
	return (1);
    /*
     * no newline - no cmd.
     */
    return (0);
}				/* cmd_in_buf() */

/*
 * move pointers to next cmd, or clear buf.
 */
static void next_cmd_in_buf P1(interactive_t *, ip)
{
    char *p = ip->text + ip->text_start;

    while (*p && p < ip->text + ip->text_end)
	p++;
    /*
     * skip past any nulls at the end.
     */
    while (!*p && p < ip->text + ip->text_end)
	p++;
    if (p < ip->text + ip->text_end)
	ip->text_start = p - ip->text;
    else {
	ip->text_start = ip->text_end = 0;
	ip->text[0] = '\0';
    }
}				/* next_cmd_in_buf() */

/*
 * Remove an interactive user immediately.
 */
void remove_interactive P1(object_t *, ob)
{
    int i;

    if (!ob->interactive) {
	return;
    }
    for (i = 0; i < max_users; i++) {
	if (all_users[i] != ob->interactive)
	    continue;
	if (ob->interactive->iflags & CLOSING) {
	    debug_message("Double call to remove_interactive()\n");
	    return;
	}
	ob->interactive->iflags |= CLOSING;

	/*
	 * auto-notification of net death
	 */
	safe_apply(APPLY_NET_DEAD, ob, 0, ORIGIN_DRIVER);

	if (ob->interactive->snoop_by) {
	    ob->interactive->snoop_by->snoop_on = 0;
	    ob->interactive->snoop_by = 0;
	}
	if (ob->interactive->snoop_on) {
	    ob->interactive->snoop_on->snoop_by = 0;
	    ob->interactive->snoop_on = 0;
	}
	debug(512, ("Closing connection from %s.\n",
		    inet_ntoa(ob->interactive->addr.sin_addr)));
#ifdef F_ED
	if (ob->interactive->ed_buffer) {
	    save_ed_buffer(ob);
	}
#endif
	debug(512, ("remove_interactive: closing fd %d\n", ob->interactive->fd));
	if (OS_socket_close(ob->interactive->fd) == -1) {
	    debug_perror("remove_interactive: close", 0);
	}
	if (ob->flags & O_HIDDEN)
	    num_hidden--;
	num_user--;
#ifndef NO_ADD_ACTION
	clear_notify();
#endif
	if (ob->interactive->input_to) {
	    free_object(ob->interactive->input_to->ob, "remove_interactive");
	    free_sentence(ob->interactive->input_to);
	    if (ob->interactive->num_carry > 0)
		free_some_svalues(ob->interactive->carryover,
				  ob->interactive->num_carry);
	    ob->interactive->carryover = NULL;
	    ob->interactive->num_carry = 0;
	    ob->interactive->input_to = 0;
	}
	FREE((char *) ob->interactive);
	total_users--;
	ob->interactive = 0;
	all_users[i] = 0;
	free_object(ob, "remove_interactive");
	return;
    }
    fatal("remove_interactive: could not find and remove user %s\n",
	ob->name);
}				/* remove_interactive() */

static int call_function_interactive P2(interactive_t *, i, char *, str)
{
    object_t *ob;
    funptr_t *funp;
    char *function;
    svalue_t *args;
    sentence_t *sent;
    int num_arg;

    i->iflags &= ~NOESC;
    if (!(sent = i->input_to))
	return (0);

    /*
     * Special feature: input_to() has been called to setup a call to a
     * function.
     */
    if (sent->ob->flags & O_DESTRUCTED) {
	/* Sorry, the object has selfdestructed ! */
	free_object(sent->ob, "call_function_interactive");
	free_sentence(sent);
	i->input_to = 0;
	if (i->num_carry)
	    free_some_svalues(i->carryover, i->num_carry);
	i->carryover = NULL;
	i->num_carry = 0;
	return (0);
    }
    /*
     * We must all references to input_to fields before the call to apply(),
     * because someone might want to set up a new input_to().
     */
    free_object(sent->ob, "call_function_interactive");
    /* we put the function on the stack in case of an error */
    sp++;
    if (sent->flags & V_FUNCTION) {
      function = 0;
      sp->type = T_FUNCTION;
      sp->u.fp = funp = sent->function.f;
      funp->hdr.ref++;
    } else {
      sp->type = T_STRING;
      sp->subtype = STRING_SHARED;
      sp->u.string = function = sent->function.s;
      ref_string(function);
    }
    ob = sent->ob;
    free_sentence(sent);

    /*
     * If we have args, we have to copy them, so the svalues on the
     * interactive struct can be FREEd
     */
    num_arg = i->num_carry;
    if (num_arg) {
	args = i->carryover;
	i->num_carry = 0;
	i->carryover = NULL;
    } else
	args = NULL;

    i->input_to = 0;
    if (i->iflags & SINGLE_CHAR) {
	/*
	 * clear single character mode
	 */
	i->iflags &= ~SINGLE_CHAR;
	add_message(i->ob, telnet_no_single);
    }

    push_constant_string(str);
    /*
     * If we have args, we have to push them onto the stack in the order they
     * were in when we got them.  They will be popped off by the called
     * function.
     */
    if (args) {
	transfer_push_some_svalues(args, num_arg);
	FREE(args);
    }
    /* current_object no longer set */
    if (function)
       (void) apply(function, ob, num_arg + 1, ORIGIN_DRIVER);
    else
       call_function_pointer(funp, num_arg + 1);

    pop_stack();		/* remove `function' from stack */

    return (1);
}				/* call_function_interactive() */

int set_call P3(object_t *, ob, sentence_t *, sent, int, flags)
{
    if (ob == 0 || sent == 0)
	return (0);
    if (ob->interactive == 0 || ob->interactive->input_to)
	return (0);
    ob->interactive->input_to = sent;
    ob->interactive->iflags |= (flags & (I_NOECHO | I_NOESC | I_SINGLE_CHAR));
    if (flags & I_NOECHO)
	add_message(ob, telnet_yes_echo);
    if (flags & I_SINGLE_CHAR)
	add_message(ob, telnet_yes_single);
    return (1);
}				/* set_call() */

void set_prompt P1(char *, str)
{
    if (command_giver && command_giver->interactive) {
	command_giver->interactive->prompt = str;
    }
}				/* set_prompt() */

/*
 * Print the prompt, but only if input_to not is disabled.
 */
static void print_prompt()
{
    if (command_giver->interactive->input_to == 0) {
	/* give user object a chance to write its own prompt */
	if (!(command_giver->interactive->iflags & HAS_WRITE_PROMPT))
	    tell_object(command_giver, command_giver->interactive->prompt);
#ifdef OLD_ED
	else if (command_giver->interactive && command_giver->interactive->ed_buffer)
	    tell_object(command_giver, command_giver->interactive->prompt);
#endif
	else if (!(command_giver->flags & O_DESTRUCTED) &&
		 !apply(APPLY_WRITE_PROMPT, command_giver, 0, ORIGIN_DRIVER)) {
	    if (command_giver->interactive) {
		command_giver->interactive->iflags &= ~HAS_WRITE_PROMPT;
		tell_object(command_giver, command_giver->interactive->prompt);
	    }
	}
    }
    /*
     * Put the IAC GA thing in here... Moved from before writing the prompt;
     * vt src says it's a terminator. Should it be inside the no-input_to
     * case? We'll see, I guess.
     */
    if (command_giver->interactive->iflags & USING_TELNET)
	add_message(command_giver, telnet_ga);
}				/* print_prompt() */

/*
 * Let object 'me' snoop object 'you'. If 'you' is 0, then turn off
 * snooping.
 *
 * This routine is almost identical to the old set_snoop. The main
 * difference is that the routine writes nothing to user directly,
 * all such communication is taken care of by the mudlib. It communicates
 * with master.c in order to find out if the operation is permissble or
 * not. The old routine let everyone snoop anyone. This routine also returns
 * 0 or 1 depending on success.
 */
int new_set_snoop P2(object_t *, me, object_t *, you)
{
    interactive_t *on, *by, *tmp;

    /*
     * 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);
    /*
     * Find the snooper && snoopee.
     */
    if (!me->interactive)
	error("First argument of snoop() is not interactive!\n");

    by = me->interactive;
    
    if (you) {
	if (!you->interactive)
	    error("Second argument of snoop() is not interactive!\n");
	on = you->interactive;
    } else {
	/*
	 * Stop snoop.
	 */
	if (by->snoop_on) {
	    by->snoop_on->snoop_by = 0;
	    by->snoop_on = 0;
	}
	return 1;
    }

    /*
     * Protect against snooping loops.
     */
    for (tmp = on; tmp; tmp = tmp->snoop_on) {
	if (tmp == by)
	    return (0);
    }
    
    /*
     * Terminate previous snoop, if any.
     */
    if (by->snoop_on) {
	by->snoop_on->snoop_by = 0;
	by->snoop_on = 0;
    }
    if (on->snoop_by) {
	on->snoop_by->snoop_on = 0;
	on->snoop_by = 0;
    }
    on->snoop_by = by;
    by->snoop_on = on;
    return (1);
}				/* set_new_snoop() */

/*
 * Bit of a misnomer now.  But I can't be bothered changeing the
 * name.  This will handle backspace resolution amongst other things,
 * (Pinkfish change)
 */
static void telnet_neg P2(char *, to, char *, from)
{
    int ch;
    char *first = to;

    while (1) {
	ch = (*from++ & 0xff);
	switch (ch) {
	case '\b':		/* Backspace */
	case 0x7f:		/* Delete */
	    if (to <= first)
		continue;
	    to -= 1;
	    continue;
	default:
	    if (ch & 0x80) {
		continue;
	    }
	    *to++ = ch;
	    if (ch == 0)
		return;
	    continue;
	}			/* switch() */
    }				/* while() */
}				/* telnet_neg() */

static void query_addr_name P1(object_t *, ob)
{
    static char buf[100];
    static char *dbuf = &buf[sizeof(int) + sizeof(int) + sizeof(int)];
    int msglen;
    int msgtype;

    sprintf(dbuf, "%s", query_ip_number(ob));
    msglen = sizeof(int) + strlen(dbuf) +1;

    msgtype = DATALEN;
    memcpy(buf, (char *) &msgtype, sizeof(msgtype));
    memcpy(&buf[sizeof(int)], (char *) &msglen, sizeof(msglen));

    msgtype = NAMEBYIP;
    memcpy(&buf[sizeof(int) + sizeof(int)], (char *) &msgtype, sizeof(msgtype));
    debug(512, ("query_addr_name: sent address server %s\n", dbuf));

    if (OS_socket_write(addr_server_fd, buf, msglen + sizeof(int) + sizeof(int)) == -1) {
	switch (errno) {
	case EBADF:
	    debug_message("Address server has closed connection.\n");
	    addr_server_fd = -1;
	    break;
	default:
	    debug_perror("query_addr_name: write", 0);
	    break;
	}
    }
}				/* query_addr_name() */

#define IPSIZE 200
typedef struct {
    char *name, *call_back;
    object_t *ob_to_call;
} ipnumberentry_t;

static ipnumberentry_t ipnumbertable[IPSIZE];

/*
 * Does a call back on the current_object with the function call_back.
 */
int query_addr_number P2(char *, name, char *, call_back)
{
    static char buf[100];
    static char *dbuf = &buf[sizeof(int) + sizeof(int) + sizeof(int)];
    int msglen;
    int msgtype;

    if ((addr_server_fd < 0) || (strlen(name) >=
		  100 - (sizeof(msgtype) + sizeof(msglen) + sizeof(int)))) {
	push_constant_string(name);
	push_null();
	apply(call_back, current_object, 2, ORIGIN_DRIVER);
	return 0;
    }
    strcpy(dbuf, name);
    msglen = sizeof(int) + strlen(name) +1;

    msgtype = DATALEN;
    memcpy(buf, (char *) &msgtype, sizeof(msgtype));
    memcpy(&buf[sizeof(int)], (char *) &msglen, sizeof(msglen));

    msgtype = (name[0] >= '0' && name[0] <= '9') ? NAMEBYIP : IPBYNAME;
    memcpy(&buf[sizeof(int) + sizeof(int)], (char *) &msgtype, sizeof(msgtype));

    debug(512, ("query_addr_number: sent address server %s\n", dbuf));

    if (OS_socket_write(addr_server_fd, buf, msglen + sizeof(int) + sizeof(int)) == -1) {
	switch (errno) {
	case EBADF:
	    debug_message("Address server has closed connection.\n");
	    addr_server_fd = -1;
	    break;
	default:
	    debug_perror("query_addr_name: write", 0);
	    break;
	}
	push_constant_string(name);
	push_null();
	apply(call_back, current_object, 2, ORIGIN_DRIVER);
	return 0;
    } else {
	int i;

/* We put ourselves into the pending name lookup entry table */
/* Find the first free entry */
	for (i = 0; i < IPSIZE && ipnumbertable[i].name; i++)
	    ;
	if (i == IPSIZE) {
/* We need to error...  */
	    push_constant_string(name);
	    push_null();
	    apply(call_back, current_object, 2, ORIGIN_DRIVER);
	    return 0;
	}
/* Create our entry... */
	ipnumbertable[i].name = make_shared_string(name);
	ipnumbertable[i].call_back = make_shared_string(call_back);
	ipnumbertable[i].ob_to_call = current_object;
	add_ref(current_object, "query_addr_number: ");
	return i + 1;
    }
}				/* query_addr_number() */

static void got_addr_number P2(char *, number, char *, name)
{
    int i;
    char *theName, *theNumber;

    /* First remove all the dested ones... */
    for (i = 0; i < IPSIZE; i++)
	if (ipnumbertable[i].name
	    && ipnumbertable[i].ob_to_call->flags & O_DESTRUCTED) {
	    free_string(ipnumbertable[i].call_back);
	    free_string(ipnumbertable[i].name);
	    free_object(ipnumbertable[i].ob_to_call, "got_addr_number: ");
	    ipnumbertable[i].name = NULL;
	}
    for (i = 0; i < IPSIZE; i++) {
	if (ipnumbertable[i].name && strcmp(name, ipnumbertable[i].name)== 0) {
	    /* Found one, do the call back... */
	    theName = ipnumbertable[i].name;
	    theNumber = number;
	    
	    if (isdigit(theName[0])) {
		char *tmp;
		
		tmp = theName;
		theName = theNumber;
		theNumber = tmp;
	    }
	    if (strcmp(theName, "0")) {
		push_string(theName, STRING_SHARED);
	    } else {
		push_null();
	    }
	    if (strcmp(number, "0")) {
		push_string(theNumber, STRING_SHARED);
	    } else {
		push_null();
	    }
	    push_number(i + 1);
	    safe_apply(ipnumbertable[i].call_back, ipnumbertable[i].ob_to_call,
		       3, ORIGIN_DRIVER);
	    free_string(ipnumbertable[i].call_back);
	    free_string(ipnumbertable[i].name);
	    free_object(ipnumbertable[i].ob_to_call, "got_addr_number: ");
	    ipnumbertable[i].name = NULL;
	}
    }
}				/* got_addr_number() */

#undef IPSIZE
#define IPSIZE 200
typedef struct {
    long addr;
    char *name;
} ipentry_t;

static ipentry_t iptable[IPSIZE];
static int ipcur;

#ifdef DEBUGMALLOC_EXTENSIONS
void mark_iptable() {
    int i;

    for (i=0; i < IPSIZE; i++)
	if (iptable[i].name)
	    EXTRA_REF(BLOCK(iptable[i].name))++;
}
#endif

char *query_ip_name P1(object_t *, ob)
{
    int i;

    if (ob == 0)
	ob = command_giver;
    if (!ob || ob->interactive == 0)
	return ((char *) NULL);
    for (i = 0; i < IPSIZE; i++) {
	if (iptable[i].addr == ob->interactive->addr.sin_addr.s_addr &&
	    iptable[i].name)
	    return (iptable[i].name);
    }
    return (inet_ntoa(ob->interactive->addr.sin_addr));
}

static void add_ip_entry P2(long, addr, char *, name)
{
    int i;

    for (i = 0; i < IPSIZE; i++) {
	if (iptable[i].addr == addr)
	    return;
    }
    iptable[ipcur].addr = addr;
    if (iptable[ipcur].name)
	free_string(iptable[ipcur].name);
    iptable[ipcur].name = make_shared_string(name);
    ipcur = (ipcur + 1) % IPSIZE;
}

char *query_ip_number P1(object_t *, ob)
{
    if (ob == 0)
	ob = command_giver;
    if (!ob || ob->interactive == 0)
	return ((char *) NULL);
    return (inet_ntoa(ob->interactive->addr.sin_addr));
}

#ifndef INET_NTOA_OK
/*
 * Note: if the address string is "a.b.c.d" the address number is
 *       a * 256^3 + b * 256^2 + c * 256 + d
 */
char *inet_ntoa P1(struct in_addr, ad)
{
    u_long s_ad;
    int a, b, c, d;
    static char addr[20];	/* 16 + 1 should be enough */

    s_ad = ad.s_addr;
    d = s_ad % 256;
    s_ad /= 256;
    c = s_ad % 256;
    s_ad /= 256;
    b = s_ad % 256;
    a = s_ad / 256;
    sprintf(addr, "%d.%d.%d.%d", a, b, c, d);
    return (addr);
}
#endif				/* INET_NTOA_OK */

char *query_host_name()
{
    static char name[40];

    gethostname(name, sizeof(name));
    name[sizeof(name) - 1] = '\0';	/* Just to make sure */
    return (name);
}				/* query_host_name() */

object_t *query_snoop P1(object_t *, ob)
{
    if (!ob->interactive || (ob->interactive->snoop_by == 0))
	return (0);
    return (ob->interactive->snoop_by->ob);
}				/* query_snoop() */

object_t *query_snooping P1(object_t *, ob)
{
    if (!ob->interactive || (ob->interactive->snoop_on == 0))
	return (0);
    return (ob->interactive->snoop_on->ob);
}				/* query_snooping() */

int query_idle P1(object_t *, ob)
{
    if (!ob->interactive)
	error("query_idle() of non-interactive object.\n");
    return (current_time - ob->interactive->last_time);
}				/* query_idle() */

#ifndef NO_ADD_ACTION
void notify_no_command()
{
    union string_or_func p;
    svalue_t *v;

    if (!command_giver || !command_giver->interactive)
	return;
    p = command_giver->interactive->default_err_message;
    if (command_giver->interactive->iflags & NOTIFY_FAIL_FUNC) {
	v = call_function_pointer(p.f, 0);
	free_funp(p.f);
	if (v && v->type == T_STRING) {
	    tell_object(command_giver, v->u.string);
	}
	command_giver->interactive->iflags &= ~NOTIFY_FAIL_FUNC;
	command_giver->interactive->default_err_message.s = 0;
    } else {
	if (p.s) {
	    tell_object(command_giver, p.s);
	    free_string(p.s);
	    command_giver->interactive->default_err_message.s = 0;
	} else {
	    tell_object(command_giver, default_fail_message);
	    tell_object(command_giver, "\n");
	}
    }
}				/* notify_no_command() */

static void clear_notify()
{
    union string_or_func dem;

    if (!command_giver || !command_giver->interactive)
	return;
    dem = command_giver->interactive->default_err_message;
    if (command_giver->interactive->iflags & NOTIFY_FAIL_FUNC) {
	free_funp(dem.f);
	command_giver->interactive->iflags &= ~NOTIFY_FAIL_FUNC;
    }
    else if (dem.s)
	free_string(dem.s);
    command_giver->interactive->default_err_message.s = 0;
}				/* clear_notify() */

void set_notify_fail_message P1(char *, str)
{
    if (!command_giver || !command_giver->interactive)
	return;
    clear_notify();
    command_giver->interactive->default_err_message.s = make_shared_string(str);
}				/* set_notify_fail_message() */

void set_notify_fail_function P1(funptr_t *, fp)
{
    if (!command_giver || !command_giver->interactive)
	return;
    clear_notify();
    command_giver->interactive->iflags |= NOTIFY_FAIL_FUNC;
    command_giver->interactive->default_err_message.f = fp;
    fp->hdr.ref++;
}				/* set_notify_fail_message() */
#endif /* NO_ADD_ACTION */

int replace_interactive P2(object_t *, ob, object_t *, obfrom)
{
    if (ob->interactive) {
	error("Bad argument 1 to exec()\n");
    }
    if (!obfrom->interactive) {
	error("Bad argument 2 to exec()\n");
    }
    if ((ob->flags & O_HIDDEN) != (obfrom->flags & O_HIDDEN)) {
	if (ob->flags & O_HIDDEN) {
	    num_hidden++;
	} else {
	    num_hidden--;
	}
    }
    ob->interactive = obfrom->interactive;
    /*
     * assume the existance of write_prompt and process_input in user.c until
     * proven wrong (after trying to call them).
     */
    ob->interactive->iflags |= (HAS_WRITE_PROMPT | HAS_PROCESS_INPUT);
    obfrom->interactive = 0;
    ob->interactive->ob = ob;
    ob->flags |= O_ONCE_INTERACTIVE;
    obfrom->flags &= ~O_ONCE_INTERACTIVE;
    add_ref(ob, "exec");
    free_object(obfrom, "exec");
    if (obfrom == command_giver) {
	command_giver = ob;
    }
    return (1);
}				/* replace_interactive() */

void outbuf_zero P1(outbuffer_t *, outbuf) {
    outbuf->real_size = 0;
    outbuf->buffer = 0;
}

int outbuf_extend P2(outbuffer_t *, outbuf, int, l)
{
    int limit;
    
    if (outbuf->buffer) {
	limit = MSTR_SIZE(outbuf->buffer);
	if (outbuf->real_size + l > limit) {
	    if (outbuf->real_size == USHRT_MAX) return 0; /* TRUNCATED */

	    /* assume it's going to grow some more */
	    limit = (outbuf->real_size + l) * 2;
	    if (limit > USHRT_MAX) {
		limit = outbuf->real_size + l;
		if (limit > USHRT_MAX) {
		    outbuf->buffer = extend_string(outbuf->buffer, USHRT_MAX);
		    return USHRT_MAX - outbuf->real_size;
		}
	    }
	    outbuf->buffer = extend_string(outbuf->buffer, limit);
	}
    } else {
	outbuf->buffer = new_string(l, "outbuf_add");
	outbuf->real_size = 0;
    }
    return l;
}

void outbuf_add P2(outbuffer_t *, outbuf, char *, str)
{
    int l, limit;
    
    if (!outbuf) return;
    l = strlen(str);
    if (outbuf->buffer) {
	limit = MSTR_SIZE(outbuf->buffer);
	if (outbuf->real_size + l > limit) {
	    if (outbuf->real_size == USHRT_MAX) return; /* TRUNCATED */

	    /* assume it's going to grow some more */
	    limit = (outbuf->real_size + l) * 2;
	    if (limit > USHRT_MAX) {
		limit = outbuf->real_size + l;
		if (limit > USHRT_MAX) {
		    outbuf->buffer = extend_string(outbuf->buffer, USHRT_MAX);
		    strncpy(outbuf->buffer + outbuf->real_size, str,
			    USHRT_MAX - outbuf->real_size);
		    outbuf->buffer[USHRT_MAX] = 0;
		    outbuf->real_size = USHRT_MAX;
		    return;
		}
	    }
	    outbuf->buffer = extend_string(outbuf->buffer, limit);
	}
    } else {
	outbuf->buffer = new_string(l, "outbuf_add");
	outbuf->real_size = 0;
    }
    strcpy(outbuf->buffer + outbuf->real_size, str);
    outbuf->real_size += l;
}

void outbuf_addchar P2(outbuffer_t *, outbuf, char, c)
{
    int limit;
    
    if (!outbuf) return;

    if (outbuf->buffer) {
	limit = MSTR_SIZE(outbuf->buffer);
	if (outbuf->real_size + 1 > limit) {
	    if (outbuf->real_size == USHRT_MAX) return; /* TRUNCATED */

	    /* assume it's going to grow some more */
	    limit = (outbuf->real_size + 1) * 2;
	    if (limit > USHRT_MAX) {
		limit = outbuf->real_size + 1;
		if (limit > USHRT_MAX) {
		    outbuf->buffer = extend_string(outbuf->buffer, USHRT_MAX);
		    *(outbuf->buffer + outbuf->real_size) = c;
		    outbuf->buffer[USHRT_MAX] = 0;
		    outbuf->real_size = USHRT_MAX;
		    return;
		}
	    }
	    outbuf->buffer = extend_string(outbuf->buffer, limit);
	}
    } else {
	outbuf->buffer = new_string(80, "outbuf_add");
	outbuf->real_size = 0;
    }
    *(outbuf->buffer + outbuf->real_size++) = c;
    *(outbuf->buffer + outbuf->real_size) = 0;
}

void outbuf_addv P2V(outbuffer_t *, outbuf, char *, format)
{
    char buf[LARGEST_PRINTABLE_STRING];
    va_list args;
    V_DCL(char *format);
    V_DCL(outbuffer_t *outbuf);

    V_START(args, format);
    V_VAR(outbuffer_t *, outbuf, args);
    V_VAR(char *, format, args);

    vsprintf(buf, format, args);
    va_end(args);

    if (!outbuf) return;
    
    outbuf_add(outbuf, buf);
}

void outbuf_fix P1(outbuffer_t *, outbuf) {
    if (outbuf && outbuf->buffer)
	outbuf->buffer = extend_string(outbuf->buffer, outbuf->real_size);
}

void outbuf_push P1(outbuffer_t *, outbuf) {
    (++sp)->type = T_STRING;
    if (outbuf && outbuf->buffer) {
	outbuf->buffer = extend_string(outbuf->buffer, outbuf->real_size);
	
	sp->subtype = STRING_MALLOC;
	sp->u.string = outbuf->buffer;
    } else {
	sp->subtype = STRING_CONSTANT;
	sp->u.string = "";
    }
}