sima/autoconf/
sima/hosts/i386/
sima/mudlib/
sima/mudlib/kernel/
sima/mudlib/obj/
sima/mudlib/sys/
sima/synhash/mips/
/* Copyright 1991, 1993 - 1997 J"orn Rennecke */

#include <signal.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>

#include "machine.h"

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

#include "common.h"
#include "comm.h"
#include "exec.h"
#include "object.h"
#include "telnet.h"
#include "interpret.h"
#include "schedule.h"

int comm_nfds;

struct fd_entry fd_table[MAX_USER+MAX_MISC_DESCRIPTORS];
int port_number = PORTNO;
static int listen_socket;
static struct sockaddr_in host_ip_addr;
static struct in_addr host_ip_number;
static int num_users;
static struct interactive *first_user_for_flush;
static fd_set nullfds, *readfdsp;

static void new_user(struct fd_entry *, int accept_socket);
static void user_input(struct fd_entry *, int accept_socket);
static void set_socket_nonblocking(int);
static void set_close_on_exec(int);
static int telnet_neg(struct interactive *ip);
static void remove_interactive(struct interactive *ip);
static void remove_flush_entry(struct interactive *ip);
static void urgent_data_handler();

void init_comm() {
    int new_socket;
    int tmp;
    char host_name[128];
    struct hostent *he;

    /* Need to use gethostbyname to find out if it's PF_INET or PF_INET6 ??? */
    if (gethostname(host_name, sizeof host_name) < 0) {
        perror("gethostname");
        fatal("\n");
    }
    he = gethostbyname(host_name);
    if (!he) {
        perror("gethostbyname");
        fatal("\n");
    }
    host_ip_addr.sin_family = he->h_addrtype;
    host_ip_addr.sin_port = htons((u_short)port_number);
    host_ip_addr.sin_addr.s_addr = INADDR_ANY;
    new_socket = socket(host_ip_addr.sin_family, SOCK_STREAM, 0);
    if (new_socket < 0) {
        perror("socket");
        exit(1);
    }
    tmp = 1;
    if (setsockopt(new_socket, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof tmp)
	< 0)
    {
        perror("setsockopt");
        exit(1);
    }
    if (bind(
	  new_socket,
	  (struct sockaddr *)&host_ip_addr,
	  sizeof host_ip_addr))
    {
        if (errno == EADDRINUSE) {
            fatal("Socket already bound.\n");
	}
        perror("bind");
        exit(1);
    }
    if (listen(new_socket, 7) == -1) {
        perror("listen");
        exit(1);
    }
    set_socket_nonblocking(new_socket);
    set_close_on_exec(new_socket);
    if (new_socket >= comm_nfds)
        comm_nfds = new_socket + 1;
    signal(SIGPIPE, SIG_IGN);
    signal(SIGURG, urgent_data_handler);
    fd_table[new_socket].f = new_user;
    listen_socket = new_socket;
}

void start_comm(fd_set *fdsp) {
    readfdsp = fdsp;
    FD_SET(listen_socket, fdsp);
}

