TinyMAZE/
TinyMAZE/config/
TinyMAZE/doc/
TinyMAZE/run/msgs/
TinyMAZE/src/
TinyMAZE/src/db/
TinyMAZE/src/ident/
TinyMAZE/src/io/
TinyMAZE/src/prog/
TinyMAZE/src/softcode/
TinyMAZE/src/util/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include "externs.h"
#include "db.h"
#include "config.h"

#if defined(SYSV) || defined(HPUX) || defined(SYSV_MAYBE)
#define mklinebuf(fp) setvbuf(fp, NULL, _IOLBF, 0)
#else
#define mklinebuf(fp) setlinebuf(fp)
#endif

static void init_args P((int, char *[]));
static void init_io P((void));
static void set_signals P((void));
static void save_uptime P((void));
static signal_type bailout P((int));
static signal_type do_sig_reboot P((int));
static signal_type do_sig_shutdown P((int));
static void process_commands P((void));
static int do_command P((DDATA *, char *));
static void check_connect P((DDATA *, char *));
static void do_notify_connect P((OBJ *, char, time_t));
static void do_main_engine P((void));
static DDATA *new_connection P((void));
static int process_input P((DDATA *));
static void close_sockets P((void));
static void open_sockets P((void));
static DDATA *initializesock P((int, char *, int));
static void get_uptime P((void));

#ifdef CATCH_SIGSEGV
static signal_type do_sig_segv P((int));
#endif /* CATCH_SIGSEGV */

DDATA *descriptor_list = NULL;

unsigned long total_input = 0, total_output = 0;

int exit_status;
int shutdown_flag = 0;
int quiet_reboot = 0;
int num_reboots = 0;
int num_crashes = 0;
int con_since_reboot = 0;
int con_since_boot = 0;
time_t maze_up_time;
time_t since_reboot;
time_t next_dump;
time_t next_mail_clear;
time_t now;

struct timed_reboot_struct timed_reboot = { 0, 0, "" };

static int sock = -1;
static int topd = 0;
static int sig_caught;
static bool descriptor_list_modified;

const char *NullFile = "logs/null";

extern int reserved;
extern FILE *db_read_ptr;

#ifdef USE_DEV_URANDOM
extern int rand_fd;
#endif /* USE_DEV_URANDOM */

fd_set input_set, output_set;

static void free_everything()
{
  extern char *swlm;

  stack_free(swlm);

  free_global_config();
  free_mlists();
  free_todomotd();
  free_pages();
  free_cmdav();
  free_channels();

  free_database();
}

int main(int argc, char *argv[])
{
  read_global_config_file();
  init_args(argc, argv);
  init_io();
  printf("--------------------------------\n");
  printf("MAZE online (pid = %d)\n", getpid());

  init_timer();
  open_sockets();
  read_host_lockouts();

  if(init_game(config.db_name) < 0)
  {
    log_error(tprintf("Couldn't load %s!", config.db_name));
    exit_nicely(136);
  }

  set_signals();

  time(&since_reboot);
  get_uptime();

  now = time(NULL);
  next_dump = now+config.dump_interval;
  next_mail_clear = now+config.old_mail_interval;

  do_main_engine();

  alarm(0);           /* Shut off timers */

#ifdef USE_DEV_URANDOM
  if(rand_fd != -1)
    close(rand_fd);
#endif /* USE_DEV_URANDOM */

  if(exit_status == 1)    /* Reboot */
  {
    log_important("MAZE rebooting.");
    notify_all("MAZE rebooting.", NULL);
  }
  else
  {
    log_important("Shutting down normally.");
    notify_all("MAZE shutting down.", NULL);
  }

  flush_all_output();
  close_sockets();

  if(exit_status != 1)
    unlink("logs/socket_table");
  do_haltall(root_obj);
  save_todomotd(0);
  save_todomotd(1);
  save_sc_funcs();
  free_sc_funcs();
  write_host_lockouts();
  plugin_shutdown();
  dump_database();

  free_everything();

  fcntl(sock, F_SETFD, 1);

  if(sig_caught > 0)
    log_important(tprintf("Shutting down due to signal %d", sig_caught));

  if(exit_status == 1)      /* Reboot */
  {
    num_reboots++;
    close_logs();
    save_uptime();

    stack_report();
    wait(0);
    execv("../bin/netmaze", argv);
    unlink("logs/socket_table");
    _exit(exit_status);
  }

  stack_report();
  exit_nicely(exit_status);
  return(0);
}

static void get_uptime()
{
  FILE *fp;
  char buf[4096];

  if((fp = fopen("logs/uptime", "r")) == NULL)
  {
    maze_up_time = time(NULL);
    return;
  }

  fgets(buf, sizeof(buf), fp);
  maze_up_time = atol(buf);
  fgets(buf, sizeof(buf), fp);
  quiet_reboot = atoi(buf);
  fgets(buf, sizeof(buf), fp);
  num_reboots = atoi(buf);
  fgets(buf, sizeof(buf), fp);
  num_crashes = atoi(buf);
  fgets(buf, sizeof(buf), fp);
  con_since_boot = atoi(buf);
  fclose(fp);
  unlink("logs/uptime");
}

