/* command.c
 *
 */

/*
    fics - An internet chess server.
    Copyright (C) 1993  Richard V. Nash

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
*/

/* Revision history:
   name		email		yy/mm/dd	Change
   Richard Nash			93/10/22	Created
*/

#include "stdinclude.h"

#include "common.h"
#include "rmalloc.h"
#include "command.h"
#include "command_list.h"
#include "movecheck.h"
#include "ficsmain.h"
#include "config.h"
#include "utils.h"
#include "playerdb.h"
#include "gamedb.h"
#include "gameproc.h"
#include "obsproc.h"
#include "ratings.h"
#include "vers.h"
#include "network.h"
#include "shutdown.h"
#include <sys/param.h>
#ifdef TIMESEAL
#include "timeseal.h"
#endif
/*#include "hostinfo.h" */

PUBLIC char *mess_dir = DEFAULT_MESS;
PUBLIC char *index_dir = DEFAULT_INDEX;
PUBLIC char *help_dir[NUM_LANGS] = {DEFAULT_HELP, HELP_SPANISH, HELP_FRENCH, HELP_DANISH};
PUBLIC char *comhelp_dir = DEFAULT_COMHELP;
PUBLIC char *info_dir = DEFAULT_INFO;
PUBLIC char *adhelp_dir = DEFAULT_ADHELP;
PUBLIC char *uscf_dir = DEFAULT_USCF;
PUBLIC char *stats_dir = DEFAULT_STATS;
PUBLIC char *player_dir = DEFAULT_PLAYERS;
PUBLIC char *adj_dir = DEFAULT_ADJOURNED;
PUBLIC char *hist_dir = DEFAULT_HISTORY;
PUBLIC char *journal_dir = DEFAULT_JOURNAL;
PUBLIC char *board_dir = DEFAULT_BOARDS;
PUBLIC char *def_prompt = DEFAULT_PROMPT;
PUBLIC char *source_dir = DEFAULT_SOURCE;
PUBLIC char *lists_dir = DEFAULT_LISTS;
PUBLIC char *news_dir = DEFAULT_NEWS;
PUBLIC char *usage_dir[NUM_LANGS] = {DEFAULT_USAGE, USAGE_SPANISH, USAGE_FRENCH, USAGE_DANISH};

PUBLIC char *hadmin_handle = HADMINHANDLE;
char *hadmin_email = HADMINEMAIL;
char *reg_addr = REGMAIL;

PUBLIC int startuptime;
PUBLIC char fics_hostname[81];
PUBLIC int player_high;
PUBLIC int game_high;
PUBLIC int MailGameResult;

PUBLIC int commanding_player = -1;	/* The player whose command your in */

PRIVATE int lastCommandFound = -1;

/* grimm */
int player_notify_departure(int p);
/* added for warnings */

/* Copies command into comm, and returns pointer to parameters in
 * parameters
 */
PRIVATE int parse_command(char *com_string,
			   char **comm,
			   char **parameters)
{
  *comm = com_string;
  *parameters = eatword(com_string);
  if (**parameters != '\0') {
    **parameters = '\0';
    (*parameters)++;
    *parameters = eatwhite(*parameters);
  }
  if (strlen(*comm) >= MAX_COM_LENGTH) {
    return COM_BADCOMMAND;
  }
  return COM_OK;
}

PUBLIC int alias_lookup(char *tmp, alias_type *alias_list, int numalias)
/* numalias is the maximum number to search through */
{
  int i;

  for (i = 0; (alias_list[i].comm_name && i < numalias); i++) {
    if (!strcmp(tmp, alias_list[i].comm_name))
      return i;
  }
  return -1;			/* not found */
}

PUBLIC int alias_count(alias_type *alias_list)
{
  int i;

  for (i = 0; alias_list[i].comm_name; i++);
  return i;
}


/* Puts alias substitution into alias_string */
PRIVATE void alias_substitute(alias_type *alias_list, int num_alias,
			       char *com_str, char outalias[])
{
  char *s = com_str;
  char name[MAX_COM_LENGTH];
  char *t = name;
  int i = 0;
  char *atpos, *aliasval;

  /* Get first word of command, terminated by whitespace or by containing
     punctuation */
  while (*s && !iswhitespace(*s)) {
    if (i++ >= MAX_COM_LENGTH) {
      strcpy(outalias, com_str);
      return;
    }
    if (ispunct(*t++ = *s++))
      break;
  }
  *t = '\0';
  if (*s && iswhitespace(*s))
    s++;

  i = alias_lookup(name, alias_list, num_alias);
  if (i < 0) {
    strcpy(outalias, com_str);
    return;
  }
  aliasval = alias_list[i].alias;

  /* See if alias contains an @ */
  atpos = strchr(aliasval, '@');
  if (atpos != NULL) {
    strncpy(outalias, aliasval, atpos - aliasval);
    outalias[atpos - aliasval] = '\0';
    strcat(outalias, s);
    strcat(outalias, atpos + 1);
  } else {
    strcpy(outalias, aliasval);
    if (*s) {
      strcat(outalias, " ");
      strcat(outalias, s);
    }
  }
}

