/* 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 Markus Uhlin 23/12/17 Fixed compiler warnings Markus Uhlin 23/12/19 Usage of 'time_t' Markus Uhlin 23/12/23 Fixed crypt() Markus Uhlin 24/05/01 Refactored and reformatted all functions. Markus Uhlin 24/05/01 Replaced unbounded string handling functions. Markus Uhlin 24/11/17 process_prompt: handle 'COM_FAILED' differently Markus Uhlin 24/11/23 Improved: check_news() and rscan_news(). */ #include "stdinclude.h" #include "common.h" #include #include #include "command.h" #include "command_list.h" #include "config.h" #include "fics_getsalt.h" #include "ficsmain.h" #include "gamedb.h" #include "gameproc.h" #include "maxxes-utils.h" #include "movecheck.h" #include "network.h" #include "obsproc.h" #include "playerdb.h" #include "ratings.h" #include "rmalloc.h" #include "shutdown.h" #include "utils.h" #include "vers.h" PUBLIC char *adhelp_dir = DEFAULT_ADHELP; PUBLIC char *adj_dir = DEFAULT_ADJOURNED; PUBLIC char *board_dir = DEFAULT_BOARDS; PUBLIC char *comhelp_dir = DEFAULT_COMHELP; PUBLIC char *def_prompt = DEFAULT_PROMPT; PUBLIC char *help_dir[NUM_LANGS] = { DEFAULT_HELP, HELP_SPANISH, HELP_FRENCH, HELP_DANISH }; PUBLIC char *hist_dir = DEFAULT_HISTORY; PUBLIC char *index_dir = DEFAULT_INDEX; PUBLIC char *info_dir = DEFAULT_INFO; PUBLIC char *journal_dir = DEFAULT_JOURNAL; PUBLIC char *lists_dir = DEFAULT_LISTS; PUBLIC char *mess_dir = DEFAULT_MESS; PUBLIC char *news_dir = DEFAULT_NEWS; PUBLIC char *player_dir = DEFAULT_PLAYERS; PUBLIC char *source_dir = DEFAULT_SOURCE; PUBLIC char *stats_dir = DEFAULT_STATS; PUBLIC char *usage_dir[NUM_LANGS] = { DEFAULT_USAGE, USAGE_SPANISH, USAGE_FRENCH, USAGE_DANISH }; PUBLIC char *uscf_dir = DEFAULT_USCF; PUBLIC char *hadmin_handle = HADMINHANDLE; PRIVATE char *hadmin_email = HADMINEMAIL; PRIVATE char *reg_addr = REGMAIL; PUBLIC char fics_hostname[81]; PUBLIC int MailGameResult; PUBLIC int game_high; PUBLIC int player_high; PUBLIC time_t startuptime; /* * The player whose command you're in */ PUBLIC int commanding_player = -1; PRIVATE int lastCommandFound = -1; /* * Copies command into 'comm'. */ 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) { for (int i = 0; (alias_list[i].comm_name && i < numalias); i++) { if (!strcmp(tmp, alias_list[i].comm_name)) return i; } return -1; /* not found */ } #if 0 PRIVATE int alias_count(alias_type *alias_list) { int i; for (i = 0; alias_list[i].comm_name; i++) { /* null */; } return i; } #endif /* * Puts alias substitution into alias_string */ PRIVATE void alias_substitute(alias_type *alias_list, int num_alias, char *com_str, char outalias[], const size_t size) { char *atpos, *aliasval; char *s = com_str; char name[MAX_COM_LENGTH] = { '\0' }; char *t = name; int i = 0; /* * Get first word of command, terminated by whitespace or by * containing punctuation. */ while (*s && !iswhitespace(*s)) { if (i++ >= MAX_COM_LENGTH) { mstrlcpy(outalias, com_str, size); return; } if (ispunct(*t++ = *s++)) break; } *t = '\0'; if (*s && iswhitespace(*s)) s++; i = alias_lookup(name, alias_list, num_alias); if (i < 0) { mstrlcpy(outalias, com_str, size); return; } aliasval = alias_list[i].alias; // See if alias contains an @ atpos = strchr(aliasval, '@'); if (atpos != NULL) { const size_t diff = atpos - aliasval; if (diff >= size) { // XXX outalias[0] = '\0'; warnx("%s: diff out of bounds!", __func__); return; } strncpy(outalias, aliasval, diff); outalias[diff] = '\0'; mstrlcat(outalias, s, size); mstrlcat(outalias, atpos + 1, size); } else { mstrlcpy(outalias, aliasval, size); if (*s) { mstrlcat(outalias, " ", size); mstrlcat(outalias, s, size); } } } /* * Returns pointer to command that matches */ PRIVATE int match_command(char *comm, int p) { int gotIt = -1; int i = 0; 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 (gotIt >= 0 && 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) { char c; int i, parlen; int paramLower; 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; } /* switch */ } /* for */ if (*parameters) return COM_BADPARAMETERS; else return COM_OK; } PRIVATE void printusage(int p, char *command_str) { char *filenames[1000]; // enough for all usage names char c; int command; int i, parlen, UseLang = parray[p].language; 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, ARRAY_SIZE(filenames)); if (i == 0) { if (UseLang != LANG_DEFAULT) { i += search_directory(usage_dir[LANG_DEFAULT], command_str, filenames, ARRAY_SIZE(filenames)); 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? 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) { char *comm, *parameters; int which_command, retval; param_list params; static char alias_string1[MAX_STRING_LENGTH * 4] = { '\0' }; static char alias_string2[MAX_STRING_LENGTH * 4] = { '\0' }; #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, sizeof(alias_string1)); alias_substitute(g_alias_list, 999, alias_string1, alias_string2, sizeof(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, ¶meters))) 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)) { rfree(parray[p].name); parray[p].name = xstrdup(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.\n" "If 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; } PRIVATE 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 *junkp = NULL; char count[10] = { '\0' }; char junk[MAX_LINE_SIZE] = { '\0' }; long int lval = 0; time_t crtime = 0; if (fgets(junk, sizeof junk, fp) == NULL || feof(fp)) return; _Static_assert(ARRAY_SIZE(count) > 9, "Unexpected array size"); if (sscanf(junk, "%ld %9s", &lval, count) != 2) { warnx("%s: sscanf() error: too few items", __func__); return; } crtime = lval; 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) { #define SCAN_JUNK "%ld %9s" FILE *fp = NULL; char count[10] = { '\0' }; char filename[MAX_FILENAME_SIZE] = { '\0' }; char junk[MAX_LINE_SIZE] = { '\0' }; char *junkp = NULL; long int lval = 0; time_t crtime = 0; time_t lc = player_lastconnect(p); _Static_assert(ARRAY_SIZE(count) > 9, "Unexpected array size"); if (admin) { msnprintf(filename, sizeof filename, "%s/newadminnews.index", news_dir); if ((fp = fopen(filename, "r")) == NULL) { warn("%s: can't find admin news index (%s)", __func__, filename); return; } if (num_anews == -1) { num_anews = count_lines(fp); fclose(fp); if ((fp = fopen(filename, "r")) == NULL) { warn("%s: can't find admin news index (%s)", __func__, filename); return; } } if (fgets(junk, sizeof junk, fp) == NULL) { warnx("%s: fgets() error", __func__); fclose(fp); return; } else if (sscanf(junk, SCAN_JUNK, &lval, count) != 2) { warnx("%s: sscanf() error", __func__); fclose(fp); return; } crtime = lval; 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 { msnprintf(filename, sizeof filename, "%s/newnews.index", news_dir); if ((fp = fopen(filename, "r")) == NULL) { warn("%s: can't find news index (%s)", __func__, filename); return; } if (num_news == -1) { num_news = count_lines(fp); fclose(fp); if ((fp = fopen(filename, "r")) == NULL) { warn("%s: can't find news index (%s)", __func__, filename); return; } } if (fgets(junk, sizeof junk, fp) == NULL) { warnx("%s: fgets() error", __func__); fclose(fp); return; } else if (sscanf(junk, SCAN_JUNK, &lval, count) != 2) { warnx("%s: sscanf() error", __func__); fclose(fp); return; } crtime = lval; 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) { char salt[FICS_SALT_SIZE]; int dummy; // to hold a return value int fd; int messnum; int p1; unsigned int fromHost; turn_echo_on(parray[p].socket); if (parray[p].passwd && parray[p].registered) { strncpy(salt, &(parray[p].passwd[0]), sizeof salt - 1); salt[sizeof salt - 1] = '\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(NULL); 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 (!parray[p1].i_login) continue; if (parray[p1].adminLevel > 0) { 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); /* * 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) { 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(NULL); parray[p].logon_time = parray[p].last_command_time = time(NULL); dummy = check_and_print_shutdown(p); // Tells the user if we are // going to shutdown // XXX: unused (void) dummy; pprintf(p, "\n%s", parray[p].prompt); return 0; } PRIVATE int process_prompt(int p, char *command) { char *cmd = ""; int i, len; int retval; 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: i = 0; len = strlen(cmd); 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: pprintf(p, "%s: Command has failed.\n", cmd); retval = COM_OK; pprintf(p, "%s", parray[p].prompt); break; 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(NULL); 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(NULL); 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); 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 (int 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(NULL) - parray[p].logon_time); player_save(p); } else { // delete unreg history file char fname[MAX_FILENAME_SIZE] = { '\0' }; (void) snprintf(fname, sizeof 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) { time_t now = time(NULL); int time_since_last; static int last_comfile = 0; static int last_space = 0; static int lastcalled = 0; if (lastcalled == 0) time_since_last = 0; else time_since_last = now - lastcalled; lastcalled = now; // XXX: unused (void) time_since_last; /* * Check for timed out connections */ for (int 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; } } /* * 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; } } 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"); } } } ShutHeartBeat(); return COM_OK; } PUBLIC void commands_init(void) { FILE *fp, *afp; char fname[MAX_FILENAME_SIZE]; int i = 0; snprintf(fname, sizeof fname, "%s/commands", comhelp_dir); if ((fp = fopen(fname, "w")) == NULL) { warn("%s: could not write commands help file (%s)", __func__, fname); return; } snprintf(fname, sizeof fname, "%s/admin_commands", adhelp_dir); if ((afp = fopen(fname, "w")) == NULL) { warn("%s: could not write admin_commands help file (%s)", __func__, fname); 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(void) { for (int g = 0; g < g_num; g++) { if (garray[g].status != GAME_ACTIVE) continue; if (garray[g].rated) game_ended(g, WHITE, END_ADJOURN); } for (int 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(NULL) - parray[p1].logon_time); } player_save(p1); } } }