/* bsd.c */

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char RCSid[] = "$Id: bsd.c,v 1.7 1995/03/20 23:59:45 ambar Exp $";
USE(RCSid);
#endif

#ifdef VMS
#include "multinet_root:[multinet.include.sys]file.h"
#include "multinet_root:[multinet.include.sys]ioctl.h"
#include "multinet_root:[multinet.include]errno.h"
#else
#include <sys/file.h>
#include <sys/ioctl.h>
#endif
#include <signal.h>

#include "interface.h"
#include "file_c.h"
#include "flags.h"
#include "command.h"

#ifdef TLI
#include <sys/stream.h>
#include <sys/tiuser.h>
#include <sys/tihdr.h>
#include <stropts.h>
#include <poll.h>

#define SOCKETCLOSE(x)		t_unbind(x); \
				t_close(x);

extern int t_errno;
extern char *t_errlist[];

#else

#define SOCKETCLOSE(x)	if (shutdown((x), 2) < 0) \
				log_perror("NET", "FAIL", NULL, "shutdown"); \
			close(x);

#endif /* TLI */

extern void NDECL(dispatch);

static int sock;
static int ndescriptors = 0;
DESC *descriptor_list = NULL;

DESC *FDECL(initializesock, (int, struct sockaddr_in *, char *));
DESC *FDECL(new_connection, (int));
int FDECL(process_output, (DESC *));
int FDECL(process_input, (DESC *));

int 
make_socket(port)
    int port;
{
    int s, opt;

#ifdef TLI
    struct t_bind *bindreq, *bindret;
    struct sockaddr_in *serverreq, *serverret;

#else
    struct sockaddr_in server;

#endif

#ifdef TLI
    s = t_open(TLI_TCP, (O_RDWR | O_NDELAY), NULL);
#else
    s = socket(AF_INET, SOCK_STREAM, 0);
#endif
    if (s < 0) {
	log_perror("NET", "FAIL", NULL, "creating master socket");
	exit(3);
    }
#ifdef TLI
    bindreq = (struct t_bind *) t_alloc(s, T_BIND, T_ALL);
    if (!bindreq) {
	log_perror("NET", "FAIL", NULL, "t_alloc(req)");
	t_close(s);
	exit(4);
    }
    bindret = (struct t_bind *) t_alloc(s, T_BIND, T_ALL);
    if (!bindret) {
	log_perror("NET", "FAIL", NULL, "t_alloc(ret)");
	t_close(s);
	exit(4);
    }
    serverreq = (struct sockaddr_in *) bindreq->addr.buf;
    serverreq->sin_family = AF_INET;
    serverreq->sin_addr.s_addr = INADDR_ANY;
    serverreq->sin_port = htons(port);
    bindreq->addr.len = sizeof(struct sockaddr_in);
    bindret->addr.maxlen = sizeof(struct sockaddr_in);

    bindreq->qlen = 5;

    if (t_bind(s, bindreq, bindret) < 0) {
	log_perror("NET", "FAIL", NULL, "t_bind");
	t_close(s);
	exit(5);
    }
    serverret = (struct sockaddr_in *) bindret->addr.buf;
    if (serverreq->sin_port != serverret->sin_port) {
	log_perror("NET", "FAIL", NULL, "t_bind(portinuse)");
	t_close(s);
	exit(6);
    }
#else
    opt = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
		   (char *) &opt, sizeof(opt)) < 0) {
	log_perror("NET", "FAIL", NULL, "setsockopt");
    }
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(port);
    if (bind(s, (struct sockaddr *) &server, sizeof(server))) {
	log_perror("NET", "FAIL", NULL, "bind");
	close(s);
	exit(4);
    }
    listen(s, 5);
#endif /* TLI */
    return s;
}

#ifndef HAVE_GETTIMEOFDAY
#define get_tod(x)	{ (x)->tv_sec = time(NULL); (x)->tv_usec = 0; }
#else
#define get_tod(x)	gettimeofday(x, (struct timezone *)0)
#endif