/* Returns pointer to command that matches */
PRIVATE int match_command(char *comm, int p)
{
  int i = 0;
  int gotIt = -1;
  int len = strlen(comm);

  while (command_list[i].comm_name) {
    if (!strncmp(command_list[i].comm_name, comm, len)
	&& parray[p].adminLevel >= command_list[i].adminLevel) {
      if (gotIt >= 0)
	return -COM_AMBIGUOUS;
      gotIt = i;
    }
    i++;
  }
  if (in_list(p, L_REMOVEDCOM, command_list[gotIt].comm_name)) {
    pprintf(p, "Due to a bug - this command has been temporarily removed.\n");
    return -COM_FAILED;
  }
  if (gotIt >= 0) {
    lastCommandFound = gotIt;
    return gotIt;
  }
  return -COM_FAILED;
}

/* Gets the parameters for this command */
PRIVATE int get_parameters(int command, char *parameters, param_list params)
{
  int i, parlen;
  int paramLower;
  char c;
  static char punc[2];

  punc[1] = '\0';		/* Holds punc parameters */
  for (i = 0; i < MAXNUMPARAMS; i++)
    (params)[i].type = TYPE_NULL;	/* Set all parameters to NULL */
  parlen = strlen(command_list[command].param_string);
  for (i = 0; i < parlen; i++) {
    c = command_list[command].param_string[i];
    if (isupper(c)) {
      paramLower = 0;
      c = tolower(c);
    } else {
      paramLower = 1;
    }
    switch (c) {
    case 'w':
    case 'o':			/* word or optional word */
      parameters = eatwhite(parameters);
      if (!*parameters)
	return (c == 'o' ? COM_OK : COM_BADPARAMETERS);
      (params)[i].val.word = parameters;
      (params)[i].type = TYPE_WORD;
      if (ispunct(*parameters)) {
	punc[0] = *parameters;
	(params)[i].val.word = punc;
	parameters++;
	if (*parameters && iswhitespace(*parameters))
	  parameters++;
      } else {
	parameters = eatword(parameters);
	if (*parameters != '\0') {
	  *parameters = '\0';
	  parameters++;
	}
      }
      if (paramLower)
	stolower((params)[i].val.word);
      break;

    case 'd':
    case 'p':			/* optional or required integer */
      parameters = eatwhite(parameters);
      if (!*parameters)
	return (c == 'p' ? COM_OK : COM_BADPARAMETERS);
      if (sscanf(parameters, "%d", &(params)[i].val.integer) != 1)
	return COM_BADPARAMETERS;
      (params)[i].type = TYPE_INT;
      parameters = eatword(parameters);
      if (*parameters != '\0') {
	*parameters = '\0';
	parameters++;
      }
      break;

    case 'i':
    case 'n':			/* optional or required word or integer */
      parameters = eatwhite(parameters);
      if (!*parameters)
	return (c == 'n' ? COM_OK : COM_BADPARAMETERS);
      if (sscanf(parameters, "%d", &(params)[i].val.integer) != 1) {
	(params)[i].val.word = parameters;
	(params)[i].type = TYPE_WORD;
      } else {
	(params)[i].type = TYPE_INT;
      }
      if (ispunct(*parameters)) {
	punc[0] = *parameters;
	(params)[i].val.word = punc;
	(params)[i].type = TYPE_WORD;
	parameters++;
	if (*parameters && iswhitespace(*parameters))
	  parameters++;
      } else {
	parameters = eatword(parameters);
	if (*parameters != '\0') {
	  *parameters = '\0';
	  parameters++;
	}
      }
      if ((params)[i].type == TYPE_WORD)
	if (paramLower)
	  stolower((params)[i].val.word);
      break;

    case 's':
    case 't':			/* optional or required string to end */
      if (!*parameters)
	return (c == 't' ? COM_OK : COM_BADPARAMETERS);
      (params)[i].val.string = parameters;
      (params)[i].type = TYPE_STRING;
      while (*parameters)
	parameters = nextword(parameters);
      if (paramLower)
	stolower((params)[i].val.string);
      break;
    }
  }
  if (*parameters)
    return COM_BADPARAMETERS;
  else
    return COM_OK;
}