void save_uptime()
{
  FILE *fp;

  if ( (fp = fopen("logs/uptime", "w")) == NULL )
     return;

  fprintf(fp, "%ld\n", maze_up_time);
  fprintf(fp, "%d\n", quiet_reboot);
  fprintf(fp, "%d\n", num_reboots);
  fprintf(fp, "%d\n", num_crashes);
  fprintf(fp, "%d\n", con_since_boot);
  fclose(fp);
}

static void init_args(int argc, char *argv[])
{
/* Change database? */
  if(argc > 1)
  {
    --argc;
    stack_free(config.db_name);
    SET(config.db_name, *++argv);
  }

/* Change port number? */
  if(argc > 1)
  {
    --argc;
    config.inet_port = atoi(*++argv);
  }
}

static void init_io()
{
  int fd;
  const char *stdout_logfile = "logs/out.log";

  fclose(stdin);

/* Open a link to the log file */
  fd = open(stdout_logfile, O_WRONLY | O_CREAT | O_APPEND, 0644);
  if(fd < 0)
  {
    perror("open()");
    log_error(tprintf("error opening %s for writing", stdout_logfile));
    exit_nicely(136);
  }
  close(fileno(stdout));
  if(dup2(fd, fileno(stdout)) == -1)
  {
    perror("dup2()");
    log_error("error converting standard output to logfile");
  }
  mklinebuf(stdout);

/* Attempt to convert standard error to logfile */
  close(fileno(stderr));
  if(dup2(fd, fileno(stderr)) == -1)
  {
    perror("dup2()");
    printf("error converting standard error to logfile\n");
  }
  mklinebuf(stderr);

/* This logfile reference is no longer needed */
  close(fd);

/* Save a file descriptor */
  reserved = open(NullFile, O_RDWR, 0);
}

#ifndef BSD_REAP
  #ifndef SIGCLD
static void handle_sigchld()
{
  int status;

  while(wait3(&status, WNOHANG, 0) > 0);
}
  #endif
#endif

static void set_signals()
{
  signal(SIGINT, bailout);

/* Comment these out so we get a core dump */
  /*  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);
      signal(SIGXCPU, bailout);
      signal(SIGXFSZ, bailout);
      signal(SIGVTALRM, bailout);
      signal(SIGUSR2, bailout);
   */

#ifdef BSD_REAP
  #ifdef SIGCLD
    signal(SIGCLD, handle_sigchld);
  #else
    signal(SIGCHLD, handle_sigchld);
  #endif
#else
  #ifdef SIGCLD
    signal(SIGCLD, SIG_IGN);
  #else
    signal(SIGCHLD, handle_sigchld);
  #endif
#endif

/* Let's catch SIGSEGV and make it just reboot the place after a delay */
/* The delay is there because if it continuously crashes it would chew */
/* up an indefinite amount of resources without the delay. */
#ifdef CATCH_SIGSEGV
  signal(SIGSEGV, do_sig_segv);
#endif /* CATCH_SIGSEGV */

/* Signal to reboot the MAZE */
  signal(SIGUSR1, do_sig_reboot);
  signal(SIGHUP, do_sig_reboot);

/* Signal to shutdown the MAZE */
  signal(SIGTERM, do_sig_shutdown);
}

void raw_notify(OBJ *player, char *msg, bool new_line)
{
  DDATA *d;

  for(d = descriptor_list;d;d = d->next)
  {
    if(check_state(d, STATE_CONNECTED) && d->player == player)
    {
      if(IS(player, TYPE_PLAYER, PLAYER_ANSI))
        queue_string(d, tprintf("%s\33[37;0m", color(msg, ADD)), new_line);
      else
        queue_string(d, color(msg, REMOVE), new_line);
    }
  }
}

void flush_all_output()
{
   DDATA *d;

   for(d = descriptor_list;d;d = d->next)
     process_output(d);
}

static void do_main_engine()
{
  struct sockaddr_in servaddr;
  struct timeval timeout;
  fd_set input_set, output_set;
  int ready;
  DDATA *d, *dnext;
  int opt = 1;

  log_io(tprintf("Starting up on port %d", config.inet_port));

  if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  {
    log_error(tprintf("socket() - %s\n", strerror(errno)));
    exit(1);
  }

  if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt,
    sizeof(opt)) < 0)
  {
    log_error(tprintf("setsockopt() - %s\n", strerror(errno)));
    exit(1);
  }

  if(sock >= topd)
    topd = sock+1;

  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(config.inet_port);

  if(bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
  {
    log_error(tprintf("bind() - %s\n", strerror(errno)));
    exit(1);
  }

  listen(sock, 5);

  load_db();
  if(db_read_ptr)
    fclose(db_read_ptr);

  plugin_init();
  plugin_startup();

/* Stick stuff to run once here */

  while(!shutdown_flag)
  {
    descriptor_list_modified = 0;
    run_softcode();
    process_commands();

    if(shutdown_flag)
      break;

/* Test for events */
    dispatch();

    FD_ZERO(&input_set);
    FD_ZERO(&output_set);
    FD_SET(sock, &input_set);

    for(d = descriptor_list;d;d = d->next)
    {
      FD_SET(d->descriptor, &input_set);
      if(d->output)
        FD_SET(d->descriptor, &output_set);
    }

    now = time(NULL);

/* Setup timeout for select() */
/* If there's softcode running make the timeout almost nothing */
    timeout.tv_sec = (code_list)?0:1;
    timeout.tv_usec = 1;

    if((ready = select(topd, &input_set, &output_set, NULL, &timeout)) <= 0)
    {
      if(ready < 0)
        if(errno != EINTR)
          log_error(tprintf("select() - %s\n", strerror(errno)));
      continue;
    }

    now = time(NULL);

/* sock will be in the input set if there's a new connection */
    if(FD_ISSET(sock, &input_set))
      if(!(d = new_connection()))
        continue;

    for(d = descriptor_list;d;d = dnext)
    {
      dnext = d->next;

      if(FD_ISSET(d->descriptor, &input_set))
        if(!process_input(d))
        {
          shutdownsock(d);
          continue;
        }
      if(FD_ISSET(d->descriptor, &output_set))
        if(!process_output(d))
          shutdownsock(d);
    }
  }
}