void 
shovechars(port)
    int port;
{
    fd_set input_set, output_set;
    struct timeval last_slice, current_time, next_slice, timeout, slice_timeout;
    int maxd, found, check;
    DESC *d, *dnext, *newd;
    int avail_descriptors, maxfds;

#ifdef TLI
    struct pollfd *fds;
    int i;

#define CheckInput(x)	((fds[x].revents & POLLIN) == POLLIN)
#define CheckOutput(x)	((fds[x].revents & POLLOUT) == POLLOUT)
#else
#define CheckInput(x)	FD_ISSET(x, &input_set)
#define CheckOutput(x)	FD_ISSET(x, &output_set)
#endif
    mudstate.debug_cmd = (char *) "< shovechars >";
    sock = make_socket(port);
    maxd = sock + 1;
    get_tod(&last_slice);

#ifdef HAVE_GETDTABLESIZE
    maxfds = getdtablesize();
#else
    maxfds = sysconf(_SC_OPEN_MAX);
#endif

#ifdef TLI
    fds = (struct pollfd *) XMALLOC(sizeof(struct pollfd) *
				    maxfds, "shovechars");

#endif
    avail_descriptors = maxfds - 7;

    while (mudstate.shutdown_flag == 0) {
	get_tod(&current_time);
	last_slice = update_quotas(last_slice, current_time);

	process_commands();
	if (mudstate.shutdown_flag)
	    break;

	/* test for events */

	dispatch();

	/* any queued robot commands waiting? */

	timeout.tv_sec = que_next();
	timeout.tv_usec = 0;
	next_slice = msec_add(last_slice, mudconf.timeslice);
	slice_timeout = timeval_sub(next_slice, current_time);

#ifdef TLI
	for (i = 0; i < maxfds; i++)
	    fds[i].fd = -1;
#else
	FD_ZERO(&input_set);
	FD_ZERO(&output_set);
#endif

	/* Listen for new connections if there are free descriptors */

	if (ndescriptors < avail_descriptors) {
#ifdef TLI
	    fds[sock].fd = sock;
	    fds[sock].events = POLLIN;
#else
	    FD_SET(sock, &input_set);
#endif
	}
	/* Mark sockets that we want to test for change in status */

	DESC_ITER_ALL(d) {
#ifdef TLI
	    if (!d->input_head || d->output_head) {
		fds[d->descriptor].fd = d->descriptor;
		fds[d->descriptor].events = 0;
		if (!d->input_head)
		    fds[d->descriptor].events |= POLLIN;
		if (d->output_head)
		    fds[d->descriptor].events |= POLLOUT;
	    }
#else
	    if (!d->input_head)
		FD_SET(d->descriptor, &input_set);
	    if (d->output_head)
		FD_SET(d->descriptor, &output_set);
#endif
	}

	/* Wait for something to happen */
#ifdef TLI
	found = poll(fds, maxfds, timeout.tv_sec);
#else
	found = select(maxd, &input_set, &output_set, (fd_set *) NULL,
		       &timeout);
#endif
	if (found < 0) {
	    if (errno != EINTR) {
		log_perror("NET", "FAIL",
			   "checking for activity", "select");
	    }
	    continue;
	}
	/* if !found then time for robot commands */

	if (!found) {
	    do_top(mudconf.queue_chunk);
	    continue;
	} else {
	    do_top(mudconf.active_q_chunk);
	}

	/* Check for new connection requests */

	check = CheckInput(sock);
	if (check) {
	    newd = new_connection(sock);
	    if (!newd) {
#ifdef TLI
		check = (errno && (errno != ENFILE));
#else
		check = (errno && (errno != EINTR) &&
			 (errno != EMFILE) &&
			 (errno != ENFILE));
#endif
		if (check) {
		    log_perror("NET", "FAIL", NULL,
			       "new_connection");
		}
	    } else {
		if (newd->descriptor >= maxd)
		    maxd = newd->descriptor + 1;
	    }
	}
	/* Check for activity on user sockets */

	DESC_SAFEITER_ALL(d, dnext) {

	    /* Process input from sockets with pending input */

	    check = CheckInput(d->descriptor);
	    if (check) {

		/* Undo autodark */

		d->last_time = mudstate.now;
		if (d->flags & DS_AUTODARK) {
		    d->flags &= ~DS_AUTODARK;
		    s_Flags(d->player,
			    Flags(d->player) & ~DARK);
		}
		/* Process received data */

		if (!process_input(d)) {
		    shutdownsock(d, R_SOCKDIED);
		    continue;
		}
	    }
	    /* Process output for sockets with pending output */

	    check = CheckOutput(d->descriptor);
	    if (check) {
		if (!process_output(d)) {
		    shutdownsock(d, R_SOCKDIED);
		}
	    }
	}
    }
}

