/* bsd.c */

#include "copyright.h"

#include <stdio.h>

#ifdef WANT_ANSI
#ifdef __STDC__
#include <stdlib.h>
#endif /* __STDC__ */
#endif /* WANT_ANSI */

#include <string.h>
#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>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/errno.h>
#endif
#include <signal.h>
#include <ctype.h>

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "externs.h"
#include "interface.h"
#include "flags.h"

extern int errno;

extern void dispatch();

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

DESC *initializesock();
DESC *new_connection();
int process_output();
int process_input();

int make_socket(int port)
{
  int s;
  struct sockaddr_in server;
  int opt;
  s = socket(AF_INET, SOCK_STREAM, 0);
  if (s < 0) {
    perror("creating stream socket");
    exit(3);
  }
  opt = 1;
  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
		 (char *) &opt, sizeof(opt)) < 0) {
    perror("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))) {
    perror("binding stream socket");
    close(s);
    exit(4);
  }
  listen(s, 5);
  return s;
}

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

  mudstate.debug_cmd = (char *)"< shovechars >";
  nhashinit(&mudstate.desc_htab, 37);
  sock = make_socket(port);
  maxd = sock + 1;
  gettimeofday(&last_slice, (struct timezone *) 0);

  avail_descriptors = getdtablesize() - 4;

  while (mudstate.shutdown_flag == 0) {
    gettimeofday(&current_time, (struct timezone *) 0);
    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 = test_top() ? 0 : 1000;
    timeout.tv_usec = 0;
    next_slice = msec_add(last_slice, mudconf.timeslice);
    slice_timeout = timeval_sub(next_slice, current_time);

    FD_ZERO(&input_set);
    FD_ZERO(&output_set);
    if (ndescriptors < avail_descriptors)
      FD_SET(sock, &input_set);
    DESC_ITER_ALL(d) {
      if (!d->input_head)
	FD_SET(d->descriptor, &input_set);
      if (d->output_head)
	FD_SET(d->descriptor, &output_set);
    }

    if ((found = select(maxd, &input_set, &output_set,
			(fd_set *)NULL, &timeout)) < 0) {
      if (errno != EINTR) {
	perror("select");
      }
    } else {
      /* if !found then time for robot commands */
      if (!found) {
	do_top(mudconf.queue_chunk);
	continue;
      } else {
	do_top(mudconf.active_q_chunk);
      }
      (void) time(&now);
      if (FD_ISSET(sock, &input_set)) {
	if (!(newd = new_connection(sock))) {
	  if (errno
	      && errno != EINTR
	      && errno != EMFILE
	      && errno != ENFILE) {
	    perror("new_connection");
	  }
	} else {
	  if (newd->descriptor >= maxd)
	    maxd = newd->descriptor + 1;
	}
      }
      for (d = descriptor_list; d; d = dnext) {
	dnext = d->next;
	if (FD_ISSET(d->descriptor, &input_set)) {
	  d->last_time = now;
	  if (d->flags & DS_AUTODARK) {
		  d->flags &= ~DS_AUTODARK;
		  s_Flags(d->player, Flags(d->player) & ~DARK);
	  }
	  if (!process_input(d)) {
	    shutdownsock(d, R_SOCKDIED);
	    continue;
	  }
	}
	if (FD_ISSET(d->descriptor, &output_set)) {
	  if (!process_output(d)) {
	    shutdownsock(d, R_SOCKDIED);
	  }
	}
      }
    }
  }
}

const char *addrout(struct in_addr a)
{
  extern char *inet_ntoa();
  if(mudconf.use_hostname) {
    struct hostent *he;
    he = gethostbyaddr(&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));
}

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

  cmdsave = mudstate.debug_cmd;
  mudstate.debug_cmd = (char *)"< new_connection >";
  addr_len = sizeof(addr);
  newsock = accept(sock, (struct sockaddr *) & addr, &addr_len);
  if (newsock < 0) {
    return 0;
  } else 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
    dump_text(newsock, mudstate.site_fcache);
    shutdown(newsock, 2);
    close(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(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, mudstate.quit_fcache);
			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," <Reason: %s>",
					disc_reasons[reason]);
				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," <Reason: %s>",
					disc_reasons[reason]);
				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")
			time(&now);
			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, 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 = time(0);
		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 {
		shutdown(d->descriptor, 2);
		close(d->descriptor);
		freeqs(d);
		*d->prev = d->next;
		if (d->next)
			d->next->prev = d->prev;
		free_desc(d);
		ndescriptors--;
	}
}

void make_nonblocking(int s)
{
struct linger ling;

	if (fcntl(s, F_SETFL, FNDELAY) == -1) {
		perror("make_nonblocking: fcntl");
		panic("FNDELAY fcntl failed", 0);
	}
	ling.l_onoff = 0;
	ling.l_linger = 0;
	if (setsockopt(s, SOL_SOCKET, SO_LINGER,
		       (char *) &ling, sizeof(ling)) < 0) {
		perror("setsockopt");
	}
}