PRIVATE void printusage(int p, char *command_str)
{
  int i, parlen, UseLang = parray[p].language;
  int command;
  char c;

  char *filenames[1000];        /* enough for all usage names */

  if ((command = match_command(command_str, p)) < 0) {
    pprintf(p, "  UNKNOWN COMMAND\n");
    return;
  }

/*Usage added by DAV 11/19/95 */
  /* First lets check if we have a text usage file for it */

  i = search_directory(usage_dir[UseLang], command_str, filenames, 1000);
  if (i == 0) { /* nope none in current Lang */
    if (UseLang != LANG_DEFAULT) {
      i += search_directory(usage_dir[LANG_DEFAULT], command_str, filenames, 1000);
      if (i > 0) {
        pprintf(p, "No usage available in %s; using %s instead.\n",
                Language(UseLang), Language(LANG_DEFAULT));
        UseLang = LANG_DEFAULT;
      }
    }
  }

 if (i != 0) {
  if ((i == 1) || (!strcmp(*filenames, command_str))) { /* found it? then send */
    if (psend_file(p, usage_dir[UseLang], *filenames)) {
      /* we should never reach this unless the file was just deleted */
      pprintf(p, "Usage file %s could not be found! ", *filenames);
      pprintf(p, "Please inform an admin of this. Thank you.\n");
      /* no need to print 'system' usage - should never happen */
    }
   return;
  } 
 } 

  /* print the default 'system' usage files (which aren't much help!) */

  pprintf(p, "Usage: %s", command_list[lastCommandFound].comm_name);

  parlen = strlen(command_list[command].param_string);
  for (i = 0; i < parlen; i++) {
    c = command_list[command].param_string[i];
    if (isupper(c))
      c = tolower(c);
    switch (c) {
    case 'w':			/* word */
      pprintf(p, " word");
      break;
    case 'o':			/* optional word */
      pprintf(p, " [word]");
      break;
    case 'd':			/* integer */
      pprintf(p, " integer");
      break;
    case 'p':			/* optional integer */
      pprintf(p, " [integer]");
      break;
    case 'i':			/* word or integer */
      pprintf(p, " {word, integer}");
      break;
    case 'n':			/* optional word or integer */
      pprintf(p, " [{word, integer}]");
      break;
    case 's':			/* string to end */
      pprintf(p, " string");
      break;
    case 't':			/* optional string to end */
      pprintf(p, " [string]");
      break;
    }
  }
  pprintf(p, "\nSee 'help %s' for a complete description.\n", command_list[lastCommandFound].comm_name);
}

PUBLIC int process_command(int p, char *com_string, char **cmd)
{
  int which_command, retval;
  param_list params;
  static char alias_string1[MAX_STRING_LENGTH * 4], alias_string2[MAX_STRING_LENGTH * 4];
  char *comm, *parameters;

#ifdef DEBUG
  if (strcasecmp(parray[p].name, parray[p].login)) {
    fprintf(stderr, "FICS: PROBLEM Name=%s, Login=%s\n", parray[p].name, parray[p].login);
  }
#endif
  if (!com_string)
    return COM_FAILED;
#ifdef DEBUG
  fprintf(stderr, "%s, %s, %d: >%s<\n", parray[p].name, parray[p].login, parray[p].socket, com_string);
#endif
  alias_substitute(parray[p].alias_list, parray[p].numAlias,
		   com_string, alias_string1);
  alias_substitute(g_alias_list, 999,
		   alias_string1, alias_string2);
#ifdef DEBUG
  if (strcmp(com_string, alias_string2) != 0)
    fprintf(stderr, "%s -alias-: >%s<\n", parray[p].name, alias_string2);
#endif
  if ((retval = parse_command(alias_string2, &comm, &parameters)))
    return retval;
  if (is_move(comm))
    return COM_ISMOVE;
  stolower(comm);		/* All commands are case-insensitive */
  *cmd = comm;
  if ((which_command = match_command(comm, p)) < 0)
    return -which_command;
  if (parray[p].adminLevel < command_list[which_command].adminLevel) {
    return COM_RIGHTS;
  }
  if ((retval = get_parameters(which_command, parameters, params)))
    return retval;
  return command_list[which_command].comm_func(p, params);
}