const char *
addrout(a)
    struct in_addr a;
{
    extern char *inet_ntoa();

    if (mudconf.use_hostname) {
	struct hostent *he;

	he = gethostbyaddr((char *) &a.s_addr, sizeof(a.s_addr), AF_INET);
	if (he)
	    return he->h_name;
	else
	    return (inet_ntoa(a));
    } else
	return (inet_ntoa(a));
}

#ifdef TLI
static void 
log_tli_error(key, routine, err, t_err)
    const char *key, *routine;
    int err, t_err;
{
    char *buff;

    STARTLOG(LOG_NET, "NET", "BIND")
	buff = alloc_mbuf("log_tli_error");
    sprintf(buff, "%s failed, errno=%d(%s), t_errno=%d(%s)",
	    routine, err, sys_errlist[err],
	    t_err, t_errlist[t_err]);
    log_text(buff);
    free_mbuf(buff);
    ENDLOG
}
#endif

#ifdef TLI
struct t_call *nc_call = (struct t_call *) NULL;

#endif

DESC *
new_connection(sock)
    int sock;
{
    int newsock;
    char *buff, *buff1, *cmdsave;
    DESC *d;
    struct sockaddr_in addr;

#ifndef TLI
    int addr_len;

#else
    int ret;

#endif

    cmdsave = mudstate.debug_cmd;
    mudstate.debug_cmd = (char *) "< new_connection >";
#ifdef TLI
    if (!nc_call) {
	nc_call = (struct t_call *) t_alloc(sock, T_CALL, T_ALL);
	if (!nc_call)
	    return 0;
    }
    if (t_listen(sock, nc_call) < 0)
	return 0;

    sighold(SIGALRM);
    newsock = t_open(TLI_TCP, (O_RDWR | O_NDELAY), NULL);
    sigrelse(SIGALRM);

    if (newsock < 0) {
	log_tli_error("RES", "t_open", errno, t_errno);
	return (0);
    }
    sighold(SIGALRM);
    ret = t_bind(newsock, NULL, NULL);
    sigrelse(SIGALRM);

    if (ret < 0) {
	log_tli_error("BIND", "t_bind", errno, t_errno);
	t_close(newsock);
	return (0);
    }
    sighold(SIGALRM);
    ret = t_accept(sock, newsock, nc_call);
    sigrelse(SIGALRM);

    if (ret < 0) {
	log_tli_error("RES", "t_accept", errno, t_errno);
	t_close(newsock);
	return (0);
    }
    /* push the read/write support module on the stream */

    sighold(SIGALRM);
    ret = ioctl(newsock, I_PUSH, "tirdwr");
    sigrelse(SIGALRM);

    if (ret < 0) {
	log_tli_error("STRM", "ioctl I_PUSH", errno, t_errno);
	t_close(newsock);
	return 0;
    }
    bcopy(nc_call->addr.buf, &addr, sizeof(struct sockaddr_in));

#else
    addr_len = sizeof(struct sockaddr);

    newsock = accept(sock, (struct sockaddr *) &addr, &addr_len);
    if (newsock < 0)
	return 0;
#endif /* TLI */

    if (site_check(addr.sin_addr, mudstate.access_list) == H_FORBIDDEN) {
	STARTLOG(LOG_NET | LOG_SECURITY, "NET", "SITE")
	    buff = alloc_mbuf("new_connection.LOG.badsite");
	sprintf(buff, "[%d/%s] Connection refused.  (Remote port %d)",
		newsock, addrout(addr.sin_addr), ntohs(addr.sin_port));
	log_text(buff);
	free_mbuf(buff);
	ENDLOG
	    fcache_rawdump(newsock, FC_CONN_SITE);
	SOCKETCLOSE(newsock);
	errno = 0;
	d = NULL;
    } else {
	buff = alloc_mbuf("new_connection.sitename");
	strcpy(buff, addrout(addr.sin_addr));
	STARTLOG(LOG_NET, "NET", "CONN")
	    buff1 = alloc_mbuf("new_connection.LOG.open");
	sprintf(buff1, "[%d/%s] Connection opened (remote port %d)",
		newsock, buff, ntohs(addr.sin_port));
	log_text(buff1);
	free_mbuf(buff1);
	ENDLOG
	    d = initializesock(newsock, &addr, buff);
	free_mbuf(buff);
	mudstate.debug_cmd = cmdsave;
    }
    mudstate.debug_cmd = cmdsave;
    return (d);
}