static bool chk_max_users(int sock)
{
  DDATA *d;
  int ctr = 0;
  char *msg = tprintf("Sorry, the maximum of %d users has been reached.\r\n",
    config.max_users);

  if(!config.max_users)
    return(1);

  for(d = descriptor_list;d;d = d->next)
    ctr++;

  if(ctr >= config.max_users)
  {
    write(sock, msg, strlen(msg));
    log_io("New connection was denied access. max_users has been reached.");
    close(sock);
    return(0);
  }

  return(1);
}

DDATA *new_connection()
{
  struct sockaddr_in servaddr;
  int size = sizeof(struct sockaddr_in);
  DDATA *newd;
  int newsock;
  char *s;

  if((newsock = accept(sock, (struct sockaddr *)&servaddr, &size)) < 0)
  {
    log_error(tprintf("accept() - %s\n", strerror(errno)));
    return((DDATA *)NULL);
  }

  if(!chk_max_users(newsock))
    return(NULL);
  if(!chk_host_lockout(newsock, inet_ntoa(servaddr.sin_addr)))
    return(NULL);

  newd = initializesock(newsock, inet_ntoa(servaddr.sin_addr), STATE_WAITCONNECT);

  s = ctime(&now);
  *(s+strlen(s)-1) = '\0';

  log_io(tprintf("CONNECT: descriptor %d, host %s@%s, time: %s",
    newd->descriptor, newd->user, newd->addr, s));

  return(newd);
}

static int process_input(DDATA *d)
{
  char buf[1024], input[1024];
  int got;
  char *p, *i;
  struct io_queue *io, *newio;

  if((got = read(d->descriptor, buf, sizeof(buf))) <= 0)
    return(0);

  if(got >= MAX_COMMAND_LEN)
    *(buf+MAX_COMMAND_LEN) = '\0';

  for(p = buf, i = input;*p && p-buf <= got;p++)
  {
    if(isprint(*p))
    {
      *i++ = *p;
      continue;
    }

    if(*p != '\n')
      continue;

    *i = '\0';

    newio = (struct io_queue *)stack_alloc(sizeof(struct io_queue), 1, 0);
    newio->next = NULL;

    if(!d->input)
      d->input = newio;
    else
    {
      for(io = d->input;io->next;io = io->next);
      io->next = newio;
    }

    SET(newio->text, input);

    total_input += strlen(input);
    d->total_input += strlen(input);

    i = input;
    while(*(p+1) && !isprint(*(p+1)) && *p != '\n')
      p++;
  }

  return(1);
}

float get_input(DDATA *d)
{
  if(!d)
    return((float)total_input/(float)(now-since_reboot));
  return((float)(d->total_input)/(float)(now-since_reboot));
}

float get_output(DDATA *d)
{
  if(!d)
    return((float)total_output/(float)(now-since_reboot));
  return((float)(d->total_output)/(float)(now-since_reboot));
}

bool process_output(DDATA *d)
{
  struct io_queue *io, *ionext;
  char *flushed_msg = "*** OUTPUT FLUSHED ***\r\n";

/* Hold their output if they're in a different mode (i.e. @paste) */
  if(!d->output || d->mode != MODE_NORMAL)
    return(1);

  if(d->flushed)
  {
    write(d->descriptor, flushed_msg, strlen(flushed_msg));
    total_output += strlen(flushed_msg);
    d->total_output += strlen(flushed_msg);
    d->flushed = 0;
  }

  for(io = d->output;io;io = ionext)
  {
    ionext = io->next;

    if(config.max_server_io)
      if(get_input(NULL)+get_output(NULL) > (float)(config.max_server_io))
      {
        d->output = io;
        return(1);
      }

    if(config.max_player_io)
      if(get_input(d)+get_output(d) > (float)(config.max_player_io))
      {
        d->output = io;
        return(1);
      }

    if(write(d->descriptor, io->text, strlen(io->text)) < 0)
    {
      if(errno == EWOULDBLOCK)
        return(1);
      return(0);
     }

    total_output += strlen(io->text);
    d->total_output += strlen(io->text);
    d->output_size -= strlen(io->text);

    stack_free(io->text);
    stack_free(io);
  }

  d->output = NULL;
  return(1);
}

