aboutsummaryrefslogtreecommitdiffstats
path: root/FICS/command.c
diff options
context:
space:
mode:
Diffstat (limited to 'FICS/command.c')
-rw-r--r--FICS/command.c1382
1 files changed, 1382 insertions, 0 deletions
diff --git a/FICS/command.c b/FICS/command.c
new file mode 100644
index 0000000..df5d4c2
--- /dev/null
+++ b/FICS/command.c
@@ -0,0 +1,1382 @@
+/* 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().
+ Markus Uhlin 24/11/25 Null checks
+ Markus Uhlin 25/03/09 Fixed double free()
+ Markus Uhlin 25/03/11 Fixed memleak
+ Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t'
+ Markus Uhlin 25/07/28 Usage of 'int64_t'
+ Markus Uhlin 25/08/23 Fixed file created without
+ restricting permissions.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#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)
+{
+ if (numalias >= MAX_ALIASES)
+ return -1;
+ for (int i = 0;
+ (i < numalias && alias_list[i].comm_name != NULL);
+ 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, &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");
+ rfree(loginnameii);
+ loginnameii = NULL;
+ } else if (strlen(loginname) < 3) {
+ pprintf(p, "\nA name should be at least three "
+ "characters long! Try again.\n");
+ rfree(loginnameii);
+ loginnameii = NULL;
+ } else if (strlen(loginname) > 17) {
+ pprintf(p, "\nSorry, names may be at most 17 "
+ "characters long. Try again.\n");
+ rfree(loginnameii);
+ loginnameii = NULL;
+ } 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);
+ loginnameii = NULL; // XXX
+
+ 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");
+ 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");
+ 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");
+ 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");
+ 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, time_t lc)
+{
+ char count[10] = { '\0' };
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char *junkp = NULL;
+ const char *scan_fmt = "%" SCNd64 " " "%9s";
+ int64_t 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, scan_fmt, &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)
+{
+ FILE *fp = NULL;
+ char count[10] = { '\0' };
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char *junkp = NULL;
+ const char *v_scan_fmt = "%" SCNd64 " " "%9s";
+ int64_t 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, v_scan_fmt, &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, v_scan_fmt, &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;
+ } else if (com_string == NULL) {
+ fprintf(stderr, "FICS: Command string is NULL!\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);
+ time_t time_since_last;
+ static time_t last_comfile = 0;
+ static time_t last_space = 0;
+ static time_t 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 fd[2];
+ int i = 0;
+
+ fp = afp = NULL;
+ snprintf(fname, sizeof fname, "%s/commands", comhelp_dir);
+
+ if ((fd[0] = open(fname, g_open_flags[1], g_open_modes)) < 0) {
+ warn("%s: open: %s", __func__, fname);
+ return;
+ } else if ((fp = fdopen(fd[0], "w")) == NULL) {
+ warn("%s: could not write commands help file (%s)", __func__,
+ fname);
+ close(fd[0]);
+ return;
+ }
+
+ snprintf(fname, sizeof fname, "%s/admin_commands", adhelp_dir);
+
+ if ((fd[1] = open(fname, g_open_flags[1], g_open_modes)) < 0) {
+ warn("%s: open: %s", __func__, fname);
+ goto clean_up;
+ } else if ((afp = fdopen(fd[1], "w")) == NULL) {
+ warn("%s: could not write admin_commands help file (%s)",
+ __func__, fname);
+ close(fd[1]);
+ goto clean_up;
+ }
+
+ 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++;
+ }
+
+ clean_up:
+ if (fp)
+ fclose(fp);
+ if (afp)
+ 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);
+ }
+ }
+}