PRIVATE int process_login(int p, char *loginname)
{
  int problem = 1;

  loginname = eatwhite(loginname);

  if (!*loginname) {		/* do something in here? */
  } else {
    char *loginnameii = xstrdup(loginname);
    stolower(loginname);
    if (!alphastring(loginname)) {
      pprintf(p, "\nSorry, names can only consist of lower and upper case letters.  Try again.\n");
    } else if (strlen(loginname) < 3) {
      pprintf(p, "\nA name should be at least three characters long!  Try again.\n");
    } else if (strlen(loginname) > 17) {
      pprintf(p, "\nSorry, names may be at most 17 characters long.  Try again.\n");
    } else if (in_list(p, L_BAN, loginnameii)) {
      pprintf(p, "\nPlayer \"%s\" is banned.\n", loginname);
      rfree(loginnameii);
      return COM_LOGOUT;
    } else if ((!in_list(p, L_ADMIN, loginnameii)) && (player_count(0) >= max_connections - 10)) {
      psend_raw_file(p, mess_dir, MESS_FULL);
      rfree(loginnameii);
      return COM_LOGOUT;
    } else {
      problem = 0;
      if (player_read(p, loginname)) {
	strcpy(parray[p].name, loginnameii);
	if (in_list(p, L_FILTER, dotQuad(parray[p].thisHost))) {
	  pprintf(p, "\nDue to abusive behavior, nobody from your site may login.\n");
	  pprintf(p, "If you wish to use this server please email %s\n", reg_addr);
	  pprintf(p, "Include details of a nick-name to be called here, e-mail address and your real name.\n");
	  pprintf(p, "We will send a password to you. Thanks.\n");
	  rfree(loginnameii);
	  return COM_LOGOUT;
	}
	if ((player_count(0)) >= MAX(max_connections - 60, 200)) {
	  psend_raw_file(p, mess_dir, MESS_FULL_UNREG);
	  rfree(loginnameii);
	  return COM_LOGOUT;
	}
	pprintf_noformat(p, "\n\"%s\" is not a registered name.  You may use this name to play unrated games.\n(After logging in, do \"help register\" for more info on how to register.)\n\nPress return to enter the FICS as \"%s\":",
			 parray[p].name, parray[p].name);
      } else {
	pprintf_noformat(p, "\n\"%s\" is a registered name.  If it is yours, type the password.\nIf not, just hit return to try another name.\n\npassword: ", parray[p].name);
      }
      parray[p].status = PLAYER_PASSWORD;
      turn_echo_off(parray[p].socket);
      rfree(loginnameii);
      if (strcasecmp(loginname, parray[p].name)) {
	pprintf(p, "\nYou've got a bad name field in your playerfile -- please report this to an admin!\n");
	rfree(loginnameii);
	return COM_LOGOUT;
      }
      if ((parray[p].adminLevel != 0) && (!in_list(p, L_ADMIN, parray[p].name))) {
	pprintf(p, "\nYou've got a bad playerfile -- please report this to an admin!\n");
	pprintf(p, "Your handle is missing!");
	pprintf(p, "Please log on as an unreg until an admin can correct this.\n");
	rfree(loginnameii);
	return COM_LOGOUT;
      }
      if ((parray[p].registered) && (parray[p].fullName == NULL)) {
	pprintf(p, "\nYou've got a bad playerfile -- please report this to an admin!\n");
	pprintf(p, "Your FullName is missing!");
	pprintf(p, "Please log on as an unreg until an admin can correct this.\n");
	rfree(loginnameii);
	return COM_LOGOUT;
      }
      if ((parray[p].registered) && (parray[p].emailAddress == NULL)) {
	pprintf(p, "\nYou've got a bad playerfile -- please report this to an admin!\n");
	pprintf(p, "Your Email address is missing\n");
	pprintf(p, "Please log on as an unreg until an admin can correct this.\n");
	rfree(loginnameii);
	return COM_LOGOUT;
      }
    }
  }

  if (problem) {
    psend_raw_file(p, mess_dir, MESS_LOGIN);
    pprintf(p, "login: ");
  }
  return 0;
}

void boot_out(int p, int p1)
{
  int fd;
  pprintf(p, "\n **** %s is already logged in - kicking them out. ****\n", parray[p].name);
  pprintf(p1, "**** %s has arrived - you can't both be logged in. ****\n", parray[p].name);
  fd = parray[p1].socket;
  process_disconnection(fd);
  net_close_connection(fd);
}

PUBLIC void rscan_news(FILE *fp, int p, int lc) {

  char junk[MAX_LINE_SIZE], count[10];
  int crtime;
  char *junkp;
  
  fgets(junk, MAX_LINE_SIZE, fp);
  if (feof(fp))
    return;
  sscanf(junk, "%d %s", &crtime, count);
  
  if (crtime - lc < 0)
    return;
  else {
    rscan_news(fp, p, lc);
    junkp = junk;
    junkp = nextword(junkp);
    junkp = nextword(junkp);
    pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)), junkp);
  }
}    

