calisto-20000323/
calisto-20000323/lib/
calisto-20000323/lib/etc/
calisto-20000323/lib/players/
calisto-20000323/lib/text/
calisto-20000323/log/
/*
 Calisto (c) 1998-2000 Peter Howkins, Matthew Howkins, Simon Howkins

 $Id: main.c,v 1.32 2000/03/23 23:18:05 peter Exp $

 */
static char rcsid[] = "$Id: main.c,v 1.32 2000/03/23 23:18:05 peter Exp $";

#include "config.h"

/* Ansi Includes */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <ctype.h>
/* Unix Includes */
#include "netdb.h"
#include "unistd.h"
#include "netinet/in.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "sys/ioctl.h"
#include "sys/time.h"
#include "errno.h"
#ifdef HAVE_CRYPT_H
#include "crypt.h"
#endif
#ifdef HAVE_SYS_FILIO_H
#include "sys/filio.h"
#endif
/* Our Includes */
#include "dllist.h"
#include "globals.h"
#include "commands.h"
#include "help.h"
#include "inifile.h"
#include "library.h"
#include "log.h"
#include "playerdb.h"
#include "pool.h"
#include "privs.h"
#include "structs.h"
#include "strplus.h"

const unsigned version = 3; /* Don't prefix constant with 0 - that's octal */
listhead AllConns;
listhead AllPlayers;
pool *descriptor_pool = NULL;

bool sdown;
time_t starttime;

/* Globals from .ini file */
char talker_name[256] = "";
unsigned port;
char admin_name[256] = "";
bool use_net_lookups = FALSE;
unsigned idle_boot_time = 0;
unsigned max_connections = 20;

static fd_set readfds, creadfds;

bool player_exists(const char *n)
{
  char path[256], name[256];
  FILE *player;

  STRNCOPY(name, n, sizeof(name));
  strlower(name);
  STRNCOPY(path, "lib/players/", sizeof(path));
  STRNAPPEND(path, name, sizeof(path));
  player = fopen(path, "r+");
  if(player == FALSE) {
    return FALSE;
  } else {
    fclose(player);
    return TRUE;
  }
}

void do_motd(descriptor *des)
{
  bar2(des, "Message Of The Day", NULL, NULL);
  send_file_to_descriptor("lib/etc/motd", des);
  bar2(des, NULL, NULL, NULL);
  if (haspriv(&des->player, "s_base")) {
    bar2(des, "Super-user Message Of The Day", NULL, NULL);
    send_file_to_descriptor("lib/etc/sumotd", des);
    bar2(des, NULL, NULL, NULL);
  }
}

int init_mother(void)
{
  int mother_sfd;
  struct sockaddr_in sai;
  int opt = 1;

  /* Create mother description socket, and set socket options */
  mother_sfd = socket(PF_INET, SOCK_STREAM, 0);
  if (mother_sfd == EOF) {
    log(debug, "Could not open mother descriptor socket: %s", strerror(errno));
    exit(EXIT_FAILURE);
  }
  if (setsockopt(mother_sfd, SOL_SOCKET, SO_REUSEADDR,
                 (char *) &opt, sizeof(opt)) == EOF)
  {
    log(debug,"Could not set mother descriptor options1: %s", strerror(errno));
  }
  if (ioctl(mother_sfd, FIONBIO, &opt) == EOF) {
    log(debug, "Could not set mother descriptor options2: %s", strerror(errno));
    close(mother_sfd);
    exit(EXIT_FAILURE);
  }

  sai.sin_family      = AF_INET;
  sai.sin_port        = htons(port);
  sai.sin_addr.s_addr = htonl(INADDR_ANY);

  /* Bind mother description socket */
  if (bind(mother_sfd, (struct sockaddr *) &sai, sizeof(sai)) < 0) {
    
  log(debug, "Could not bind socket: %s", strerror(errno));
    close(mother_sfd);
    exit(EXIT_FAILURE);
  }

  /* Set socket to listen */
  if (listen(mother_sfd, 3) ) {
    log(debug, "Could not listen() on socket: %s", strerror(errno));
    close(mother_sfd);
    exit(EXIT_FAILURE);
  }
  return mother_sfd;
}