/* Disconnect reasons that get written to the logfile */

static const char *disc_reasons[] =
{
    "Unspecified",
    "Quit",
    "Inactivity Timeout",
    "Booted",
    "Remote Close or Net Failure",
    "Game Shutdown",
    "Login Retry Limit",
    "Logins Disabled",
    "Logout (Connection Not Dropped)",
    "Too Many Connected Players"
};

/* Disconnect reasons that get fed to A_ADISCONNECT via announce_disconnect */

static const char *disc_messages[] =
{
    "unknown",
    "quit",
    "timeout",
    "boot",
    "netdeath",
    "shutdown",
    "badlogin",
    "nologins",
    "logout"
};

void 
shutdownsock(d, reason)
    DESC *d;
    int reason;
{
    char *buff, *buff2;
    time_t now;

    if ((reason == R_LOGOUT) &&
    (site_check((d->address).sin_addr, mudstate.access_list) == H_FORBIDDEN))
	reason = R_QUIT;

    if (d->flags & DS_CONNECTED) {

	/* Do the disconnect stuff if we aren't doing a LOGOUT
	 * (which keeps the connection open so the player can connect
	 * to a different character).
	 */

	if (reason != R_LOGOUT) {
	    fcache_dump(d, FC_QUIT);
	    STARTLOG(LOG_NET | LOG_LOGIN, "NET", "DISC")
		buff = alloc_mbuf("shutdownsock.LOG.disconn");
	    sprintf(buff, "[%d/%s] Logout by ",
		    d->descriptor, d->addr);
	    log_text(buff);
	    log_name(d->player);
	    sprintf(buff, " <%s: %d cmds, %d bytes in, %d bytes out, %d secs>",
		    disc_reasons[reason], d->command_count, d->input_tot, 
		    d->output_tot, time(NULL) - d->connected_at);
	    log_text(buff);
	    free_mbuf(buff);
	    ENDLOG
	} else {
	    STARTLOG(LOG_NET | LOG_LOGIN, "NET", "LOGO")
		buff = alloc_mbuf("shutdownsock.LOG.logout");
	    sprintf(buff, "[%d/%s] Logout by ",
		    d->descriptor, d->addr);
	    log_text(buff);
	    log_name(d->player);
	    sprintf(buff, " <%s: %d cmds, %d bytes in, %d bytes out, %d secs>",
		    disc_reasons[reason], d->command_count, d->input_tot, 
		    d->output_tot, time(NULL) - d->connected_at);
	    log_text(buff);
	    free_mbuf(buff);
	    ENDLOG
	}

	/* If requested, write an accounting record of the form:
	 * Plyr# Flags Cmds ConnTime Loc Money [Site] <DiscRsn> Name
	 */

	STARTLOG(LOG_ACCOUNTING, "DIS", "ACCT")
	    now = mudstate.now - d->connected_at;
	buff = alloc_lbuf("shutdownsock.LOG.accnt");
	buff2 = decode_flags(GOD, Flags(d->player),
			     TYPE_PLAYER);
	sprintf(buff, "%d %s %d %d %d %d [%s] <%s> %s",
		d->player, buff2, d->command_count, (int) now,
		Location(d->player), Pennies(d->player),
		d->addr, disc_reasons[reason],
		Name(d->player));
	log_text(buff);
	free_lbuf(buff);
	free_sbuf(buff2);
	ENDLOG
	    announce_disconnect(d->player, d, disc_messages[reason]);
    } else {
	if (reason == R_LOGOUT)
	    reason = R_QUIT;
	STARTLOG(LOG_SECURITY | LOG_NET, "NET", "DISC")
	    buff = alloc_mbuf("shutdownsock.LOG.neverconn");
	sprintf(buff,
		"[%d/%s] Connection closed, never connected. <Reason: %s>",
		d->descriptor, d->addr, disc_reasons[reason]);
	log_text(buff);
	free_mbuf(buff);
	ENDLOG
    }
    process_output(d);
    clearstrings(d);
    if (reason == R_LOGOUT) {
	d->flags &= ~DS_CONNECTED;
	d->connected_at = mudstate.now;
	d->retries_left = mudconf.retry_limit;
	d->command_count = 0;
	d->timeout = mudconf.idle_timeout;
	d->player = 0;
	d->doing[0] = '\0';
	d->quota = mudconf.cmd_quota_max;
	d->last_time = 0;
	d->host_info = site_check((d->address).sin_addr,
				  mudstate.access_list) |
	    site_check((d->address).sin_addr,
		       mudstate.suspect_list);
	d->input_tot = d->input_size;
	d->output_tot = 0;
	welcome_user(d);
    } else {
	SOCKETCLOSE(d->descriptor);
	freeqs(d);
	*d->prev = d->next;
	if (d->next)
	    d->next->prev = d->prev;
	free_desc(d);
	ndescriptors--;
    }
}