static unsigned long output_queue_size(DDATA *d)
{
  struct io_queue *io;
  unsigned long total = 0L;

  for(io = d->output;io;io = io->next)
    total += strlen(io->text);
  return(total);
}

void queue_string(DDATA *d, char *str, bool newline)
{
  struct io_queue *io, *newio;
  char buf[4096];

  if(d->flushed ||
    (output_queue_size(d)+strlen(str)+2 > config.max_output && d->output))
  {
    d->output_size -= strlen(d->output->text);
    stack_free(d->output->text);
    newio = d->output;
    d->output = d->output->next;
    d->flushed = 1;
  }
  else
    newio = (struct io_queue *)stack_alloc(sizeof(struct io_queue), 1, 0);

  newio->next = NULL;

  if(!d->output)
    d->output = newio;
  else
  {
    for(io = d->output;io->next;io = io->next);
    io->next = newio;
  }

  sprintf(buf, "%s%s", str, (newline)?"\r\n":"");
  SET(newio->text, buf);
  d->output_size += strlen(buf);
}

static void free_io_queue(DDATA *des)
{
  struct io_queue *ionext;

  while(des->input)
  {
    ionext = des->input->next;
    stack_free(des->input->text);
    stack_free(des->input);
    des->input = ionext;
  }
  while(des->output)
  {
    ionext = des->output->next;
    stack_free(des->output->text);
    stack_free(des->output);
    des->output = ionext;
  }
}

static void hostname_lookup_shutdown(DDATA *d)
{
  if(d->lookup_pid)
  {
    kill(d->lookup_pid, SIGKILL);
    shmdt((char *)d->update_flag);
    shmdt(d->user);
    shmdt(d->addr);
  }
  else
  {
    stack_free(d->update_flag);
    stack_free(d->user);
    stack_free(d->addr);
  }
}

void shutdownsock(DDATA *des)
{
  OBJ *guest_player = NULL;
  DDATA *d, *dprev = NULL;

  descriptor_list_modified = 1;

  free_io_queue(des);

  if(des->lookup_pid)
  {
    log_io(tprintf("Killing hostname lookup for descriptor %d (pid %d)",
      des->descriptor, des->lookup_pid));
    kill(des->lookup_pid, SIGKILL);
  }

  if(check_state(des, STATE_CONNECTED))
  {
    if(des->player)
    {
      if(Guest(des->player))
        guest_player = des->player;

      log_io(tprintf("DISCONNECT descriptor %d player %s. Session time: %s",
        des->descriptor, unparse_object(des->player, des->player),
        format_time(now-des->connected_at)));

      if(!(*atr_get(des->player, "LHIDE")))
        com_send(config.connect_channel, NULL,
          tprintf("* DISCONNECT: %s(#%d)",
          name(des->player), des->player->dbref));

      com_send(config.adm_connect_channel, NULL,
        tprintf("* DISCONNECT: %s(#%d) (descriptor %d)",
          name(des->player), des->player->dbref, des->descriptor));

      announce_disconnect(des);
    }
  }
  else
  {
    log_io(tprintf("DISCONNECT: descriptor %d never connected",
      des->descriptor));
    if(des->create_info)
    {
      stack_free(des->create_info->name);
      if(des->create_info->password)
      {
        stack_free(des->create_info->password);
        if(des->create_info->passverify)
          stack_free(des->create_info->passverify);
      }
      stack_free(des->create_info);
    }
  }

  if(FD_ISSET(des->descriptor, &input_set))
    FD_CLR(des->descriptor, &input_set);
  if(FD_ISSET(des->descriptor, &output_set))
    FD_CLR(des->descriptor, &output_set);

  free_hashtable(des);

/* Remove descriptor from descriptor_list */
  for(d = descriptor_list;d;d = d->next)
  {
    if(d == des)
      break;
    dprev = d;
  }

  if(!d)
    return;

  if(dprev)
    dprev->next = d->next;
  else
    descriptor_list = d->next;

  hostname_lookup_shutdown(des);
  close(des->descriptor);
  stack_free(des);

  if(!guest_player)
    return;

  for(d = descriptor_list;d;d = d->next)
    if(d->player && d->player == guest_player)
      break;

  if(!d)
    destroy_guest(guest_player);
}

static void close_sockets()
{
  FILE *fp;
  DDATA *d, *dnext;

  if(!(fp = fopen("logs/socket_table", "w")))
    return;        /* Screwed */

  fprintf(fp, "%d\n", sock);
  fcntl(sock, F_SETFD, 0);   /* Make it so sock doesn't close */

  for(d = descriptor_list;d;d = dnext)
  {
    dnext = d->next;

    if((check_state(d, STATE_CONNECTED) || check_state(d, STATE_WAITINPUT)) &&
      d->player)
    {
      fprintf(fp, "%010d %010d %010ld %010ld %010d\n",
        d->descriptor, d->mode, d->connected_at,
        d->last_time, d->player->dbref);
      fcntl(d->descriptor, F_SETFD, 0);   /* Don't close this */
      hostname_lookup_shutdown(d);
      free_hashtable(d);
      stack_free(d);
    }
    else
      shutdownsock(d);
  }

  descriptor_list = NULL;

  fclose(fp);
}