void set_socket_nonblocking(int new_socket) {
    int tmp;

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

static void set_close_on_exec(int new_socket) {
    fcntl(new_socket, F_SETFD, 1L);
}

void initialize_host_ip_number() {
    char host_name[145];
    struct hostent *hp;

    if (gethostname(host_name, sizeof host_name-1) < 0) {
        perror("gethostname");
	exit(1);
    }
    hp = gethostbyname(host_name);
    if (!hp) {
	perror("getting ipaddr of this machine:");
	exit(1);
    }
    host_ip_number = ((struct sockaddr_in *)hp->h_addr)->sin_addr;
}

const char *query_host_ip_number() {
    return inet_ntoa(host_ip_number);
}

#define TS_DEACTIVATED	0
#define TS_COMMAND	1
#define TS_CHARMODE	2
#define TS_STRINGMODE	3
#define TS_IAC          4
#define TS_WILL         5
#define TS_WONT         6
#define TS_DO           7
#define TS_DONT         8
#define TS_SB           9
#define TS_SB_IAC      10
#define TS_GOBBLE_NL   11
#define TS_GOBBLE_CR   12
#define TS_SYNCH       13

static volatile long urgent_data_time;

static void urgent_data_handler()
{
    ISR_SET_JOBS(JOB(urgent_data));
    urgent_data_time = current_time;
    signal(SIGURG, urgent_data_handler);
}

void urgent_data() {
    struct timeval timeout;
    fd_set exceptfds;
    int i;
    struct interactive *ip;

    CLEAR_JOB(urgent_data);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    memset((char *)&exceptfds, 255, comm_nfds + 7 >> 3);
    if (select(comm_nfds, 0, 0, &exceptfds, &timeout) > 0) {
        for (i = comm_nfds; --i >= 0;) {
            ip = fd_table[i].ip;
            if (!ip)
                continue;
            if (FD_ISSET(i, &exceptfds)) {
		ip->tn_data_state2 = ip->tn_data_state;
		ip->tn_data_state = TS_SYNCH;
                switch (ip->tn_state) {
                  case TS_COMMAND:
                  case TS_CHARMODE:
                  case TS_STRINGMODE:
		  case TS_GOBBLE_NL:
		  case TS_GOBBLE_CR:
                    ip->tn_state = TS_SYNCH;
                }
		call_hook(ip->hook[IH_SYNCH], ip->object, 0);
            }
        }
    /* 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) {
        SET_JOB(urgent_data);
    }
    REMAINING_JOBS(urgent_data);
}

static void new_user(struct fd_entry *fde, int accept_socket) {
    struct sockaddr_in addr;
    int length;
    int new_socket;
    struct interactive *ip;
    union svalue addr_string, ob;

    length = sizeof addr;
    new_socket = accept(accept_socket, (struct sockaddr *)&addr, &length);
    if (new_socket < 0) {
	if (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN)
	    return;
	fatal("accept failed\n");
    }
    set_socket_nonblocking(new_socket);
    set_close_on_exec(new_socket);
    if (new_socket >= NELEM(fd_table)) {
	union svalue sv;

	sv = driver_hook[H_NO_IPC_SLOT];
	if (!SV_IS_NUMBER(sv) && SV_IS_STRING(sv)) {
	    struct counted_string cstr = sv_string2(sv);
	    write(new_socket, cstr.start, cstr.len);
	}
	goto failure1;
    }
    ip = alloc_gen(sizeof *ip);
    if (!ip) {
	goto failure1;
    }
#ifdef ACCESS_CONTROL
    {
	char *message;

	message = allow_host_access(&addr.sin_addr, &ip->access_class);
#ifdef ACCESS_LOG
	{
	    FILE *log_file = fopen (ACCESS_LOG, "a");

	    if (log_file) {
		fprintf(log_file, "%s: %s\n",
		    inet_ntoa(addr->sin_addr), message ? "denied" : "granted");
		fclose(log_file);
	    }
	}
#endif
	if (message) {
	    write(new_socket, message, strlen(message));
	    write(new_socket, "\r\n", 2);
	    goto failure2;
	}
    }
#endif /* ACCESS_CONTROL */
    addr_string = make_string((uint8 *)&addr.sin_addr, 4);
    push_svalue(addr_string);
    ob =
      call_hook(driver_hook[H_NEW_USER], master_ob, 1);
    if (SV_IS_NUMBER(ob))
	goto failure3;
    if (SV_TYPE(ob) != T_OBJECT) {
	goto failure4;
    } else {
	struct object_x *x = alloc_object_x(ob);

	if (!x || OX_FLAGS(x) & O_X_INTERACTIVE) {
  failure4:
	    FREE_ALLOCED_SVALUE(ob);
  failure3:
	    FREE_SVALUE(addr_string);
  failure2:
	    free_gen(ip);
  failure1:
	    close(new_socket);
	    return;
	}
	OX_FLAGS(x) |= O_X_INTERACTIVE;
	if (SV_REFINC(ob))
	    O_REF(&SV_OBJECT(ob))++;
	FD_SET(new_socket, readfdsp);
	if (new_socket >= comm_nfds)
	    comm_nfds = new_socket + 1;
	x->user = ip;
	ip->socket = new_socket;
	ip->addr = addr.sin_addr;
	ip->object = ob;
	ip->message_length = 0;
	memset(ip->charset, 255, sizeof ip->charset);
	ip->charset['\n'/8] &= ~(1 << '\n' % 8);
	ip->charset['\0'/8] &= ~(1 << '\0' % 8);
	ip->last_command_time = current_time;
	ip->total_commands = 0;
	ip->quote_iac = 1;
	ip->tn_state = TS_COMMAND;
	ip->tn_data_state = TS_COMMAND;
	ip->tn_start = 0;
	ip->tn_end = 0;
	fd_table[new_socket].f = user_input;
	fd_table[new_socket].ip = ip;
	num_users++;
	push_svalue(ob);
	PUSH_REFERENCED_SVALUE(addr_string);
	PUSH_NUMBER(addr.sin_port);
	call_hook(driver_hook[H_LOGON], ob, 3);
	return;
    }
}

static void user_input(struct fd_entry *fde, int in_socket) {
    struct interactive *ip;
    int len;

    ip = fde->ip;
    len = USER_INPUT_BUFSIZE - ip->input_end;
    len = read(in_socket, ip->input + ip->input_end, len);
    if (len <= 0) {
	if (len == 0) {
	    remove_interactive(ip);
	    return;
	}
	switch(errno) {
	  default:
	    perror("read() from user socket");
	    remove_interactive(ip);
	    return;
	}
    }
    ip->input_end += len;
    ip->in_total += len;
    len = telnet_neg(ip);
    if (len >= 0) {
	union svalue sv;

	ip->total_commands++;
	push_svalue(make_astring(ip->input, len));
	sv = call_hook(ip->hook[IH_INPUT], ip->object, 1);
	FREE_SVALUE(sv);
	ip->tn_state = ip->tn_data_state;
	len = telnet_neg(ip);
	if (len >= 0) {
	    extern struct interactive **pending_link;

	    ip->chars_ready = len;
	    *pending_link = ip;
	    pending_link = &ip->next_pending;
	    FD_CLR(ip->socket, readfdsp);
	}
    }
}

static void user_output(struct fd_entry *fde, int in_socket) {
    struct interactive *ip;
    struct timeval timeout;

    ip = fde->ip;
    FD_SET(ip->socket, &nullfds);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    if (select(ip->socket + 1, 0, &nullfds, 0, &timeout) > 0) {
	/* Socket is writable again. */
	fde->f = user_input;
	FD_CLR(ip->socket, &nullfds);
	call_hook(ip->hook[IH_OUTPUT], ip->object, 0);
	fde->f(fde, in_socket);
    }
}

void pending_commands(struct interactive *ip) {
    for (; ip; ip = ip->next_pending) {
	union svalue sv;
	int len;

	if (ip->chars_ready < 0)
	    continue;
	ip->total_commands++;
	push_svalue(make_astring(ip->input, ip->chars_ready));
	sv = call_hook(ip->hook[IH_INPUT], ip->object, 1);
	FREE_SVALUE(sv);
	len = telnet_neg(ip);
	if (len >= 0) {
	    ip->chars_ready = len;
	    *pending_link = ip;
	    pending_link = &ip->next_pending;
	} else {
	    FD_SET(ip->socket, readfdsp);
	}
    }
}

static void h_telnet(struct interactive *ip, int c1, int c2) {
    union svalue sv;

    PUSH_NUMBER(c1);
    PUSH_NUMBER(c2);
    sv = call_hook(ip->hook[IH_TELNET_NEG], ip->object, 2);
    FREE_SVALUE(sv);
}

/* telnet_neg returns number of chars (maybe 0) if command found, else -1. */
static int telnet_neg(struct interactive *ip) {
    char *to, *from;
    int ch;
    char *first, *end;

    first = ip->input;
    from = &first[ip->tn_end];
    end = &first[ip->input_end];
    if (from >= end) {
	ip->input_end = ip->tn_end = ip->command_end;
	return -1;
    }
    to = &first[ip->command_end];
    do {
	ch = (*from++ & 0xff);
	switch(ip->tn_state) {
	    int state;

	  ts_data:
	    if (from >= end) {
		ip->input_end = ip->tn_end = ip->command_end = to - first;
		if (ip->input_end >= USER_INPUT_BUFSIZE) {
		    ip->input_end = ip->tn_end = ip->command_end = 0;
		    /* this looks like a super-long command.
		     * Return the input so far as partial command.
		     */
		    return to - first;
		}
		return -1;
	    }
	    ch = (*from++ & 0xff);
	  case TS_COMMAND:
	    switch(ch) {
	      case IAC:
	  new_iac:
		state = TS_IAC;
	  change_state:
		ip->tn_state = state;
		continue;
	      case '\b':	/* Backspace */
	      case 0x7f:	/* Delete */
		if (to > first)
		    to--;
		goto ts_data;
	      default:
		*to++ = ch;
	      case '\0':
		goto ts_data;
	      case '\r':
		/* rfc854: '\r' '\n' is the correct NVT sequence */
		if (from >= end) {
		    ip->tn_state = TS_GOBBLE_NL;
		} else {
		    ch = (*from & 0xff);
		    if (ch == '\n')
			from++;
		}
	  input_complete:
		ip->command_end = 0;
		ip->tn_end = from - first;
		return to - first;
	      case '\n':
		/* alas, some broken clients send '\n' '\r'. */
		ip->tn_state = TS_GOBBLE_CR;
		goto input_complete;
	    }
	  case TS_GOBBLE_NL:
	    if (ch != '\n')
		from--;
	    state = ip->tn_data_state;
	    goto change_state;
	  case TS_GOBBLE_CR:
	    if (ch != '\r')
		from--;
	    state = ip->tn_data_state;
	    goto change_state;
	  case TS_DEACTIVATED:
	    return -1;
	  case TS_CHARMODE:
	    if (ch == IAC) {
		state = TS_IAC;
		goto change_state;
	    }
	    *to++ = ch;
	    goto input_complete;
	  continue_stringmode:
	    ch = (*from++ & 0xff);
	  case TS_STRINGMODE:
	    if (ch == IAC) {
		ip->tn_state = TS_IAC;
		if (to > first)
		    goto input_complete;
		continue;
	    }
	    *to++ = ch;
	    if (from < end)
		goto continue_stringmode;
	    goto input_complete;
	  case TS_IAC:
	    switch(ch) {
	      case WILL:
		state = TS_WILL;
		goto change_state;
	      case WONT:
		state = TS_WONT;
		goto change_state;
	      case DO:
		state = TS_DO;
		goto change_state;
	      case DONT:
		state = TS_DONT;
		goto change_state;
	      case SB:
		ip->tn_start = to - first;
		state = TS_SB;
		goto change_state;
	      case DM:
	      data_mark:
		if (ip->tn_data_state == TS_SYNCH) {
		    struct timeval timeout;
		    FD_SET(ip->socket, &nullfds);
		    timeout.tv_sec = 0;
		    timeout.tv_usec = 0;
		    if (! select(ip->socket + 1, 0, 0, &nullfds, &timeout))
		    {
			/* Synch operation finished */
			ip->tn_data_state = ip->tn_data_state2;
		    }
		    FD_CLR(ip->socket, &nullfds);
		}
		break;
	      case NOP:
	      case GA:
	      default:
		break;
	    }
	    state = ip->tn_data_state;
	    goto change_state;
	  case TS_WILL:
	    h_telnet(ip, WILL, ch);
	    state = ip->tn_data_state;
	    goto change_state;
	  case TS_WONT:
	    h_telnet(ip, WONT, ch);
	    state = ip->tn_data_state;
	    goto change_state;
	  case TS_DO:
	    h_telnet(ip, DO, ch);
	    state = ip->tn_data_state;
	    goto change_state;
	  case TS_DONT:
	    h_telnet(ip, DONT, ch);
	    state = ip->tn_data_state;
	    goto change_state;
	  case TS_SB:
	    if (ch == IAC) {
		state = TS_SB_IAC;
		goto change_state;
	    }
	    *to++ = ch;
	    continue;
	  case TS_SB_IAC:
	  {
	    mp_int size;
	    uint8 *str;
	    union svalue sv;

	    if (ch == IAC) {
		*to++ = ch;
		state = TS_SB;
		goto change_state;
	    } else if (ch == SE &&
		(size = (to - first) - ip->tn_start - 1) >= 0 &&
		(str = &ip->input[ip->tn_start],
		  (sv = make_string(str + 1, size)).i) )
	    {
		PUSH_NUMBER(SB);
		PUSH_NUMBER(*str);
		PUSH_REFERENCED_SVALUE(sv);
		sv = call_hook(ip->hook[IH_TELNET_NEG], ip->object, 3);
		FREE_SVALUE(sv);
	    }
	    to = &first[ip->tn_start];
	    state = ip->tn_data_state;
	    goto change_state;
	  }
	  case TS_SYNCH:
	    if (ch == IAC) goto new_iac;
	    if (ch == DM) goto data_mark;
	    continue;
	  default:
	    debug_message("Bad state: 0x%x\n", ip->tn_state);
	    state = ip->tn_data_state;
	    goto change_state;
	}
    } while(from < end);
    ip->input_end = ip->tn_end = ip->command_end = to - first;
    if (ip->input_end == USER_INPUT_BUFSIZE) {
	/* telnet negotiation shouldn't have such large data chunks.
	 * Ignore all data altogether and return to text mode.
	 */
	ip->input_end = ip->tn_end = ip->command_end = 0;
	ip->tn_state = ip->tn_data_state;
    }
    return -1;
}

static void remove_interactive(struct interactive *ip) {
    union svalue ob;
    struct object_x * x;

    adtstat[COMM_COMMANDS]   += ip->total_commands;
    adtstat[COMM_IN_TOTAL]   += ip->in_total;
    adtstat[COMM_OUTPACKETS] += ip->out_packets;
    adtstat[COMM_OUTTOTAL]   += ip->out_total;
    FD_CLR(ip->socket, readfdsp);
    close(ip->socket);
    ob = ip->object;
    x = SV_OBJECT(ob).x.x;
    x->user = 0;
    if (!x->shadowing.i && !x->shadowed_by.i && O_REF(&SV_OBJECT(ob)) == 1)
    {

	SV_OBJECT(ob).x.uid = x->uid;
	free_block((uint8 *)x - sizeof(char *) + 1, sizeof *x);
    }
    FREE_ALLOCED_SVALUE(ob);
    free_gen(ip);
}

void remove_deactivated_interactives() {
    int i = comm_nfds;
    do {
	struct interactive *ip = fd_table[i-1].ip;
	if (ip && ip->tn_state == TS_DEACTIVATED) {
	    remove_interactive(ip);
	}
    } while(--i);
    CLEAR_JOB(remove_deactivated_interactives);
    EXTRA_JOBS();
}

/*
 * deactivated_interactive() may be called from user_input(),
 * pending_commands() or EXTRA_JOBS(). Note that the interactive being
 * deactivated is not necessarily the one being processed.
 */
static void deactivate_interactive(struct interactive *ip) {
    if (ip && !ip->tn_state != TS_DEACTIVATED) {
	/* set tn_data_state too in case we are called from inside telnet_neg */
	ip->tn_state = ip->tn_data_state = TS_DEACTIVATED;
	ip->chars_ready = -1;
	SET_JOB(remove_deactivated_interactives);
    }
}

void flush_message(struct interactive *ip, p_int len) {
    int n, length;
    int retries;

    if ( !(length = len) ) {
	if ( (length = ip->message_length) )
	    remove_flush_entry(ip);
	else
	    return;
    }
    ip->message_length = 0;
    for (retries = 6;;) {
	if ((n = write(ip->socket, ip->message_buf, length)) >= 0)
	    break;
	switch (errno) {
	  case EINTR:
	    if (--retries)
		continue;
	  case EWOULDBLOCK:
	    fd_table[ip->socket].f = user_output;
	    push_svalue(make_string(ip->message_buf + n, length - n));
	    PUSH_NUMBER(errno);
	    call_hook(ip->hook[IH_BLOCKED], ip->object, 2);
	    return;
	  default:
	    perror("flush_message(): write");
	    break;
	}
	deactivate_interactive(ip);
	return;
    }
    ip->out_packets++;
    ip->out_total += n;
    if (n != length) {
	fd_table[ip->socket].f = user_output;
	push_svalue(make_string(ip->message_buf + n, length - n));
	call_hook(ip->hook[IH_BLOCKED], ip->object, 1);
    }
    return;
}

void add_message(struct interactive *ip, char *str, p_int len) {
    int chunk, length;
    int min_length;
    char clobbered_char;
    int old_message_length;
    char *source, *end, *dest;

    min_length = MAX_OUTPUT_PACKET_SIZE;
    old_message_length = ip->message_length;
    adtstat[ADDMESS_CALLS]++;
    source = str;
    clobbered_char = source[len];
    source[len] = '\0';
    dest = &ip->message_buf[old_message_length];
    end = &ip->message_buf[sizeof ip->message_buf];
    do {
	char c;

	if (dest == end) {
	    c = '\0';
	} else for (;;) {
	    c = *source++;
	    if ( ip->charset[(c&0xff)>>3] & 1<<(c&7) ) {
		*dest++ = c;
	    } else if (c == '\0') {
		source--;
		break;
	    } else if (c == '\n') {
		/* Insert CR before NL */
		*dest++ = '\r';
		if (dest == end)
		    break;
		*dest++ = c;
	    } else if ( (unsigned char)c == IAC && ip->quote_iac) {
		*dest++ = c;
		if (dest == end)
		    break;
		*dest++ = c;
	    }
	    if (dest == end) {
		c = '\0';
		break;
	    }
	}
	chunk = dest - ip->message_buf;
	if (chunk < min_length)
	    break;
	/* send */
	flush_message(ip, chunk);
	dest = &ip->message_buf[0];
	if (c)
	    *dest++ = c;
    } while (*source);
    ip->message_length = length = dest - ip->message_buf;
    if (length) {
	if (!old_message_length ) {
	    /* buffer became 'dirty' */
	    if ( (ip->next_user_for_flush = first_user_for_flush) ) {
		first_user_for_flush->previous_user_for_flush = ip;
	    }
	    ip->previous_user_for_flush = 0;
	    first_user_for_flush = ip;
	    SET_JOB(flush_all_output);
	}
    } else {
	if (old_message_length) {
	    /* buffer has become empty */
	    remove_flush_entry(ip);
	}
    }
    str[len] = clobbered_char;
}

static void remove_flush_entry(struct interactive *ip) {
    if (ip->previous_user_for_flush) {
	ip->previous_user_for_flush->next_user_for_flush =
	  ip->next_user_for_flush;
    } else {
	first_user_for_flush = ip->next_user_for_flush;
    }
    if (ip->next_user_for_flush) {
	ip->next_user_for_flush->previous_user_for_flush =
	  ip->previous_user_for_flush;
    }
}

void flush_all_output() {
    struct interactive *u;

    CLEAR_JOB(flush_all_output);
    if ( (u = first_user_for_flush) ) {
	do {
	    flush_message(u, u->message_length);
	} while ( (u = u->next_user_for_flush) );
	first_user_for_flush = 0;
    }
    REMAINING_JOBS(flush_all_output);
}

const char *query_host_name() {
    static char name[21];
    char *p;
    
    gethostname(name, sizeof name-1);
    /* some platforms return the FQHN, but we don't want it. */
    p = strchr(name, '.');
    if (p)
	*p = '\0';
    return name;
}

svalue *f_text_message(svalue *sp, struct frame *fp) {
    svalue sv = *sp;
    if (SV_IS_NUMBER(sv) || !SV_IS_STRING(sv))
	bad_efun_arg(1);
    else {
	struct object_x *ox= SV_OBJECT(fp->object).x.x;
	if (OX_FLAGS(ox) & O_X_INTERACTIVE) {
	    struct counted_string cs = sv_string2(sv);
	    add_message(ox->user, cs.start, cs.len);
	}
	FREE_ALLOCED_SVALUE(sv);
	sp--;
    }
    return sp;
}

svalue *f_set_interactive_hook(svalue *sp, struct frame *fp) {
    svalue hn, hv, sv, *svp;
    struct object_x *ox;

    hn = sp[-1];
    if (!SV_IS_NUMBER(hn)) {
	bad_efun_arg(1);
	return sp;
    }
    sv = hv = sp[0];
    ox = SV_OBJECT(fp->object).x.x;
    if (OX_FLAGS(ox) & O_X_INTERACTIVE) {
	svp = &ox->user->hook[hn.i >> 1];
	sv = *svp;
	*svp = hv;
    }
    FREE_SVALUE(sv);
    return sp - 2;
}

#ifdef ACCESS_CONTROL
void refresh_access_data( void (*add_entry)(struct in_addr*, long*) ) {
    int i = comm_nfds;
    do {
	struct interactive *ip = fd_table[i-1].ip;
        if (ip)
            (*add_entry)(&ip->addr, &ip->access_class);
    } while(--i);
}
#endif /* ACCESS_CONTROL */