void 
make_nonblocking(s)
    int s;
{
#ifndef TLI
#ifdef HAVE_LINGER
    struct linger ling;

#endif

#ifdef FNDELAY
    if (fcntl(s, F_SETFL, FNDELAY) == -1) {
	log_perror("NET", "FAIL", "make_nonblocking", "fcntl");
    }
#else
    if (fcntl(s, F_SETFL, O_NDELAY) == -1) {
	log_perror("NET", "FAIL", "make_nonblocking", "fcntl");
    }
#endif
#ifdef HAVE_LINGER
    ling.l_onoff = 0;
    ling.l_linger = 0;
    if (setsockopt(s, SOL_SOCKET, SO_LINGER,
		   (char *) &ling, sizeof(ling)) < 0) {
	log_perror("NET", "FAIL", "linger", "setsockopt");
    }
#endif
#endif /* not TLI */
}

DESC *
initializesock(s, a, addr)
    int s;
    struct sockaddr_in *a;
    char *addr;
{
    DESC *d;

    ndescriptors++;
    d = alloc_desc("init_sock");
    d->descriptor = s;
    d->flags = 0;
    d->connected_at = mudstate.now;
    d->retries_left = mudconf.retry_limit;
    d->command_count = 0;
    d->timeout = mudconf.idle_timeout;
    d->host_info = site_check((*a).sin_addr, mudstate.access_list) |
	site_check((*a).sin_addr, mudstate.suspect_list);
    d->player = 0;		/* be sure #0 isn't wizard.  Shouldn't be. */
    d->addr[0] = '\0';
    d->doing[0] = '\0';
    make_nonblocking(s);
    d->output_prefix = NULL;
    d->output_suffix = NULL;
    d->output_size = 0;
    d->output_tot = 0;
    d->output_lost = 0;
    d->output_head = NULL;
    d->output_tail = NULL;
    d->input_head = NULL;
    d->input_tail = NULL;
    d->input_size = 0;
    d->input_tot = 0;
    d->input_lost = 0;
    d->raw_input = NULL;
    d->raw_input_at = NULL;
    d->quota = mudconf.cmd_quota_max;
    d->last_time = 0;
    strncpy(d->addr, addr, 50);
    d->address = *a;		/* added 5/3/90 SCG */
    if (descriptor_list)
	descriptor_list->prev = &d->next;
    d->hashnext = NULL;
    d->next = descriptor_list;
    d->prev = &descriptor_list;
    descriptor_list = d;
    welcome_user(d);
    return d;
}

int 
process_output(d)
    DESC *d;
{
    TBLOCK *tb, *save;
    int cnt;
    char *cmdsave;

    cmdsave = mudstate.debug_cmd;
    mudstate.debug_cmd = (char *) "< process_output >";

    tb = d->output_head;
    while (tb != NULL) {
	while (tb->hdr.nchars > 0) {

	    cnt = WRITE(d->descriptor, tb->hdr.start,
			tb->hdr.nchars);
	    if (cnt < 0) {
		mudstate.debug_cmd = cmdsave;
		if (errno == EWOULDBLOCK)
		    return 1;
		return 0;
	    }
	    d->output_size -= cnt;
	    tb->hdr.nchars -= cnt;
	    tb->hdr.start += cnt;
	}
	save = tb;
	tb = tb->hdr.nxt;
	free_lbuf(save);
	d->output_head = tb;
	if (tb == NULL)
	    d->output_tail = NULL;
    }
    mudstate.debug_cmd = cmdsave;
    return 1;
}