PRIVATE void check_news(int p, int admin)
{
  FILE *fp;
  char filename[MAX_FILENAME_SIZE];
  char junk[MAX_LINE_SIZE];
  char *junkp;
  int crtime; 
  int lc = player_lastconnect(p);
  char count[10];

  if (admin) {

    sprintf(filename, "%s/newadminnews.index", news_dir);
    fp = fopen(filename, "r");
    if (!fp) {
      fprintf(stderr, "Can't find admin news index.\n");
      return;
    }
    
    if (num_anews == -1) {
      num_anews = count_lines(fp);
      fclose(fp);
      fp = fopen(filename, "r");
    }

    fgets(junk, MAX_LINE_SIZE, fp);
    sscanf(junk, "%d %s", &crtime, count);
    if ((crtime - lc) < 0) {
      pprintf(p, "There are no new admin news items since your last login.\n\n");
      fclose(fp);
      return;
    } else {
      pprintf(p, "Index of new admin news items:\n");
      rscan_news(fp, p, lc);
      junkp = junk;
      junkp = nextword(junkp);
      junkp = nextword(junkp);
      pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)), junkp);
      pprintf(p, "(\"anews %d\" will display the most recent admin news file)\n", num_anews);
    }
  
  } else {

    sprintf(filename, "%s/newnews.index", news_dir);
    fp = fopen(filename, "r");
    if (!fp) {
      fprintf(stderr, "Can't find news index.\n");
      return;
    }

    if (num_news == -1) {
      num_news = count_lines(fp);
      fclose(fp);
      fp = fopen(filename, "r");
    }

    fgets(junk, MAX_LINE_SIZE, fp);
    sscanf(junk, "%d %s", &crtime, count);
    if ((crtime - lc) < 0) {
      pprintf(p, "There are no new news items since your last login (%s).\n", strltime(&lc));
      fclose(fp);
      return;
    } else {
      pprintf(p, "Index of new news items:\n");
      rscan_news(fp, p, lc);
      junkp = junk;
      junkp = nextword(junkp);
      junkp = nextword(junkp);
      pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)), junkp);
      pprintf(p, "(\"news %d\" will display the most recent admin news file)\n", num_news);
    }
  }

  fclose(fp);
}  