static void open_sockets()
{
  FILE *fp;
  char buf[4096];
  DDATA *d, *nextd, *oldlist;
  int desc;
  struct sockaddr_in in;
  int len;

  if(!(fp = fopen("logs/socket_table", "r")))
    return;          /* Must not be a reboot */

  fgets(buf, sizeof(buf), fp);
  sock = atoi(buf);
  fcntl(sock, F_SETFD, 1);  /* Return it to its normal state */

  while(fgets(buf, sizeof(buf), fp))
  {
    desc = atoi(buf);
    len = sizeof(struct sockaddr_in);

    fcntl(desc, F_SETFD, 1);    /* Return it to its normal state */

    getpeername(desc, (struct sockaddr *)&in, &len);

    d = initializesock(desc, inet_ntoa(in.sin_addr), STATE_RELOADCONNECT);

    d->mode = atoi(buf+11);
    d->connected_at = atol(buf+22);
    d->last_time = atol(buf+33);
    d->player_ref = atoi(buf+44);
  }

/* Flip the descriptor list back the right way */
  oldlist = descriptor_list;
  descriptor_list = NULL;

  for(d = oldlist;d;d = nextd)
  {
    nextd = d->next;
    d->next = descriptor_list;
    descriptor_list = d;
  }

  close(sock);   /* do_main_engine() will open this back up */
  fclose(fp);
  unlink("logs/socket_table");
}

static void ip_to_hostname(char *hostname)
{
  struct hostent *hent;
  struct in_addr addr;

  if(!inet_aton(hostname, &addr))
    return;

  if((hent = gethostbyaddr((char *)&(addr.s_addr), sizeof(addr.s_addr),
    AF_INET)))
  {
/* At this point 'hostname' points to 100 bytes of shared memory */
    strncpy(hostname, hent->h_name, 99);
    hostname[99] = '\0';
  }
}

static void lookup_hostname(DDATA *d)
{
  int shared_mem_id1, shared_mem_id2, shared_mem_id3;
  char *shared_mem_ptr1, *shared_mem_ptr2;
  int *shared_mem_ptr3;
  int *des, *flg;
  char *temp;
  struct shmid_ds ds;

  shared_mem_id1 = shmget(IPC_PRIVATE, sizeof(char)*100, 0660);
  shared_mem_id2 = shmget(IPC_PRIVATE, sizeof(char)*100, 0660);
  shared_mem_id3 = shmget(IPC_PRIVATE, sizeof(int)*2, 0660);

  if(shared_mem_id1 == -1 || shared_mem_id2 == -1 ||
    shared_mem_id3 == -1)
  {
    log_error("Couldn't allocate shared memory!");

    if(shared_mem_id3 == -1)
    {
      shmctl(shared_mem_id1, IPC_RMID, &ds);
      shmctl(shared_mem_id2, IPC_RMID, &ds);
    }
    if(shared_mem_id2 == -1)
      shmctl(shared_mem_id1, IPC_RMID, &ds);

    *(d->update_flag) = 0;
    return;
  }

  shared_mem_ptr1 = shmat(shared_mem_id1, 0, 0);
  shared_mem_ptr2 = shmat(shared_mem_id2, 0, 0);
  shared_mem_ptr3 = (int *)shmat(shared_mem_id3, 0, 0);

  temp = d->addr;
  d->addr = shared_mem_ptr1;
  strcpy(d->addr, temp);
  stack_free(temp);
  temp = d->user;
  d->user = shared_mem_ptr2;
  strcpy(d->user, temp);
  stack_free(temp);
  flg = d->update_flag;
  d->update_flag = shared_mem_ptr3;
  *(d->update_flag) = *flg;
  stack_free(flg);
  des = shared_mem_ptr3+1;
  *des = d->descriptor;

  if(!(d->lookup_pid = fork()))
  {
    ip_to_hostname(d->addr);
    strcpy(d->user, ident_id(*des, 60));
    if(*(d->update_flag) != -1)
      *(d->update_flag) = 1;
    exit(0);
  }

  shmctl(shared_mem_id1, IPC_RMID, &ds);
  shmctl(shared_mem_id2, IPC_RMID, &ds);
  shmctl(shared_mem_id3, IPC_RMID, &ds);
}

DDATA *initializesock(int des, char *addr, int state)
{
  DDATA *d;

  d = (DDATA *)stack_alloc(sizeof(DDATA), 1, 0);
  d->descriptor = des;
  d->state = find_state(state);
  d->create_info = NULL;
  d->mode = MODE_NORMAL; /* Calling function is responsible */
  d->input = NULL;
  d->output = NULL;
  d->total_input = 0;
  d->total_output = 0;
  d->output_size = 0;
  d->flushed = 0;
  d->player = NULL;
  SET(d->user, "???");
  SET(d->addr, addr);
  d->update_flag = (int *)stack_alloc(sizeof(int), 1, 0);
  *(d->update_flag) = (state == STATE_RELOADCONNECT)?-1:0;
  d->lookup_pid = 0;
  d->connected_at = time(0);
  d->last_time = time(0);
  d->hashtable = NULL;

  fcntl(des, F_SETFL, O_NONBLOCK);

  d->next = descriptor_list;
  descriptor_list = d;

  if(des >= topd)
    topd = des+1;

  lookup_hostname(d);

  if(d->state->func)
    d->state->func(d, "");

  return(d);
}