int 
process_input(d)
    DESC *d;
{
    static char buf[LBUF_SIZE];
    int got, in, lost;
    char *p, *pend, *q, *qend;
    char *cmdsave;

    cmdsave = mudstate.debug_cmd;
    mudstate.debug_cmd = (char *) "< process_input >";

    got = in = READ(d->descriptor, buf, sizeof buf);
    if (got <= 0) {
	mudstate.debug_cmd = cmdsave;
	return 0;
    }
    if (!d->raw_input) {
	d->raw_input = (CBLK *) alloc_lbuf("process_input.raw");
	d->raw_input_at = d->raw_input->cmd;
    }
    p = d->raw_input_at;
    pend = d->raw_input->cmd + LBUF_SIZE - sizeof(CBLKHDR) - 1;
    lost = 0;
    for (q = buf, qend = buf + got; q < qend; q++) {
	if (*q == '\n') {
	    *p = '\0';
	    if (p > d->raw_input->cmd) {
		save_command(d, d->raw_input);
		d->raw_input = (CBLK *) alloc_lbuf("process_input.raw");
		p = d->raw_input_at = d->raw_input->cmd;
		pend = d->raw_input->cmd + LBUF_SIZE -
		    sizeof(CBLKHDR) - 1;
	    } else {
		in -= 1;	/* for newline */
	    }
	} else if ((*q == '\b') || (*q == 127)) {
	    if (*q == 127)
		queue_string(d, "\b \b");
	    else
		queue_string(d, " \b");
	    in -= 2;
	    if (p > d->raw_input->cmd)
		p--;
	    if (p < d->raw_input_at)
		(d->raw_input_at)--;
	} else if (p < pend && isascii(*q) && isprint(*q)) {
	    *p++ = *q;
	} else {
	    in--;
	    if (p >= pend)
		lost++;
	}
    }
    if (p > d->raw_input->cmd) {
	d->raw_input_at = p;
    } else {
	free_lbuf(d->raw_input);
	d->raw_input = NULL;
	d->raw_input_at = NULL;
    }
    d->input_tot += got;
    d->input_size += in;
    d->input_lost += lost;
    mudstate.debug_cmd = cmdsave;
    return 1;
}

void 
close_sockets(emergency, message)
    int emergency;
    char *message;
{
    DESC *d, *dnext;

    do_rwho(NOTHING, NOTHING, RWHO_STOP);
    DESC_SAFEITER_ALL(d, dnext) {
	if (emergency) {

	    WRITE(d->descriptor, message, strlen(message));
	    SOCKETCLOSE(d->descriptor);
	} else {
	    queue_string(d, message);
	    queue_write(d, "\r\n", 2);
	    shutdownsock(d, R_GOING_DOWN);
	}
    }
    SOCKETCLOSE(sock);
}

void 
NDECL(emergency_shutdown)
{
    close_sockets(1, (char *) "Going down - Bye");
}


/* ---------------------------------------------------------------------------
 * Signal handling routines.
 */

#ifndef SIGCHLD
#define SIGCHLD SIGCLD
#endif

static RETSIGTYPE sighandler();

NAMETAB sigactions_nametab[] =
{
    {(char *) "resignal", 3, 0, SA_RESIG},
    {(char *) "retry", 3, 0, SA_RETRY},
    {(char *) "default", 1, 0, SA_DFLT},
    {NULL, 0, 0, 0}};

void 
NDECL(set_signals)
{
    signal(SIGALRM, sighandler);
    signal(SIGCHLD, sighandler);
    signal(SIGHUP, sighandler);
    signal(SIGINT, sighandler);
    signal(SIGQUIT, sighandler);
    signal(SIGTERM, sighandler);
    signal(SIGPIPE, SIG_IGN);
#ifdef SIGXCPU
    signal(SIGXCPU, sighandler);
#endif

#ifndef linux			/* linux generates SIGFPE for all exceptions */
    signal(SIGFPE, sighandler);
#else
    signal(SIGFPE, SIG_IGN);
#endif				/* linux */

    if (mudconf.sig_action != SA_DFLT) {
	signal(SIGILL, sighandler);
	signal(SIGTRAP, sighandler);
	signal(SIGSEGV, sighandler);
	signal(SIGABRT, sighandler);
#ifdef SIGFSZ
	signal(SIGXFSZ, sighandler);
#endif
#ifdef SIGEMT
	signal(SIGEMT, sighandler);
#endif
#ifdef SIGBUS
	signal(SIGBUS, sighandler);
#endif
#ifdef SIGSYS
	signal(SIGSYS, sighandler);
#endif
    }
}