void descriptor_new(int sfd, struct sockaddr_in sin)
{
  struct hostent *host;
  int opt = 1;
  descriptor *pnew_des = pool_malloc(descriptor_pool);

  if (!pnew_des) {
    fprintf(stderr, "descriptor_new: Out of memory.");
    exit(sizeof(descriptor));
  }

  /* Set socket to non-blocking */
  if (ioctl(sfd, FIONBIO, &opt) == EOF) {
    log(debug, "Could not set %d socket options: %s", sfd, strerror(errno));
    close(sfd);
    pool_free(pnew_des);
    return;
  }

  /* perform hostname/number lookup (Socket faq Q4.9) */
  /* Nuts and part of EW-too use 4 as the size argument, but ew-too use's
     sin.sin_addr.s_addr instead of sin.sin_addr in another part */
  if (use_net_lookups) {
    if ((host = gethostbyaddr((char *) &sin.sin_addr, 
                              sizeof(sin.sin_addr),
                              AF_INET))              == NULL) {
      log(debug, "Error on incoming socket during gethostbyaddr(): %s", strerror(errno));
      STRNCOPY(pnew_des->hostname, "* Error *", sizeof(pnew_des->hostname));
    } else {
      if(strlen(host->h_name) > MAX_HOST_NAME) {
        log(debug, "Hostnamelen exceeded maximum (%d)", MAX_HOST_NAME);
        STRNCOPY(pnew_des->hostname, "* Error *", sizeof(pnew_des->hostname));
      } else {
        STRNCOPY(pnew_des->hostname, host->h_name, sizeof(pnew_des->hostname));
      }
    }
  } else {
    STRNCOPY(pnew_des->hostname, "* Not Enabled *", sizeof(pnew_des->hostname));
  }

  pnew_des->player.loggedin  = FALSE;
  pnew_des->safe             = TRUE;
  pnew_des->connecttime      = time(NULL);
  pnew_des->idletime         = pnew_des->connecttime;
  pnew_des->sfd              = sfd;
  pnew_des->inbuf_used       = 0;
  pnew_des->inbuf_used_lines = 0;

  pnew_des->termtype[0]      = '\0';
  pnew_des->term_height      = 24;  /* a couple of sensible defaults */
  pnew_des->term_width       = 80;

  LIST_LINK_NODE_AT_END(AllConns, &(pnew_des->descriptorlink) );
  /* Add sfd to fd sets */
  FD_SET(sfd, &readfds);

  log(usage, "New descriptor %d from %s", sfd, pnew_des->hostname);

  /* Put the terminal in echo on, line mode */
  echo_on(pnew_des);

  /* log(debug, "about to inquire telnet");*/
  inquire_termtype(pnew_des);
  inquire_windowsize(pnew_des);
  /* log(debug, "successfully inquired telnet"); */

  if (send_file_to_descriptor("lib/etc/login", pnew_des) != 0)
    send_to_descriptor(pnew_des, "Welcome to %s\n", talker_name);
  send_to_descriptor(pnew_des, "Calisto [Version %u.%02u, " __DATE__"]\n",
                     version / 100, version % 100);
  send_to_descriptor(pnew_des, "\nlogin: ");

  pnew_des->state = STATE_LOGIN;
}