void show_file(DDATA *d, char *filename)
{
  FILE *fp;
  char buf[4096];

  if(!(fp = fopen(filename, "r")))
    return;

  while(fgets(buf, sizeof(buf), fp))
  {
    *last_char(buf) = '\0';
    queue_string(d, color(tprintf("%s|n|", buf), ADD), 1);
  }

  fclose(fp);
}

static void process_commands()
{
  DDATA *d, *dnext;
  int nprocessed;
  struct io_queue *io, *ionext;
  bool cont;

  do
  {
    nprocessed = 0;

    for(d = descriptor_list;d;d = dnext)
    {
      cont = 0;

      if(!d->input)
      {
        dnext = d->next;
        continue;
      }

      for(io = d->input;io;io = ionext)
      {
        ionext = io->next;

        nprocessed++;

        if(!do_command(d, io->text))
        {
          show_file(d, config.leave_msg_file);
          shutdownsock(d);  /* This should flag descriptor_list_modified */
        }

        if(descriptor_list_modified)
        {
          descriptor_list_modified = 0;
          dnext = descriptor_list;
          cont = 1;
          break;
        }

        stack_free(io->text);
        stack_free(io);
      }

/* Clear the stack after every command */
      stack_unalloc();

      if(cont)
        continue;

      dnext = d->next;
      d->input = NULL;
    }
  } while(nprocessed);
}

static int do_command(DDATA *d, char *command)
{
  time_t was_idle = now-d->last_time;

  if(is_pasting(d))
  {
    add_more_paste(d, command);
    return(1);
  }

  if(!*command)
    return(1);

  d->last_time = now;

/* See if what they typed should be regarded as input */
  if(check_input(d, command))
    return(1);

  if(!strcmp(command, QUIT_COMMAND))
    return(0);

  if(!check_state(d, STATE_CONNECTED))
  {
    check_connect(d, command);
    return(1);
  }

  if(was_idle > 1800)
    do_notify_connect(d->player, 1, was_idle);

  process_command(d, d->player, command, NULL);

  if(was_idle > 300)
    check_mail(d->player, 1);

  return(1);
}

int check_password(OBJ *player, char *pass)
{
  if(!strcmp(Pass(player), pass) ||
    !strcmp(crypt(pass, "XX"), Pass(player)))
  {
    return(1);
  }

  return(0);
}

void emergency_shutdown()
{
  log_error("Emergency shutdown.");
  shutdown_flag = 1;
  exit_status = 136;
  close_sockets();
}

static void check_connect(DDATA *d, char *msg)
{
  OBJ *player;
  int notified = 0;
  int connects;

  if(!d->state->func || !d->state->func(d, msg))
    return;

  player = d->player;

  con_since_boot++;
  con_since_reboot++;

  log_io(tprintf("CONNECTED: %s%s on descriptor %d%s",
    name(player), (Guest(player))?" |+W|(GUEST)|n|":"",
    d->descriptor, (*atr_get(player, "LHIDE"))?" |+W|(HIDDEN)":""));

  if(!(*atr_get(player, "LHIDE")))
    com_send(config.connect_channel, NULL,
      tprintf("* CONNECT: %s(#%d)", name(player), player->dbref));

  com_send(config.adm_connect_channel, NULL,
    tprintf("* CONNECT: %s(#%d) (descriptor %d, host %s@%s)%s",
    name(player), player->dbref, d->descriptor, d->user, d->addr,
    (*atr_get(player, "LHIDE"))?" |+W|(HIDDEN)":""));

/* Stop multiple connections */
  if(get_class(player) == CLASS_CITIZEN)
  {
    DDATA *d2, *d2next;

    for(d2 = descriptor_list;d2;d2 = d2next)
    {
      d2next = d2->next;

      if(d2 == d)
        continue;
      if(check_state(d2, STATE_CONNECTED))
        continue;
      if(d2->player == player)
      {
        set_state(d, STATE_CONNECTED);
        d->player = player;

        do_notify_connect(player, 0, 0);
        notified++;

        queue_string(d2, "You're getting booted!", 1);
        process_output(d2);
        shutdownsock(d2);
      }
    }
  }

  set_state(d, STATE_CONNECTED);
  d->connected_at = now;

  if(!notified)
    do_notify_connect(player, 0, 0);

/* Give players a message on connection */
  show_file(d, config.connect_msg_file);

  announce_connect(d);

  if(!(Guest(player)))
  {
    my_atr_add(player, "CONNECTS", 1);

/* Show the player the +motd */
/*    if(num_motd)
      if(atoi(atr_get(player, "MOTD")))
      {
        notify(player, "");
        do_motd(player, "", "");
        notify(player, "");
      }
*/
    connects = atoi(atr_get(player, "CONNECTS"));
    if(connects == 1)
      notify(player, "This is your first time here. Welcome!");
    else
      notify(player, tprintf("You've connected %d times. Welcome back!",
        connects));

/* Notify player if they have mail */
    notify(player, "");
    check_mail(player, 0);
    notify(player, "");
  }
  else
  {
    notify(player, tprintf("Welcome to %s; your name is %s",
      config.maze_name, player->name));
  }

  do_look_around(player);
}