static void 
unset_signals()
{
    int i;

    for (i = 0; i < NSIG; i++)
	signal(i, SIG_DFL);
    abort();
}

static void 
check_panicking(sig)
    int sig;
{
    int i;

    /* If we are panicking, turn off signal catching and resignal */

    if (mudstate.panicking) {
	for (i = 0; i < NSIG; i++)
	    signal(i, SIG_DFL);
	kill(getpid(), sig);
    }
    mudstate.panicking = 1;
}

void 
log_signal(signame)
    const char *signame;
{
    STARTLOG(LOG_PROBLEMS, "SIG", "CATCH")
	log_text((char *) "Caught signal ");
    log_text((char *) signame);
    ENDLOG
}

#ifdef HAVE_STRUCT_SIGCONTEXT
static RETSIGTYPE 
sighandler(sig, code, scp)
    int sig, code;
    struct sigcontext *scp;

#else
static RETSIGTYPE 
sighandler(sig, code)
    int sig, code;

#endif
{
#ifdef HAVE_SYS_SIGLIST
#ifdef NEED_SYS_SIGLIST_DCL
    extern char *sys_siglist[];

#endif
#define signames sys_siglist
#else /* HAVE_SYS_SIGLIST */
#ifdef HAVE__SYS_SIGLIST
#ifdef NEED__SYS_SIGLIST_DCL
    extern char *_sys_siglist[];

#endif
#define signames _sys_siglist
#else /* HAVE__SYS_SIGLIST */
    static const char *signames[] =
    {
	"SIGZERO", "SIGHUP", "SIGINT", "SIGQUIT",
	"SIGILL", "SIGTRAP", "SIGABRT", "SIGEMT",
	"SIGFPE", "SIGKILL", "SIGBUS", "SIGSEGV",
	"SIGSYS", "SIGPIPE", "SIGALRM", "SIGTERM",
	"SIGURG", "SIGSTOP", "SIGTSTP", "SIGCONT",
	"SIGCHLD", "SIGTTIN", "SIGTTOU", "SIGIO",
	"SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF",
	"SIGWINCH", "SIGLOST", "SIGUSR1", "SIGUSR2"};

#endif /* HAVE__SYS_SIGLIST */
#endif /* HAVE_SYS_SIGLIST */

    char buff[32];

#ifdef HAVE_UNION_WAIT
    union wait stat;

#else
    int stat;

#endif

    switch (sig) {
    case SIGALRM:		/* Timer */
	mudstate.alarm_triggered = 1;
	break;
    case SIGCHLD:		/* Change in child status */
#ifndef SIGNAL_SIGCHLD_BRAINDAMAGE
	signal(SIGCHLD, sighandler);
#endif
#ifdef HAVE_WAIT3
	while (wait3(&stat, WNOHANG, NULL) > 0);
#else
	wait(&stat);
#endif
	break;
    case SIGHUP:		/* Perform a database dump */
	log_signal(signames[sig]);
	mudstate.dump_counter = 0;
	break;
    case SIGINT:		/* Log + ignore */
	log_signal(signames[sig]);
	break;
    case SIGQUIT:		/* Normal shutdown */
    case SIGTERM:
#ifdef SIGXCPU
    case SIGXCPU:
#endif
	check_panicking(sig);
	log_signal(signames[sig]);
	sprintf(buff, "Caught signal %s", signames[sig]);
	do_shutdown(NOTHING, NOTHING, 0, buff);
	break;
    case SIGILL:		/* Panic save + coredump */
    case SIGTRAP:
    case SIGFPE:
    case SIGSEGV:
#ifdef SIGXFSZ
    case SIGXFSZ:
#endif
#ifdef SIGEMT
    case SIGEMT:
#endif
#ifdef SIGBUS
    case SIGBUS:
#endif
#ifdef SIGSYS
    case SIGSYS:
#endif
	check_panicking(sig);
	log_signal(signames[sig]);
	report();
	sprintf(buff, "Caught signal %s", signames[sig]);
	do_shutdown(NOTHING, NOTHING, SHUTDN_PANIC, buff);

	/* Either resignal, or clear signal handling and retry the
	 * failed operation.  It should still fail, we haven't done
	 * anything to fix it, but it just _might_ succeed.  If
	 * that happens things will go downhill rapidly because we
	 * have closed all our files and cleaned up.  On the other
	 * hand, retrying the failing operation usually gets a
	 * usable coredump, whereas aborting from within the
	 * signal handler often doesn't.
	 */

	unset_signals();
	if (mudconf.sig_action == SA_RESIG)
	    signal(sig, SIG_DFL);
	else
	    return;
	exit(1);

    case SIGABRT:		/* Coredump. */
	check_panicking(sig);
	log_signal(signames[sig]);
	report();

	unset_signals();
	if (mudconf.sig_action == SA_RESIG)
	    signal(sig, SIG_DFL);
	else
	    return;
	exit(1);

    }
    signal(sig, sighandler);
    mudstate.panicking = 0;
#ifdef VMS
    return 1;
#else
    return;
#endif
}