DESC *initializesock(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 = time(0);
  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;
}

void dump_text (int descriptor, FBLOCK *fb)
{
int	cnt, remaining;
char	*start;

	while (fb != NULL) {
		start = fb->data;
		remaining = fb->hdr.nchars;
		while (remaining > 0) {
#ifndef VMS
			cnt = write(descriptor, start, remaining);
#else
			cnt = socket_write(descriptor, start, remaining);
#endif
			if (cnt < 0)
				return;
			remaining -= cnt;
			start += cnt;
		}
		fb = fb->hdr.nxt;
	}
	return;
}

int process_output (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) {
#ifndef VMS
			cnt = write(d->descriptor, tb->hdr.start,
				tb->hdr.nchars);
#else
			cnt = socket_write(d->descriptor, tb->hdr.start,
				tb->hdr.nchars);
#endif
			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 (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 >";

#ifndef VMS
	got = in = read(d->descriptor, buf, sizeof buf);
#else
	got = in = socket_read(d->descriptor, buf, sizeof buf);
#endif
	if (got <= 0) {
		mudstate.debug_cmd = cmdsave;
		return 0;
	}
	if (!d->raw_input) {
		d->raw_input = alloc_lbuf("process_input.raw");
		d->raw_input_at = d->raw_input;
	}
	p = d->raw_input_at;
	pend = d->raw_input + 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) {
				save_command(d, d->raw_input);
			} else {
				in -= 1;	/* for newline */
			}
			p = d->raw_input;
		} else if ((*q == '\b') || (*q == 8) || (*q == 127)) {
			if (*q == 127)
				queue_string(d, "\b \b");
			else
				queue_string(d, " \b");
			in -= 2;
			if (d->raw_input_at > d->raw_input)
				(d->raw_input_at)--;
			if (p > d->raw_input_at)
				p--;
		} else if (p < pend && isascii(*q) && isprint(*q)) {
			*p++ = *q;
		} else {
			in--;
			if (p >= pend)
				lost++;
		}
	}
	if (p > d->raw_input) {
		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(int emergency, char *message)
{
DESC	*d, *dnext;

	do_rwho(NOTHING, NOTHING, RWHO_STOP);
	DESC_SAFEITER_ALL(d,dnext) {
		if (emergency) {
#ifndef VMS
			write(d->descriptor, message, strlen(message));
#else
			socket_write(d->descriptor, message, strlen(message));
#endif
			if (shutdown(d->descriptor, 2) < 0)
				perror("shutdown");
			close(d->descriptor);
		} else {
			queue_string(d, message);
			queue_write(d, "\r\n", 2);
			shutdownsock(d, R_GOING_DOWN);
		}
	}
	close(sock);
}

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

void bailout(sig, code, scp)
    int sig;
    int code;
    struct sigcontext *scp;
{
  char message[128];
  sprintf(message, "BAILOUT: caught signal %d code %d", sig, code);
  if((sig >= SIGINT) && (sig <= SIGSYS) && (sig != SIGKILL))
    panic(message, 1);
  else
    panic(message, 0);
  exit(7);
}

void set_signals()
{
  /* we don't care about SIGPIPE, we notice it in select() and write() */
  signal(SIGPIPE, SIG_IGN);

  /* standard termination signals */
  signal(SIGINT, bailout);
  signal(SIGTERM, bailout);
  /* things we need to core dump on, but want to trap */
/*  signal(SIGQUIT, bailout);
  signal(SIGILL, bailout);
  signal(SIGTRAP, bailout);
  signal(SIGIOT, bailout);
  signal(SIGEMT, bailout);
  signal(SIGFPE, bailout);
  signal(SIGBUS, bailout);
  signal(SIGSEGV, bailout);
  signal(SIGSYS, bailout); */
}

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

int fd;
int 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 *)0) {
			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;

	close(mudstate.reserved_fileid);
	if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
		queue_string(call_by, "Error in connecting to rwhod.\r\n");
		return;
	}

	if(connect(fd,&addr,sizeof(addr)) < 0) {
		queue_string(call_by, "Error in connecting to rwhod.\r\n");
		close(fd);
		mudstate.reserved_fileid = open(DEV_NULL, O_RDWR, 0);
		return;
	}

	rbuf = alloc_lbuf("dump_rusers");
#ifndef VMS
	while((red = read(fd, rbuf, sizeof(rbuf))) > 0) {
		rbuf[red] = '\0';
		strcat(rbuf,"\r\n");
		queue_string(call_by, rbuf);
	}
#else
	while((red = socket_read(fd, rbuf, sizeof(rbuf))) > 0) {
		rbuf[red] = '\0';
		strcat(rbuf,"\r\n");
		queue_string(call_by, rbuf);
	}
#endif
	free_lbuf(rbuf);
	close(fd);
	mudstate.reserved_fileid = open(DEV_NULL, O_RDWR, 0);
}
#endif /* LOCAL_RWHO_SERVER */