PRIVATE int process_password(int p, char *password)
{
  int p1;
  char salt[3];
  int fd;
  unsigned int fromHost;
  int messnum;
/*
  struct hostent *hp;
*/
  int dummy; /* (to hold a return value) */ 

  turn_echo_on(parray[p].socket);

/*  if (password[0] == '\n') {
    parray[p].status = PLAYER_LOGIN;
    return 0;
  }*/

  if (parray[p].passwd && parray[p].registered) {
    salt[0] = parray[p].passwd[0];
    salt[1] = parray[p].passwd[1];
    salt[2] = '\0';
    if (strcmp(crypt(password, salt), parray[p].passwd)) {
      fd = parray[p].socket;
      fromHost = parray[p].thisHost;
      player_clear(p);
      parray[p].logon_time = parray[p].last_command_time = time(0);
      parray[p].status = PLAYER_LOGIN;
      parray[p].socket = fd;
      parray[p].thisHost = fromHost;
      if (*password)
	pprintf(p, "\n\n**** Invalid password! ****\n\n");
      return COM_LOGOUT;
    }
  }
  for (p1 = 0; p1 < p_num; p1++) {
    if (parray[p1].name != NULL) {
      if ((!strcasecmp(parray[p].name, parray[p1].name)) && (p != p1)) {
	if (parray[p].registered == 0) {
	  pprintf(p, "\n*** Sorry %s is already logged in ***\n", parray[p].name);
	  return COM_LOGOUT;
	}
	boot_out(p, p1);
      }
    }
  }
  if (parray[p].adminLevel > 0) {
    psend_raw_file(p, mess_dir, MESS_ADMOTD);
  } else {
    psend_raw_file(p, mess_dir, MESS_MOTD);
  }
  if (!parray[p].passwd && parray[p].registered)
    pprintf(p, "\n*** You have no password. Please set one with the password command.");
  if (!parray[p].registered)
    psend_raw_file(p, mess_dir, MESS_UNREGISTERED);
  parray[p].status = PLAYER_PROMPT;
  player_write_login(p);
  for (p1 = 0; p1 < p_num; p1++) {
    if (p1 == p)
      continue;
    if (parray[p1].status != PLAYER_PROMPT)
      continue;
#if 0				/* deleted by mann */
    if (parray[p1].thisHost == parray[p].thisHost) {
      fprintf(stderr, "FICS: Players %s and %s - same host: %s\n",
	      parray[p].name, parray[p1].name, dotQuad(parray[p].thisHost));
    }
#endif
    if (!parray[p1].i_login)
      continue;
    if (parray[p1].adminLevel > 0) {
/*
      hp = gethostbyaddr((const char*)&(parray[p].thisHost), sizeof(parray[p].thisHost), AF_INET);
*/
      pprintf_prompt(p1, "\n[%s (%s: %s) has connected.]\n", parray[p].name,
		     (parray[p].registered ? "R" : "U"),
		     dotQuad(parray[p].thisHost));
    } else {
      pprintf_prompt(p1, "\n[%s has connected.]\n", parray[p].name);
    }
  }
  parray[p].num_comments = player_num_comments(p);
  messnum = player_num_messages(p);

/* loon: don't send unreg any news.  when you change this, feel free to
   put all the news junk in one source file:)  no reason for identical code
   in command.c and comproc.c */
  if (parray[p].registered) {
    check_news(p, 0);
    if (parray[p].adminLevel > 0) {
      pprintf(p, "\n");
      check_news(p, 1);
    }
  }
  if (messnum) {
    pprintf(p, "\nYou have %d messages.\nUse \"messages\" to display them, or \"clearmessages\" to remove them.\n", messnum);
  }
  player_notify_present(p);
  player_notify(p, "arrived", "arrival");
  showstored(p);
  if (parray[p].registered && (parray[p].lastHost != 0) &&
      (parray[p].lastHost != parray[p].thisHost)) {
#if 0 /* removed by DAV - we don't need to know this rubbish */
    fprintf(stderr, "FICS: Player %s: Last login: %s ", parray[p].name,
	    dotQuad(parray[p].lastHost));
    fprintf(stderr, "This login: %s\n", dotQuad(parray[p].thisHost));
#endif
    pprintf(p, "\nPlayer %s: Last login: %s ", parray[p].name,
	    dotQuad(parray[p].lastHost));
    pprintf(p, "This login: %s", dotQuad(parray[p].thisHost));
  }
  parray[p].lastHost = parray[p].thisHost;
  if (parray[p].registered && !parray[p].timeOfReg)
    parray[p].timeOfReg = time(0);
  parray[p].logon_time = parray[p].last_command_time = time(0);
  dummy = check_and_print_shutdown(p); /*Tells the user if we are going to shutdown */
  pprintf(p, "\n%s", parray[p].prompt);
  return 0;
}

PRIVATE int process_prompt(int p, char *command)
{
  int retval;
  char *cmd = "";

  command = eattailwhite(eatwhite(command));
  if (!*command) {
    pprintf(p, "%s", parray[p].prompt);
    return COM_OK;
  }
  retval = process_command(p, command, &cmd);
  switch (retval) {
  case COM_OK:
    retval = COM_OK;
    pprintf(p, "%s", parray[p].prompt);
    break;
  case COM_OK_NOPROMPT:
    retval = COM_OK;
    break;
  case COM_ISMOVE:
    retval = COM_OK;

#ifdef TIMESEAL
    if (parray[p].game >= 0 && garray[parray[p].game].status == GAME_ACTIVE
        && parray[p].side == garray[parray[p].game].game_state.onMove
        && garray[parray[p].game].flag_pending != FLAG_NONE) {
      ExecuteFlagCmd(parray[p].game, con[parray[p].socket].time);
    }
#endif
    process_move(p, command);
    pprintf(p, "%s", parray[p].prompt);

    break;
  case COM_RIGHTS:
    pprintf(p, "%s: Insufficient rights.\n", cmd);
    pprintf(p, "%s", parray[p].prompt);
    retval = COM_OK;
    break;
  case COM_AMBIGUOUS:
/*    pprintf(p, "%s: Ambiguous command.\n", cmd); */
    {
      int len = strlen(cmd);
      int i = 0;
      pprintf(p, "Ambiguous command. Matches:");
      while (command_list[i].comm_name) {
	if ((!strncmp(command_list[i].comm_name, cmd, len)) &&
	    (parray[p].adminLevel >= command_list[i].adminLevel)) {
	  pprintf(p, " %s", command_list[i].comm_name);
	}
	i++;
      }
    }
    pprintf(p, "\n%s", parray[p].prompt);
    retval = COM_OK;
    break;
  case COM_BADPARAMETERS:
    printusage(p, command_list[lastCommandFound].comm_name);
    pprintf(p, "%s", parray[p].prompt);
    retval = COM_OK;
    break;
  case COM_FAILED:
  case COM_BADCOMMAND:
    pprintf(p, "%s: Command not found.\n", cmd);
    retval = COM_OK;
    pprintf(p, "%s", parray[p].prompt);
    break;
  case COM_LOGOUT:
    retval = COM_LOGOUT;
    break;
  }
  return retval;
}