#ifdef LOCAL_RWHO_SERVER
void 
dump_rusers(call_by)
    DESC *call_by;
{
    struct sockaddr_in addr;
    struct hostent *hp;
    char *rbuf, *p, *srv;
    int fd, red;

    if (!(mudconf.control_flags & CF_ALLOW_RWHO)) {
	queue_string(call_by, "RWHO is not available now.\r\n");
	return;
    }
    p = srv = mudconf.rwho_host;
    while ((*p != '\0') && ((*p == '.') || isdigit(*p)))
	p++;

    if (*p != '\0') {
	if ((hp = gethostbyname(srv)) == (struct hostent *) NULL) {
	    queue_string(call_by, "Error connecting to rwho.\r\n");
	    return;
	}
	(void) bcopy(hp->h_addr, (char *) &addr.sin_addr, hp->h_length);
    } else {
	unsigned long f;

	if ((f = inet_addr(srv)) == -1L) {
	    queue_string(call_by, "Error connecting to rwho.\r\n");
	    return;
	}
	(void) bcopy((char *) &f, (char *) &addr.sin_addr, sizeof(f));
    }
    addr.sin_port = htons(mudconf.rwho_data_port);
    addr.sin_family = AF_INET;

#ifdef TLI
    fd = tf_topen(TLI_TCP, O_RDWR);
    if (fd = <0) {
	queue_string(call_by, "Error in connecting to rwhod.\r\n");
	return;
    }
    if (t_bind(fd, NULL, NULL) < 0) {
	tf_close(fd);
	queue_string(call_by, "Error in connecting to rwhod.\r\n");
	return;
    }
    call = (struct t_call *) t_alloc(fd, T_CALL, T_ALL);
    if (call == NULL) {
	tf_close(fd);
	queue_string(call_by, "Error in connecting to rwhod.\r\n");
	return;
    }
    bcopy(addr, pbuf->addr.buf, sizeof(struct sockaddr_in));
    call->addr.len = sizeof(struct sockaddr_in);

#else
    fd = tf_socket(AF_INET, SOCK_STREAM);
    if (fd < 0) {
	queue_string(call_by, "Error in connecting to rwhod.\r\n");
	return;
    }
#endif

#ifdef TLI
    if (t_connect(fd, call, NULL) < 0)
	queue_string(call_by, "Error in connecting to rwhod.\r\n");
    tf_close(fd);
    return;
}

if (ioctl(fd, I_PUSH, "tirdwr") < 0) {
    queue_string(call_by, "Error in connecting to rwhod.\r\n");
    tf_close(fd);
    return;
}
#else
    if (connect(fd, &addr, sizeof(addr)) < 0) {
	queue_string(call_by, "Error in connecting to rwhod.\r\n");
	tf_close(fd);
	return;
    }
#endif
    rbuf = alloc_lbuf("dump_rusers");
    red = 0;
    while ((red = READ(fd, rbuf, LBUF_SIZE)) > 0) {
	rbuf[red] = '\0';
	queue_string(call_by, rbuf);
    }

    free_lbuf(rbuf);
    tf_close(fd);
}
#endif /* LOCAL_RWHO_SERVER */