void character_new(descriptor *des, const char *name)
{
  character *c = &des->player;

  /* Do concurrency check, in case new character of same name has been 
     created during the login process */
  if (player_exists(name)) {
    send_to_descriptor(des,
                       "Sorry, that name has *just* been taken\n"
                       "Please choose another\n");
    send_to_descriptor(des, "\nlogin: ");
    des->state = STATE_LOGIN;
    return;
  }

  /* OK to continue, setup the new character */
  STRNCOPY(c->name, name, sizeof(c->name));
  /* set privs and check for the admin character */
  if (STRIEQ(c->name, admin_name)) {
    setpriv(c, "ha");
    log(usage, "Player %s got full Admin Rights automatically", c->name);
  } else {
    setpriv(c, "resident");
  }
  c->loggedin  = TRUE;
  c->logintime = time(NULL);
  c->prevtime  = 0;
  STRNCOPY(c->group, "Public", sizeof(c->group));
  STRNCOPY(c->password, des->data.password, sizeof(c->password));
  LIST_LINK_NODE_AT_END(AllPlayers, &(c->characterlink));
  STRNCOPY(c->prompt, "Calisto> ", sizeof(c->prompt));
  save_player(c, c->name);
  des->state = STATE_PLAY;
  log(usage, "Making new character %s from %s on sfd %d", c->name, des->hostname, des->sfd);

  do_motd(des);

  /* Send messages to new character and other characters */
  send_to_all_except(c, "\n+++ ^r%s^n has logged in\n", c->name);
  send_to_char(c, "\n+++ You have logged in\n");
  send_to_char(c, "%s^n", c->prompt);
}

void new1(descriptor *des, const char *buffer)
{
  char temp[MAX_RAW_INPUT_BUFFER];
 
  STRNCOPY(temp, buffer, sizeof(temp));

  strlower(temp);
  if (temp[0] == 'y') {
    send_to_descriptor(des, "Please give a password for the new character: ");
    echo_off(des); 
    des->state = STATE_NEW2;
  } else if(temp[0] == 'n') {
    send_to_descriptor(des, "login: ");
    des->state = STATE_LOGIN;
  } else {
    send_to_descriptor(des,
                       "Do you wish to make a new character called %s? (y/n)\n",
                       des->data.name); 
  }
}

void new2(descriptor *des, const char *buffer)
{
  echo_on(des);
  if (strlen(buffer) > MAX_PASSWORD_LENGTH) {
    send_to_descriptor(des, "Password too long (Max %d)\n"
                       "Please re-enter a password: ", MAX_PASSWORD_LENGTH);
  } else if (strlen(buffer) < MIN_PASSWORD_LENGTH) {
    send_to_descriptor(des, "Password too short (Min %d)\n"
                       "Please re-enter a password: ", MIN_PASSWORD_LENGTH);
  } else {
#if HAVE_CRYPT
    STRNCOPY(des->data.password, crypt(buffer, CRYPT_SALT),
             sizeof(des->data.password));
#else
    STRNCOPY(des->data.password, buffer, sizeof(des->data.password));
#endif /* HAVE_CRYPT */
    send_to_descriptor(des, "Please re-enter your password: ");
    des->state = STATE_NEW3;
  }
  echo_off(des);
}

void new3(descriptor *des, const char *buffer)
{
  echo_on(des);
  if (strlen(buffer) > MAX_PASSWORD_LENGTH) {
    send_to_descriptor(des, "Password too long (Max %d)\nPlease re-enter both passwords: ", MAX_PASSWORD_LENGTH);
    echo_off(des);
    des->state = STATE_NEW2;
  } else if(strlen(buffer) < MIN_PASSWORD_LENGTH) {
    send_to_descriptor(des, "Password too short (Min %d)\nPlease re-enter both passwords: ", MIN_PASSWORD_LENGTH);
    echo_off(des);
    des->state = STATE_NEW2;
#ifdef HAVE_CRYPT
  } else if (!STREQ(crypt(buffer, CRYPT_SALT), des->data.password)) {
#else 
  } else if (!STREQ(buffer, des->data.password)) {
#endif /* HAVE_CRYPT */
    send_to_descriptor(des, "Passwords don't match\n"
                            "Please re-enter both passwords: ");
    echo_off(des);
    des->state = STATE_NEW2;
  } else {
    character_new(des, des->data.name);
  }
}