/* Return 1 to disconnect */
PUBLIC int process_input(int fd, char *com_string)
{
  int p = player_find(fd);
  int retval = 0;

  if (p < 0) {
    fprintf(stderr, "FICS: Input from a player not in array!\n");
    return -1;
  }

  commanding_player = p;
  parray[p].last_command_time = time(0);

  switch (parray[p].status) {
  case PLAYER_EMPTY:
    fprintf(stderr, "FICS: Command from an empty player!\n");
    break;
  case PLAYER_NEW:
    fprintf(stderr, "FICS: Command from a new player!\n");
    break;
  case PLAYER_INQUEUE:
    /* Ignore input from player in queue */
    break;
  case PLAYER_LOGIN:
    retval = process_login(p, com_string);
    if (retval == COM_LOGOUT && com_string != NULL)
      fprintf (stderr, "%s tried to log in and failed.\n", com_string);
    break;
  case PLAYER_PASSWORD:
    retval = process_password(p, com_string);
    break;
  case PLAYER_PROMPT:
    parray[p].busy[0] = '\0';
    /* added this to stop buggy admin levels; shane */
    if (parray[p].adminLevel < 10)
      parray[p].adminLevel = 0;
    retval = process_prompt(p, com_string);
    break;
  }

  commanding_player = -1;
  return retval;
}

PUBLIC int process_new_connection(int fd, unsigned int fromHost)
{
  int p = player_new();

  parray[p].status = PLAYER_LOGIN;
  parray[p].socket = fd;
  parray[p].thisHost = fromHost;
  parray[p].logon_time = time(0);
  psend_raw_file(p, mess_dir, MESS_WELCOME);
  pprintf(p, "Head admin : %s   Complaints to : %s\n", hadmin_handle, hadmin_email);
  pprintf(p, "Server location: %s   Server version : %s\n", fics_hostname, VERS_NUM);
  psend_raw_file(p, mess_dir, MESS_LOGIN);
  pprintf(p, "login: ");
  return 0;
}

PUBLIC int process_disconnection(int fd)
{
  int p = player_find(fd);
  int p1;

  if (p < 0) {
    fprintf(stderr, "FICS: Disconnect from a player not in array!\n");
    return -1;
  }
  if ((parray[p].game >=0) &&(garray[parray[p].game].status == GAME_EXAMINE)) {
    pcommand(p, "unexamine");
  }
  if ((parray[p].game >=0) &&in_list(p, L_ABUSER, parray[p].name)) {
    pcommand(p, "resign");
  }
  if (parray[p].status == PLAYER_PROMPT) {
    for (p1 = 0; p1 < p_num; p1++) {
      if (p1 == p)
	continue;
      if (parray[p1].status != PLAYER_PROMPT)
	continue;
      if (!parray[p1].i_login)
	continue;
      pprintf_prompt(p1, "\n[%s has disconnected.]\n", parray[p].name);
    }
    player_notify(p, "departed", "departure");
    player_notify_departure(p);
    player_write_logout(p);
    if (parray[p].registered) {
      parray[p].totalTime += time(0) - parray[p].logon_time;
      player_save(p);
    } else {			/* delete unreg history file */
      char fname[MAX_FILENAME_SIZE];
      sprintf(fname, "%s/player_data/%c/%s.games", stats_dir, parray[p].login[0], parray[p].login);
      unlink(fname);
    }
  }
  player_remove(p);
  return 0;
}