int boot_off(OBJ *player)
{
  DDATA *d;

  for(d = descriptor_list;d;d = d->next)
  {
    if(check_state(d, STATE_CONNECTED) && d->player == player)
    {
      process_output(d);
      shutdownsock(d);
      return(1);
    }
  }

  return(0);
}

static signal_type bailout(int sig)
{
  char message[1024];

  sprintf(message, "BAILOUT: caught signal %d", sig);
  panic(message);
  exit_nicely(136);
  return;
}

#ifdef CATCH_SIGSEGV
static signal_type do_sig_segv(int i)
{
  char *argv[] = {"../bin/netmaze", NULL};

  log_sensitive("SIGSEGV: Server crash. rebooting server in 10 seconds.");
  notify_all(tprintf("Server crash. %s rebooting in 10 seconds.",
    config.maze_name), NULL);

  log_important("MAZE rebooting.");
  notify_all("MAZE rebooting.", NULL);
  log_important(tprintf("Shutting down due to signal %d", i));
  flush_all_output();

  alarm(0);           /* Shut off timers */
  sleep(10);          /* Wait 10 seconds */

  close_sockets();
  do_haltall(root_obj);
  save_todomotd(0);
  save_todomotd(1);
  save_sc_funcs();
  free_sc_funcs();
  dump_database();

  free_everything();

  fcntl(sock, F_SETFD, 1);

  quiet_reboot = 1;
  num_crashes++;

  close_logs();
  save_uptime();

/* Do this to dump a core */
  if(!fork())
  {
    signal(SIGSEGV, SIG_DFL);
    kill(getpid(), SIGSEGV);
    exit(0);
  }

  wait(0);
  execv("../bin/netmaze", argv);
  unlink("logs/socket_table");
  _exit(exit_status);
}
#endif /* CATCH_SIGSEGV */

static signal_type do_sig_reboot(int i)
{
  log_sensitive("REBOOT: by external source");
  log_important("REBOOT: by external source");
  exit_status = 1;
  shutdown_flag = 1;
  return;
}

static signal_type do_sig_shutdown(int i)
{
  log_sensitive("SHUTDOWN: by external source");
  log_important("SHUTDOWN: by external source");
  exit_status = 0;
  shutdown_flag = 1;
  return;
}

static void do_notify_connect(OBJ *player, char flag, time_t idle)
{
  OBJ *o;
  char *p, *b;
  char buf[2048];
  int found = 0;
  char *str;
  DDATA *d;

  if(flag == 1)
  {
    log_important(tprintf("%s unidled after %s", name(player),
      format_time(idle)));
    str = "|+W|Unidle:";
  }
  else if(flag == 2)
  {
    for(d = descriptor_list;d;d = d->next)
      if(check_state(d, STATE_CONNECTED))
        if(d->player == player)
          found++;

/* Don't notify anyone if they're connected more than once */
    if(found > 1)
      return;

    str = "|+W|Disconnect:";
  }
  else
  {
    for(d = descriptor_list;d;d = d->next)
      if(check_state(d, STATE_CONNECTED))
        if(d->player == player)
          found++;

    if(found > 1)
      str = "|+W|Reconnect:";
    else
      str = "|+W|Connect:";
  }

  for(o = player_list;o;o = o->next)
  {
    if(o == player)
      continue;

    found = 0;

    p = atr_get(o, "NOTIFY");

    if(!string_compare(p, "all"))
    {
      if(could_doit(o, player, "LHIDE"))
        found = 1;
    }
    else
    {
      while(*p)
      {
        b = buf;

        while(*p && isspace(*p))
          p++;
        while(*p && !isspace(*p))
          *b++ = *p++;
        *b = '\0';
        if(!*buf)
          break;
        if(match_player(NULL, buf) == player)
        {
          if(could_doit(o, player, "LHIDE") &&
            !controls(player, o, POW_WHO))
            found = 1;
          break;
        }
      }
    }
    if(found)
    {
      if(idle)
        notify(o, tprintf("|+W|%s %s |+W|(%s)",
          str, name(player), format_time(idle)));
      else
        notify(o, tprintf("|+W|%s %s", str, name(player)));
    }
  }
}