void login(descriptor *des, const char *buffer)
{
  char temp[MAX_RAW_INPUT_BUFFER];

  STRNCOPY(temp, buffer, sizeof(temp));

  /* handle incorect data, too long, too short and illegal chars */
  if (strlen(temp) < MIN_NAME_LENGTH || strlen(temp) > MAX_NAME_LENGTH
      || !strisalnum(temp)) {
    if (strlen(temp) < MIN_NAME_LENGTH) {
      send_to_descriptor(des, "Name too short (Min %d)\n", MIN_NAME_LENGTH);
    }
    if (strlen(temp) > MAX_NAME_LENGTH) {
      send_to_descriptor(des, "Name too long (Max %d)\n", MAX_NAME_LENGTH);
    }
    if(!strisalnum(temp)) {
      send_to_descriptor(des, "Name can only contain characters (a-z, A-Z, 0-9)\n");
    }
    send_to_descriptor(des, "login: ");
    des->state = STATE_LOGIN;
    return;
  }  

  /* change name to lower case and check to see if it exists in the player
     dir */
   if (player_exists(temp) == FALSE) {
     strlower(temp);
     temp[0] = toupper(temp[0]);
     STRNCOPY(des->data.name, temp, sizeof(des->data.name));
     send_to_descriptor(des,
                  "Do you wish to make a new character called %s? (y/n)\n",
                   des->data.name); 
     des->state = STATE_NEW1;
   } else {
     STRNCOPY(des->data.name, temp, sizeof(des->data.name));
     send_to_descriptor(des, "Please enter your password or press return to enter another name: \n");
     echo_off(des);
     des->state = STATE_PASSWORD;
  }
}