/* Called every few seconds */
PUBLIC int process_heartbeat(int *fd)
{
  static int last_space = 0;
  static int last_comfile = 0;
/*  static int last_ratings = 0; */
  static int lastcalled = 0;
  int time_since_last;
  int p;
/*  static int rpid = 0; */
  int now = time(0);

/*  game_update_times(); */

  if (lastcalled == 0)
    time_since_last = 0;
  else
    time_since_last = now - lastcalled;
  lastcalled = now;

  /* Check for timed out connections */
  for (p = 0; p < p_num; p++) {
    if (((parray[p].status == PLAYER_LOGIN) ||
	 (parray[p].status == PLAYER_PASSWORD)) &&
	(player_idle(p) > MAX_LOGIN_IDLE)) {
      pprintf(p, "\n**** LOGIN TIMEOUT ****\n");
      *fd = parray[p].socket;
      return COM_LOGOUT;
    }
    if ((parray[p].status == PLAYER_PROMPT) &&
	(player_idle(p) > MAX_IDLE_TIME) &&
	(parray[p].adminLevel == 0) &&
	(!in_list(p, L_TD, parray[p].name))) {
      pprintf(p, "\n**** Auto-logout because you were idle "
	      "more than one hour. ****\n");
      *fd = parray[p].socket;
      return COM_LOGOUT;
    }
  }
/* loon: turning this fork off temporarily (28 Oct) for lag */
#if 0
  if (rpid) {			/* Rating calculating going on */
    int statusp;

    if (wait3(&statusp, WNOHANG, NULL) == rpid) {
      fprintf(stderr, "FICS: Reinitting statistics.\n");
      rating_init();		/* Child finished, get the results. */
      rpid = 0;
    }
  }
  /* Recalc ratings every 3 hours */
  /* This is done because the ratings stats and players can get out of sync
     if there is a system crash. */
  /* This is done as a child process and read results when complete */
  if (last_ratings == 0)
    last_ratings = (now - (5 * 60 * 60)) + 120;	/* Do one in 2 minutes */
  else {
    if (last_ratings + 6 * 60 * 60 < now) {
      last_ratings = now;
      rpid = fork();
      if (rpid < 0) {
	fprintf(stderr, "FICS: Couldn't fork\n");
      } else {
	if (rpid == 0) {	/* The child */
	  rating_recalc();
	  exit(0);
	  fprintf(stderr, "Recalc process should never get here!\n");
	  ASSERT(0);
	}
      }
    }
  }
#endif
  /* Check for the communication file from mail updates every 10 minutes */
  /* That is probably too often, but who cares */
  if (MailGameResult) {
    if (last_comfile == 0)
      last_comfile = now;
    else {
      if (last_comfile + 10 * 60 < now) {
	last_comfile = now;
	/* Check for com file */
/*	hostinfo_checkcomfile(); */
      }
    }
  }
  if (last_space == 0)
    last_space = now;
  else {
    if (last_space + 60 < now) {/* Check the disk space every minute */
      last_space = now;
      if (available_space() < 1000000) {
	server_shutdown(60, "    **** Disk space is dangerously low!!! ****\n");
      }
      /* Check for com file */
      /* hostinfo_checkcomfile(); */
    }
  }
  ShutHeartBeat();
  return COM_OK;
}

PUBLIC void commands_init()
{
  FILE *fp, *afp;
  char fname[MAX_FILENAME_SIZE];
  int i = 0;

  sprintf(fname, "%s/commands", comhelp_dir);
  fp = fopen(fname, "w");
  if (!fp) {
    fprintf(stderr, "FICS: Could not write commands help file.\n");
    return;
  }
  sprintf(fname, "%s/admin_commands", adhelp_dir);
  afp = fopen(fname, "w");
  if (!afp) {
    fprintf(stderr, "FICS: Could not write admin_commands help file.\n");
    fclose(fp);
    return;
  }
  while (command_list[i].comm_name) {
    if (command_list[i].adminLevel >= ADMIN_ADMIN) {
      fprintf(afp, "%s\n", command_list[i].comm_name);
    } else {
      fprintf(fp, "%s\n", command_list[i].comm_name);
    }
    i++;
  }
  fclose(fp);
  fclose(afp);
}

/* Need to save rated games */
PUBLIC void TerminateCleanup()
{
  int p1;
  int g;

  for (g = 0; g < g_num; g++) {
    if (garray[g].status != GAME_ACTIVE)
      continue;
    if (garray[g].rated) {
      game_ended(g, WHITE, END_ADJOURN);
    }
  }
  for (p1 = 0; p1 < p_num; p1++) {
    if (parray[p1].status == PLAYER_EMPTY)
      continue;
    pprintf(p1, "\n    **** Server shutting down immediately. ****\n\n");
    if (parray[p1].status != PLAYER_PROMPT) {
      close(parray[p1].socket);
    } else {
      pprintf(p1, "Logging you out.\n");
      psend_raw_file(p1, mess_dir, MESS_LOGOUT);
      player_write_logout(p1);
      if (parray[p1].registered)
	parray[p1].totalTime += time(0) - parray[p1].logon_time;
      player_save(p1);
    }
  }
}