void announce_connect(DDATA *d)
{
  OBJ *player = d->player;
  OBJ *loc = player->location;
  char *s, *t;
  time_t tt;
  int connect_again = 0;
  DDATA *des;

  plugin_login(d);

/* Tell everyone a new player has connected. */
  if(!atoi(atr_get(player, "CONNECTS")))
  {
    if(!Guest(player))
    {
      notify_all("|C+|Welcome a new lamb to the slaughter!", player);
      notify_all(tprintf("|W+|-- |B+|%s has entered the realm of %s.",
        name(player), config.maze_name), player);
    }
  }

  for(des = descriptor_list;des;des = des->next)
    if(des == d)
      connect_again++;

  if(!(player->flags & PLAYER_INVISIBLE))
  {
    if(connect_again > 1)
      notify_in(loc, player, tprintf("%s has reconnected.", name(player)));
    else
      notify_in(loc, player, tprintf("%s has connected.", name(player)));
  }

  if(!Guest(player))
  {
    tt = atol(atr_get(player, "LASTCONN"));
    if(tt == 0L)
      s = "no previous login";
    else
    {
      s = ctime(&tt);
      s[strlen(s)-1] = 0;
    }

    if(*atr_get(player, "LASTSITE"))
      t = tprintf(" |+W|from |+Y|%s", atr_get(player, "LASTSITE"));
    else
      t = "";
    notify(player, tprintf("|+B|Last login|+W|: |+C|%s%s", s, t));

    if(*atr_get(player, "BADLOGINS"))
    {
      t = atr_get(player, "BADLOGINS");

      notify(player,
        "\n|+R|There were failed login attempts on your character|+W|:");
      while((s = parse_up(&t, ' ')))
        notify(player, tprintf("|R|%s", s));
      notify(player, "");
      atr_clr(player, "BADLOGINS");
    }

    tt = now;
    atr_add(player, "LASTCONN", tprintf("%ld", tt));

/* Do this regardless of d->update_flag. If the hostname is still */
/* updating, this will temporarily fill it with their IP address and */
/* be later updated with their userid and hostname by */
/* chk_hostname_update() */
    atr_add(player, "LASTSITE", tprintf("%s@%s", d->user, d->addr));
  }

  if(!connect_again)
  {
    did_it(player, player, NULL, NULL, "PCONN");
    did_it(player, player->location, NULL, NULL, "PCONN");
  }
}

void announce_disconnect(DDATA *des)
{
  OBJ *player = des->player;
  OBJ *loc = player->location;
  int num;
  char buf[4096];
  DDATA *d;
  int partial_disconnect;

  plugin_logout(des);

  for(num = 0, d = descriptor_list;d;d = d->next)
    if(check_state(d, STATE_CONNECTED) && d->player && d->player == player)
      num++;
  if(num < 3 && !shutdown_flag)
    partial_disconnect = 0;
  else
    partial_disconnect = 1;
  atr_add(player, "LASTDISC", tprintf("%ld", now));

  do_notify_connect(player, 2, 0);

  if(is_pasting(des))
    remove_paste(des);

  if(partial_disconnect)
    sprintf(buf, "%s has partially disconnected.", name(player));
  else
    sprintf(buf, "%s has disconnected.", name(player));

  if(!(player->flags & PLAYER_INVISIBLE))
    notify_in(loc, player, buf);

  if(!partial_disconnect)
  {
    did_it(player, player, NULL, NULL, "PDISC");
    did_it(player, player->location, NULL, NULL, "PDISC");
  }
}

void info_io(OBJ *player)
{
  float throughput;
  char buf[4096];

  notify(player, "|+Y|I/O stats since last reboot:");

  if(now-since_reboot == 0)
    throughput = 0;
  else
    throughput = (float)total_input/(float)(now-since_reboot);

  notify(player,
    tprintf("|+B|Total Input|+W|: |+C|%s bytes |+W|(%s bytes/second)",
    comma(tprintf("%ld", total_input)),
    comma(tprintf("%.2f", throughput))));

  if(now-since_reboot == 0)
    throughput = 0;
  else
    throughput = (float)total_output/(float)(now-since_reboot);

  notify(player,
    tprintf("|+B|Total Output|+W|: |+C|%s bytes |+W|(%s bytes/second)",
    comma(tprintf("%ld", total_output)),
    comma(tprintf("%.2f", throughput))));

  if(config.max_server_io)
    sprintf(buf, "%s bytes/second",
      comma(tprintf("%d", config.max_server_io)));
  else
    strcpy(buf, "NO LIMIT");
  notify(player,
    tprintf("\n|+B|Maximum Server Throughput|+W|: |+C|%s", buf));

  if(config.max_player_io)
    sprintf(buf, "%s bytes/second",
      comma(tprintf("%d", config.max_player_io)));
  else
    strcpy(buf, "NO LIMIT");
  notify(player,
    tprintf("|+B|Maximum Player Throughput|+W|: |+C|%s", buf));
}

void chk_hostname_update()
{
  DDATA *d;
  char *newuser, *newaddr;
  int *newflag;

  for(d = descriptor_list;d;d = d->next)
  {
    if(*(d->update_flag) != 0)
    {
      if(*(d->update_flag) == 1)
      {
        if(check_state(d, STATE_CONNECTED))
          log_io(tprintf("Host for %s (descriptor %d): %s@%s",
            name(d->player), d->descriptor, d->user, d->addr));
        else
          log_io(tprintf("Host for descriptor %d: %s@%s", d->descriptor,
            d->user, d->addr));
      }

      *(d->update_flag) = 0;
      d->lookup_pid = 0;

/* Free the shared memory areas */
      SET(newuser, d->user);
      shmdt(d->user);
      d->user = newuser;
      SET(newaddr, d->addr);
      shmdt(d->addr);
      d->addr = newaddr;
      newflag = (int *)stack_alloc(sizeof(int), 1, 0);
      *newflag = *(d->update_flag);
      shmdt((char *)d->update_flag);
      d->update_flag = newflag;

      if(d->player && !Guest(d->player))
        atr_add(d->player, "LASTSITE", tprintf("%s@%s", d->user, d->addr));
    }
  }
}