void password(descriptor *des, const char *buffer)
{
  character *c = &des->player;

  if (STREQ(buffer, "")) {
    echo_on(des);
    send_to_descriptor(des, "login: \n");
    des->state = STATE_LOGIN;
  } else {
    if (player_exists(des->data.name) == FALSE) {
      /* paranoia , should never reach this error, as the file should have been
         found in the previous function */
      log(debug, "This error should never occur, main.c, password()");
      log(debug, "Failed to open %s, when successfully opened previously", des->data.name);
    } else {
      load_player(c, des->data.name);
#ifdef HAVE_CRYPT
      if (STREQ(crypt(buffer, CRYPT_SALT), c->password)) {
#else
      if (STREQ(buffer, c->password)) {
#endif /* HAVE_CRYPT */
        character *current_session = character_from_name_exact(des->data.name);

        /* is char allready logged on ? */
        if (current_session) {
          descriptor *d = getdes(current_session);

          memcpy(&des->player, current_session, sizeof(character));
          d->state = STATE_CLOSING;
        } else {
          STRNCOPY(c->group, "Public", sizeof(c->group));
          c->loggedin = TRUE;
          c->logintime = time(NULL);
        }
        echo_on(des);
        des->state = STATE_PLAY;
        LIST_LINK_NODE_AT_END(AllPlayers, &(c->characterlink));
        log(usage, "%s has logged in from %s on sfd %d", c->name, des->hostname, des->sfd);

        do_motd(des);

        send_to_all_except(c, "\n+++ ^r%s^n has logged in\n", c->name);
        send_to_char(c, "\n+++ You have logged in\n");
        send_to_char(c, "%s^n", c->prompt);
      } else {
        echo_on(des);
        send_to_descriptor(des, "\nIncorrect Password\nlogin: ");
        des->state = STATE_LOGIN;
      }
    }
  }
}

void descriptor_close(descriptor *des)
{
  if(des->player.loggedin == TRUE) {
    character *c = &des->player;

    /* Test (save the player) */
    save_player(c, c->name);

    log(usage, "%s has logged out", c->name);
    LIST_UNLINK_NODE(&c->characterlink);
  }

  /* remove sfd from fd sets */
  FD_CLR(des->sfd, &readfds);

  close(des->sfd);
}

void close_all_connections(const char *message)
{
  listnode *node = AllConns.head.next;

  log(usage, "Shutdown: Closed all connections, saved all characters");
  log(usage, "Shutdown: \'%s\'", message);

  while (LIST_NODE_IS_REAL(node)) {
    descriptor *des = LIST_GET_DATA(node, descriptor *, descriptorlink);

    send_to_descriptor(des, "\n+++ %s\n", message);
    descriptor_close(des);
    LIST_UNLINK_NODE(node);
    pool_free(des);
    node = node->next;
  }
}

static void init_inifile(void)
{
  if (inifile_open("calisto.ini")) {
    /* we may be able to do without, and make up some values - for now exit */
    fprintf(stderr, "Could not open .ini file\n");
    exit(EXIT_FAILURE);
  }

  inifile_get_token("talker_name",     "%s", talker_name);
  if (inifile_get_token("port",            "%u", &port)) {
    fprintf(stderr, "Value for 'port' not found in .ini file\n");
    exit(EXIT_FAILURE);
  }
  if (inifile_get_token("admin_name",      "%s", admin_name)) {
    fprintf(stderr, "Value for 'admin_name' not found in .ini file\n");
    exit(EXIT_FAILURE);
  }
  inifile_get_token("use_net_lookups", "%d", &use_net_lookups);
  inifile_get_token("idle_boot_time",  "%u", &idle_boot_time);
  inifile_get_token("max_connections", "%u", &max_connections);

  inifile_close();
}

void init_stuff(void)
{
  /* process the .ini file for options */
  init_inifile();

  /* initialise logs, if the logs fail to open thats not actually that bad */
  usage = log_init("log/usage",     LOGDATE);
  debug = log_init("log/debug",     LOGDATE);
  bug   = log_init("lib/text/bug",  LOGDATE);
  idea  = log_init("lib/text/idea", LOGDATE);
  typo  = log_init("lib/text/typo", LOGDATE);

  log(usage, "%s (Calisto %u.%02u) booted on port %u",
      talker_name, version/100, version%100, port);

  /* Init the starttime */
  starttime = time(NULL);

  /* Initialise the help text */
  help_init("lib/helpfile");

  /* init fd set */
  FD_ZERO(&readfds);

  /* Initialise pool-based memory management */
  descriptor_pool = pool_create(sizeof(descriptor));
  if (!descriptor_pool) {
    fprintf(stderr, "Not enough memory creating descriptor_pool\n");
    log(usage, "Not enough memory creating descriptor_pool\n");
    exit(EXIT_FAILURE);
  }

  /* Initialise descriptor list */
  LIST_INIT(AllConns);
  LIST_INIT(AllPlayers);
}

RETSIGTYPE signal_term(int signal)
{
  /* The int received is the signal - in this case always SIGTERM */
  switch(signal) {
    case SIGTERM:
      close_all_connections("Received SIGTERM - shutting down");
      break;
    case SIGINT:
      close_all_connections("Received SIGINT - shutting down");
      break;
  }
  exit(0);
}

void init_signal(void)
{
  signal(SIGPIPE, SIG_IGN); /* handles problems, such as trying to write to
                               a disconnected socket */
  signal(SIGTERM, signal_term);
  signal(SIGINT,  signal_term);
}

void auto_idle_booting(void)
{
  time_t now = time(NULL);
  listnode *node = AllConns.head.next;

  if (idle_boot_time != 0) {
    while (LIST_NODE_IS_REAL(node)) {
      descriptor *des = LIST_GET_DATA(node, descriptor *, descriptorlink);
      if (difftime(now, des->idletime) > (idle_boot_time * 60)) {
        send_to_descriptor(des, "\n+++ You have been automatically logged off "
                           "as you have been idle for %u minute(s).\n",
                           idle_boot_time);
        if (des->player.loggedin == TRUE) {
          character *c = &des->player;
          send_to_all_except(c, "\n+++ ^r%s^n idles out of the program\n",
                             des->player.name); 
        }
        des->state = STATE_CLOSING;
        des->safe = FALSE;
        log(usage, "%s will be logged off for being idle for %u minutes",
            des->player.name, idle_boot_time);
      }
      node = node->next;
    }
  }
}

int main(void)
{
  int mother_sfd;
  struct timeval tv;
  socklen_t len = sizeof(struct sockaddr_in); /* for the accept call later */

  init_signal();
  init_stuff();
  mother_sfd = init_mother();

  sdown = FALSE;

  fprintf(stderr, "%s booted on port %d\n", talker_name, port);

  /* The Main Loop */
  while (!sdown) {
    int new_con_sfd;
    int retval;
    struct sockaddr_in sin;

    /* throw off the idle buggers */
    auto_idle_booting();

    /* See if there's a new connection ready */
    new_con_sfd = accept(mother_sfd, (struct sockaddr *) &sin, &len);
    if (new_con_sfd != -1)
      descriptor_new(new_con_sfd, sin);

    /* Use select() to see if there is any data to be read on any sfd, set
       time struct for select, this below should make it loop 5 times a 
       second (0.2 seconds) and copy the fd sets so they don't get corrupted */
    tv.tv_sec = 0; tv.tv_usec = 200;
    creadfds = readfds;
    retval = select(FD_SETSIZE, &creadfds, NULL, NULL, &tv);

    if(retval != -1) { /* Loop through connected descriptors to see if they have changed state */
      listnode *node = AllConns.head.next;

      while (LIST_NODE_IS_REAL(node)) {
        descriptor *des = LIST_GET_DATA(node, descriptor *, descriptorlink);
        int state = des->state;
        int read;

        /* check for incoming input */
        if (FD_ISSET(des->sfd, &creadfds)) {
          read = get_from_descriptor(des);
          if (read < 0) {
            log(usage, "Connection broken on socket %d read() function",
                des->sfd);
            des->state = STATE_CLOSING;
            des->safe = FALSE;
            /* if logged in inform other players of their departure */
            if(des->player.loggedin == TRUE) {
              character *c = &des->player;
              send_to_all_except(c, "\n+++ ^r%s^n disappears suddenly\n",
                                 c->name);
            }
          } else if (read >= 0) {
            int line;
            char *curline = &des->inbuf[0];
            des->idletime = time(NULL);
            
            /* begin loop */
            for (line=0 ; line<read ; line++) {
              switch(state) {
                case STATE_LOGIN:     login(des, curline);       break;
                case STATE_PASSWORD:  password(des, curline);    break;
                case STATE_NEW1:      new1(des, curline);        break;
                case STATE_NEW2:      new2(des, curline);        break;
                case STATE_NEW3:      new3(des, curline);        break;
                case STATE_PLAY:      command_do(des, curline);  break;
              }
              /* move onto next command */
              if(line != read-1) {
                curline += strlen(curline) + 1;
              }
            } /* end loop through comand line */
          } 
        } /* endif check for fd set inclusion */ 
        /* Ensure that sfd is checked next time */
        FD_SET(des->sfd, &readfds);
        /* dump all those who have been told to bob off, as they may have been
           removed from the fd sets */
        if (state == STATE_CLOSING) {
          descriptor_close(des);
          LIST_UNLINK_NODE(node);
          pool_free(des);
          break;
        } /* endif (state == STATE_CLOSING) */
        node = node->next;
      } /* end while(LIST_NODE_IS_REAL(node)) */  
    } else { /* end loop through connections to see if they have changed state */
      /* select has returned -1 */
      /* best to bail out, as I can't do much with the errors */
      sdown = TRUE; 
    }
  } /* end while(!sdown) */

  /* Program is shutting down */
  close_all_connections("System shutting down normally");

  close(mother_sfd);
  fprintf(stderr, "%s has shutdown normally\n", talker_name);

  return EXIT_SUCCESS;
}