diff options
Diffstat (limited to 'FICS/comproc.c')
-rw-r--r-- | FICS/comproc.c | 2104 |
1 files changed, 2104 insertions, 0 deletions
diff --git a/FICS/comproc.c b/FICS/comproc.c new file mode 100644 index 0000000..b2504fd --- /dev/null +++ b/FICS/comproc.c @@ -0,0 +1,2104 @@ +/* comproc.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 + foxbat 95/03/11 Added filters in cmatch + Markus Uhlin 23/12/09 Fixed implicit ints + Markus Uhlin 23/12/17 Fixed compiler warnings + Markus Uhlin 23/12/17 Usage of 'time_t' + Markus Uhlin 23/12/17 Reformatted com_stats() + Markus Uhlin 23/12/19 Usage of 'time_t' + Markus Uhlin 23/12/23 Fixed crypt() + Markus Uhlin 24/01/03 Added usage of ARRAY_SIZE() and + reformatted functions. + Markus Uhlin 24/01/06 Fixed potentially insecure + format strings. + Markus Uhlin 24/03/23 Size-bounded string handling + plus truncation checks. + Markus Uhlin 24/11/19 Improved com_news(). + plogins: fscanf: + added width specification + Markus Uhlin 25/03/08 Calc string length once + Markus Uhlin 25/03/11 Fixed possibly uninitialized + value 'rat' in who_terse(). + Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t'. + Markus Uhlin 25/03/16 Fixed untrusted array index. + Markus Uhlin 25/03/25 com_unalias: fixed overflowed + array index read/write. + Markus Uhlin 25/07/21 com_who: fixed multiplication + result converted to larger type. + Markus Uhlin 25/07/24 Fixed use of potentially + dangerous functions. + Markus Uhlin 25/07/29 Usage of 'int64_t'. +*/ + +#include "stdinclude.h" +#include "common.h" + +#include <sys/resource.h> + +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <stdint.h> + +#include "board.h" +#include "command.h" +#include "comproc.h" +#include "config.h" +#include "eco.h" +#include "fics_getsalt.h" +#include "ficsmain.h" +#include "formula.h" +#include "gamedb.h" +#include "gameproc.h" +#include "lists.h" +#include "multicol.h" +#include "network.h" +#include "obsproc.h" +#include "playerdb.h" +#include "ratings.h" +#include "rmalloc.h" +#include "talkproc.h" +#include "utils.h" +#include "variable.h" + +#if __linux__ +#include <bsd/string.h> +#endif + +#define WHO_OPEN 0x01 +#define WHO_CLOSED 0x02 +#define WHO_RATED 0x04 +#define WHO_UNRATED 0x08 +#define WHO_FREE 0x10 +#define WHO_PLAYING 0x20 +#define WHO_REGISTERED 0x40 +#define WHO_UNREGISTERED 0x80 +#define WHO_BUGTEAM 0x100 + +#define WHO_ALL 0xff + +typedef int ((*who_cmp_t)(const void *, const void *)); + +PUBLIC const int none = 0; +PUBLIC const int blitz_rat = 1; +PUBLIC const int std_rat = 2; +PUBLIC const int wild_rat = 3; +PUBLIC const int light_rat = 4; + +PUBLIC int num_news = -1; + +PRIVATE char *inout_string[] = { "login", "logout" }; + +PUBLIC int +com_rating_recalc(int p, param_list param) +{ + (void) p; // maybe not referenced + (void) param; // not referenced + + ASSERT(parray[p].adminLevel >= ADMIN_ADMIN); + rating_recalc(); + return COM_OK; +} + +PUBLIC int +com_more(int p, param_list param) +{ + // not referenced + (void) param; + + pmore_file(p); + return COM_OK; +} + +PUBLIC void +rscan_news2(FILE *fp, int p, int num) +{ + char count[10] = { '\0' }; + char junk[MAX_LINE_SIZE] = { '\0' }; + char *junkp; + const char *v_scan_fmt = "%" SCNd64 " " "%9s"; + int64_t lval; + time_t crtime; + + if (num == 0) + return; + + if (fgets(junk, sizeof junk, fp) == NULL || feof(fp) || + sscanf(junk, v_scan_fmt, &lval, count) != 2) + return; + + rscan_news2(fp, p, num - 1); + + junkp = junk; + junkp = nextword(junkp); + junkp = nextword(junkp); + + crtime = lval; + pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)), junkp); +} + +PUBLIC int +com_news(int p, param_list param) +{ + 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"; + int found = 0; + int64_t lval = 0; + time_t crtime = 0; + + snprintf(filename, sizeof filename, "%s/newnews.index", news_dir); + + if ((fp = fopen(filename, "r")) == NULL) { + fprintf(stderr, "Can\'t find news index.\n"); + return COM_OK; + } + + _Static_assert(9 < ARRAY_SIZE(count), "'count' too small"); + + if (param[0].type == 0) { + /* + * No params - then just display index of last ten + * news items. + */ + + pprintf(p, "Index of recent news items:\n"); + + if (fgets(junk, sizeof junk, fp) == NULL || + sscanf(junk, v_scan_fmt, &lval, count) != 2) { + warnx("%s: error: fgets() or sscanf()", __func__); + fclose(fp); + return COM_FAILED; + } + + rscan_news2(fp, p, 9); + + junkp = junk; + junkp = nextword(junkp); + junkp = nextword(junkp); + + crtime = lval; + pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)), + junkp); + fclose(fp); + } else if (param[0].type == TYPE_WORD && + !strcmp(param[0].val.word, "all")) { + /* + * Param all - displays index all news items. + */ + + pprintf(p, "Index of all news items:\n"); + + if (fgets(junk, sizeof junk, fp) == NULL || + sscanf(junk, v_scan_fmt, &lval, count) != 2) { + warnx("%s: error: fgets() or sscanf()", __func__); + fclose(fp); + return COM_FAILED; + } + + rscan_news(fp, p, 0); + + junkp = junk; + junkp = nextword(junkp); + junkp = nextword(junkp); + + crtime = lval; + pprintf(p, "%3s (%s) %s", count, fix_time(strltime(&crtime)), + junkp); + fclose(fp); + } else { + /* + * Check if the specific news file exist in index. + */ + + while (!feof(fp) && !found) { + junkp = junk; + + if (fgets(junk, sizeof junk, fp) == NULL || feof(fp)) + break; + if (sscanf(junkp, v_scan_fmt, &lval, count) != 2) + warnx("%s: sscanf() error...", __func__); + crtime = lval; + + if (!strcmp(count, param[0].val.word)) { + found = 1; + + junkp = nextword(junkp); + junkp = nextword(junkp); + + pprintf(p, "NEWS %3s (%s) %s\n", count, + fix_time(strltime(&crtime)), junkp); + } + } + + fclose(fp); + + if (!found) { + pprintf(p, "Bad index number!\n"); + return COM_OK; + } + + /* + * File exists - show it + */ + + snprintf(filename, sizeof filename, "%s/news.%s", news_dir, + param[0].val.word); + + if ((fp = fopen(filename, "r")) == NULL) { + pprintf(p, "No more info.\n"); + return COM_OK; + } + + fclose(fp); + snprintf(filename, sizeof filename, "news.%s", + param[0].val.word); + + if (psend_file(p, news_dir, filename) < 0) { + pprintf(p, "Internal error - couldn't send news file!" + "\n"); + } + } + + return COM_OK; +} + +PUBLIC int +com_quit(int p, param_list param) +{ + // not referenced + (void) param; + + if (parray[p].game >= 0 && garray[parray[p].game].status == + GAME_EXAMINE) + pcommand(p, "unexamine"); + if (parray[p].game >= 0) { + pprintf(p, "You can't quit while you are playing a game.\n" + "Type 'resign' to resign the game, or you can request an " + "abort with 'abort'.\n"); + return COM_OK; + } + + psend_logoutfile(p, mess_dir, MESS_LOGOUT); + return COM_LOGOUT; +} + +PUBLIC int +com_set(int p, param_list param) +{ + char *val; + int result; + int which; + + if (param[1].type == TYPE_NULL) + val = NULL; + else + val = param[1].val.string; + + result = var_set(p, param[0].val.word, val, &which); + + switch (result) { + case VAR_OK: + break; + case VAR_BADVAL: + pprintf(p, "Bad value given for variable %s.\n", + param[0].val.word); + break; + case VAR_NOSUCH: + pprintf(p, "No such variable name %s.\n", param[0].val.word); + break; + case VAR_AMBIGUOUS: + pprintf(p, "Ambiguous variable name %s.\n", param[0].val.word); + break; + } + + return COM_OK; +} + +PUBLIC int +FindPlayer(int p, char *name, int *p1, int *connected) +{ + *p1 = player_search(p, name); + + if (*p1 == 0) + return 0; + if (*p1 < 0) { // The player had to be connected and will be removed + // later. + *connected = 0; + *p1 = (-*p1) - 1; + } else { + *connected = 1; + *p1 = *p1 - 1; + } + + return 1; +} + +PRIVATE void +com_stats_andify(int *numbers, int howmany, char *dest, size_t dsize) +{ + char tmp[10] = { '\0' }; + + *dest = '\0'; + + while (howmany--) { + snprintf(tmp, sizeof tmp, "%d", numbers[howmany]); + strlcat(dest, tmp, dsize); + + if (howmany > 1) + strlcpy(tmp, ", ", sizeof tmp); + else if (howmany == 1) + strlcpy(tmp, " and ", sizeof tmp); + else + strlcpy(tmp, ".\n", sizeof tmp); + + if (strlcat(dest, tmp, dsize) >= dsize) { + (void) fprintf(stderr, "FICS: %s: warning: strlcat() " + "truncated\n", __func__); + } + } +} + +PRIVATE void +com_stats_rating(char *hdr, statistics *stats, char *dest, const size_t dsize) +{ + char tmp[100] = { '\0' }; + + snprintf(dest, dsize, "%-10s%4s %5.1f %4d %4d %4d %4d", hdr, + ratstr(stats->rating), + stats->sterr, + stats->win, + stats->los, + stats->dra, + stats->num); + + if (stats->whenbest) { + struct tm res = {0}; + + snprintf(tmp, sizeof tmp, " %d", stats->best); + strlcat(dest, tmp, dsize); + + errno = 0; + + if (localtime_r(&stats->whenbest, &res) != NULL) { + if (strftime(tmp, sizeof tmp, " (%d-%b-%y)", &res) != 0) + strlcat(dest, tmp, dsize); + } else + warn("%s: localtime_r", __func__); + } + + if (strlcat(dest, "\n", dsize) >= dsize) { + (void) fprintf(stderr, "FICS: %s (line %d): warning: strlcat() " + "truncated\n", __func__, __LINE__); + } +} + +PUBLIC int +com_stats(int p, param_list param) +{ +#define NUMBERS_SIZE \ + (MAX_OBSERVE > MAX_SIMUL ? MAX_OBSERVE : MAX_SIMUL) + char line[255] = { '\0' }; + char tmp[255] = { '\0' }; + int g, i, t; + int numbers[NUMBERS_SIZE]; + int onTime; + int p1, connected; + + if (param[0].type == TYPE_WORD) { + if (!FindPlayer(p, param[0].val.word, &p1, &connected)) + return COM_OK; + } else { + p1 = p; + connected = 1; + } + + snprintf(line, sizeof line, "\nStatistics for %-11s ", parray[p1].name); + + if (connected && parray[p1].status == PLAYER_PROMPT) { + snprintf(tmp, sizeof tmp, "On for: %s", + hms_desc(player_ontime(p1))); + strlcat(line, tmp, sizeof line); + snprintf(tmp, sizeof tmp, " Idle: %s\n", + hms_desc(player_idle(p1))); + } else { + time_t last; + + if ((last = player_lastdisconnect(p1)) != 0) { + snprintf(tmp, sizeof tmp, "(Last disconnected %s):\n", + strltime(&last)); + } else + strlcpy(tmp, "(Never connected.)\n", sizeof tmp); + } + + strlcat(line, tmp, sizeof line); + pprintf(p, "%s", line); + + if (parray[p1].simul_info.numBoards) { + t = 0; + i = 0; + + while (i < parray[p1].simul_info.numBoards) { + if ((numbers[t] = parray[p1].simul_info.boards[i] + 1) != 0) + t++; + i++; + } + pprintf(p, "%s is giving a simul: game%s ", parray[p1].name, + (t > 1 ? "s" : "")); + com_stats_andify(numbers, t, tmp, sizeof tmp); + pprintf(p, "%s", tmp); + } else if (parray[p1].game >= 0) { + g = parray[p1].game; + + if (garray[g].status == GAME_EXAMINE) { + pprintf(p, "(Examining game %d: %s vs. %s)\n", (g + 1), + garray[g].white_name, + garray[g].black_name); + } else { + pprintf(p, "(playing game %d: %s vs. %s)\n", (g + 1), + parray[garray[g].white].name, + parray[garray[g].black].name); + + if (garray[g].link >= 0) { + pprintf(p, "(partner is playing game %d: " + "%s vs. %s)\n", + (garray[g].link + 1), + parray[garray[garray[g].link].white].name, + parray[garray[garray[g].link].black].name); + } + } + } + + if (parray[p1].num_observe) { + t = 0; + i = 0; + + while (i < parray[p1].num_observe) { + if ((g = parray[p1].observe_list[i]) != -1 && + (parray[p].adminLevel >= ADMIN_ADMIN || + garray[g].private == 0)) + numbers[t++] = (g + 1); + i++; + } + + if (t) { + pprintf(p, "%s is observing game%s ", parray[p1].name, + (t > 1 ? "s" : "")); + com_stats_andify(numbers, t, tmp, sizeof tmp); + pprintf(p, "%s", tmp); + } + } + + if (parray[p1].busy[0]) + pprintf(p, "(%s %s)\n", parray[p1].name, parray[p1].busy); + + if (!parray[p1].registered) { + pprintf(p, "%s is NOT a registered player.\n\n", + parray[p1].name); + } else { + pprintf(p, "\n rating RD win loss draw " + "total best\n"); + + com_stats_rating("Blitz", &parray[p1].b_stats, tmp, sizeof tmp); + pprintf(p, "%s", tmp); + com_stats_rating("Standard", &parray[p1].s_stats, tmp, + sizeof tmp); + pprintf(p, "%s", tmp); + com_stats_rating("Lightning", &parray[p1].l_stats, tmp, + sizeof tmp); + pprintf(p, "%s", tmp); + com_stats_rating("Wild", &parray[p1].w_stats, tmp, sizeof tmp); + pprintf(p, "%s", tmp); + } + + pprintf(p, "\n"); + + if (parray[p1].adminLevel > 0) { + pprintf(p, "Admin Level: "); + + switch (parray[p1].adminLevel) { + case 5: + pprintf(p, "Authorized Helper Person\n"); + break; + case 10: + pprintf(p, "Administrator\n"); + break; + case 15: + pprintf(p, "Help File Librarian/Administrator\n"); + break; + case 20: + pprintf(p, "Master Administrator\n"); + break; + case 50: + pprintf(p, "Master Help File Librarian/Administrator" + "\n"); + break; + case 60: + pprintf(p, "Assistant Super User\n"); + break; + case 100: + pprintf(p, "Super User\n"); + break; + default: + pprintf(p, "%d\n", parray[p1].adminLevel); + break; + } + } + + // Full Name + if (parray[p].adminLevel > 0) { + pprintf(p, "Full Name : %s\n", (parray[p1].fullName ? + parray[p1].fullName : "(none)")); + } + + // Address + if ((p1 == p && parray[p1].registered) || parray[p].adminLevel > 0) { + pprintf(p, "Address : %s\n", (parray[p1].emailAddress ? + parray[p1].emailAddress : "(none)")); + } + + // Host + if (parray[p].adminLevel > 0) { + pprintf(p, "Host : %s\n", dotQuad(connected ? + parray[p1].thisHost : parray[p1].lastHost)); + } + + // Comments + if (parray[p].adminLevel > 0 && parray[p1].registered) { + if (parray[p1].num_comments) { + pprintf(p, "Comments : %d\n", + parray[p1].num_comments); + } + } + + if (connected && + parray[p1].registered && + (p == p1 || parray[p].adminLevel > 0)) { + char timeToStr[30] = { '\0' }; + + errno = 0; + + if (ctime_r(&parray[p1].timeOfReg, timeToStr) == NULL) + warn("%s: ctime_r", __func__); + timeToStr[strcspn(timeToStr, "\n")] = '\0'; + + pprintf(p, "\n"); + + onTime = ((time(NULL) - parray[p1].logon_time) + + parray[p1].totalTime); + + pprintf(p, "Total time on-line: %s\n", hms_desc(onTime)); + pprintf(p, "%% of life on-line: %3.1f (since %s)\n", // XXX + (double) ((onTime * 100) / (double) (time(NULL) - + parray[p1].timeOfReg)), + timeToStr); + } + +#ifdef TIMESEAL + if (connected) { + pprintf(p, "\nTimeseal : %s\n", + (con[parray[p1].socket].timeseal ? "On" : "Off")); + } + if (parray[p].adminLevel > 0 && connected) { + if (findConnection(parray[p1].socket) && + con[parray[p1].socket].timeseal) { + pprintf(p, "Unix acc : %s\nSystem/OS : %s\n", + con[parray[p1].socket].user, + con[parray[p1].socket].sys); + } + } +#endif + + if (parray[p1].num_plan) { + pprintf(p, "\n"); + + for (i = 0; i < parray[p1].num_plan; i++) { + pprintf(p, "%2d: %s\n", (i + 1), + (parray[p1].planLines[i] != NULL + ? parray[p1].planLines[i] + : "")); + } + } + + if (!connected) + player_remove(p1); + return COM_OK; +} + +PUBLIC int +com_password(int p, param_list param) +{ + char *oldpassword = param[0].val.word; + char *newpassword = param[1].val.word; + char salt[FICS_SALT_SIZE]; + + if (!parray[p].registered) { + pprintf(p, "Setting a password is only for registered players." + "\n"); + return COM_OK; + } + + if (parray[p].passwd) { + strncpy(salt, &(parray[p].passwd[0]), sizeof salt - 1); + salt[sizeof salt - 1] = '\0'; + + if (strcmp(crypt(oldpassword, salt), parray[p].passwd)) { + pprintf(p, "Incorrect password, password not changed!" + "\n"); + return COM_OK; + } + + rfree(parray[p].passwd); + parray[p].passwd = NULL; + } + + strlcpy(salt, fics_getsalt(), sizeof salt); + parray[p].passwd = xstrdup(crypt(newpassword, salt)); + + pprintf(p, "Password changed to \"%s\".\n", newpassword); + return COM_OK; +} + +PUBLIC int +com_uptime(int p, param_list param) +{ + int days, hours, mins, secs; + struct rusage ru; + unsigned long int uptime = (time(NULL) - startuptime); + + (void) param; // XXX: unused + + days = (uptime / (60*60*24)); + hours = ((uptime % (60*60*24)) / (60*60)); + mins = (((uptime % (60*60*24)) % (60*60)) / 60); + secs = (((uptime % (60*60*24)) % (60*60)) % 60); + + pprintf(p, "Server location: %s Server version : %s\n", fics_hostname, + VERS_NUM); + pprintf(p, "The server has been up since %s.\n", + strltime(&startuptime)); + + if (days == 0 && hours == 0 && mins == 0) { + pprintf(p, "(Up for %d second%s)\n", + secs, (secs == 1 ? "" : "s")); + } else if (days == 0 && hours == 0) { + pprintf(p, "(Up for %d minute%s and %d second%s)\n", + mins, (mins == 1 ? "" : "s"), + secs, (secs == 1 ? "" : "s")); + } else if (days == 0) { + pprintf(p, "(Up for %d hour%s, %d minute%s and %d second%s)\n", + hours, (hours == 1 ? "" : "s"), + mins, (mins == 1 ? "" : "s"), + secs, (secs == 1 ? "" : "s")); + } else { + pprintf(p, "(Up for %d day%s, %d hour%s, %d minute%s and %d " + "second%s)\n", + days, (days == 1 ? "" : "s"), + hours, (hours == 1 ? "" : "s"), + mins, (mins == 1 ? "" : "s"), + secs, (secs == 1 ? "" : "s")); + } + + pprintf(p, "\nAllocs: %u Frees: %u Allocs In Use: %u\n", + malloc_count, free_count, (malloc_count - free_count)); + if (parray[p].adminLevel >= ADMIN_ADMIN) { + pprintf(p, "\nplayer size:%zu, game size:%zu, con size:%d, " + "g_num:%d\n", sizeof(player), sizeof(game), net_consize(), + g_num); + + getrusage(RUSAGE_SELF, &ru); + + pprintf(p, "pagesize = %d, maxrss = %ld, total = %ld\n", + getpagesize(), + ru.ru_maxrss, + (getpagesize() * ru.ru_maxrss)); + } + + pprintf(p, "\nPlayer limit: %d\n", (max_connections - 10)); + pprintf(p, "\nThere are currently %d players, with a high of %d " + "since last restart.\n", player_count(1), player_high); + pprintf(p, "There are currently %d games, with a high of %d " + "since last restart.\n", game_count(), game_high); + pprintf(p, "\nCompiled on %s\n", COMP_DATE); + + return COM_OK; +} + +PUBLIC int +com_date(int p, param_list param) +{ + time_t t = time(NULL); + + (void) param; // XXX: unused + + pprintf(p, "Local time - %s\n", strltime(&t)); + pprintf(p, "Greenwich time - %s\n", strgtime(&t)); + + return COM_OK; +} + +PRIVATE int +plogins(int p, char *fname) +{ + FILE *fp = NULL; + char ipstr[20] = { '\0' }; + char loginName[MAX_LOGIN_NAME + 1] = { '\0' }; + const char *v_scan_fmt = "%" SCNu16 " %19s " "%" SCNd64 " " + "%d %19s\n"; + int registered = 0; + int64_t lval = 0; + time_t tval = 0; + uint16_t inout = 0; + + if ((fp = fopen(fname, "r")) == NULL) { + pprintf(p, "Sorry, no login information available.\n"); + return COM_OK; + } + + _Static_assert(19 < ARRAY_SIZE(ipstr), "'ipstr' too small"); + _Static_assert(19 < ARRAY_SIZE(loginName), "'loginName' too small"); + + while (!feof(fp)) { + if (fscanf(fp, v_scan_fmt, &inout, loginName, &lval, + ®istered, ipstr) != 5) { + fprintf(stderr, "FICS: Error in login info format. " + "%s\n", fname); + fclose(fp); + return COM_OK; + } + + tval = lval; + + if (inout >= ARRAY_SIZE(inout_string)) { + warnx("%s: %s: 'inout' too large (%u)", __func__, fname, + inout); + } else { + pprintf(p, "%s: %-17s %-6s", strltime(&tval), loginName, + inout_string[inout]); + } + + if (parray[p].adminLevel > 0) + pprintf(p, " from %s\n", ipstr); + else + pprintf(p, "\n"); + } + + fclose(fp); + return COM_OK; +} + +PUBLIC int +com_llogons(int p, param_list param) +{ + char fname[MAX_FILENAME_SIZE] = { '\0' }; + + (void) param; // XXX: unused + + snprintf(fname, sizeof fname, "%s/%s", stats_dir, STATS_LOGONS); + return plogins(p, fname); +} + +PUBLIC int +com_logons(int p, param_list param) +{ + char fname[MAX_FILENAME_SIZE] = { '\0' }; + + if (param[0].type == TYPE_WORD) { + snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", + stats_dir, param[0].val.word[0], param[0].val.word, + STATS_LOGONS); + } else { + snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", + stats_dir, parray[p].login[0], parray[p].login, + STATS_LOGONS); + } + return plogins(p, fname); +} + +PRIVATE void +AddPlayerLists(int p1, char *ptmp, const size_t size) +{ + if (parray[p1].adminLevel >= 10 && parray[p1].i_admin) + strlcat(ptmp, "(*)", size); + if (in_list(p1, L_COMPUTER, parray[p1].name)) + strlcat(ptmp, "(C)", size); + if (in_list(p1, L_FM, parray[p1].name)) + strlcat(ptmp, "(FM)", size); + if (in_list(p1, L_IM, parray[p1].name)) + strlcat(ptmp, "(IM)", size); + if (in_list(p1, L_GM, parray[p1].name)) + strlcat(ptmp, "(GM)", size); + if (in_list(p1, L_TD, parray[p1].name)) + strlcat(ptmp, "(TD)", size); + if (in_list(p1, L_TEAMS, parray[p1].name)) + strlcat(ptmp, "(T)", size); + if (in_list(p1, L_BLIND, parray[p1].name)) + strlcat(ptmp, "(B)", size); +} + +PRIVATE void +who_terse(int p, int num, int *plist, int type) +{ + char ptmp[80 + 20] = { '\0' }; // for highlight + int i; + int p1; + int rat; + multicol *m = multicol_start(PARRAY_SIZE); + + for (i = 0; i < num; i++) { + p1 = plist[i]; + + if (type == blitz_rat) + rat = parray[p1].b_stats.rating; + else if (type == wild_rat) + rat = parray[p1].w_stats.rating; + else if (type == std_rat) + rat = parray[p1].s_stats.rating; + else if (type == light_rat) + rat = parray[p1].l_stats.rating; + else // Fallback to std... + rat = parray[p1].s_stats.rating; + + if (type == none) { + strlcpy(ptmp, " ", sizeof ptmp); + } else { + snprintf(ptmp, sizeof ptmp, "%-4s", ratstrii(rat, + parray[p1].registered)); + + if (parray[p1].simul_info.numBoards) { + strlcat(ptmp, "~", sizeof ptmp); + } else if (parray[p1].game >= 0 && + garray[parray[p1].game].status == GAME_EXAMINE) { + strlcat(ptmp, "#", sizeof ptmp); + } else if (parray[p1].game >= 0) { + strlcat(ptmp, "^", sizeof ptmp); + } else if (!parray[p1].open) { + strlcat(ptmp, ":", sizeof ptmp); + } else if (player_idle(p1) > 300) { + strlcat(ptmp, ".", sizeof ptmp); + } else { + strlcat(ptmp, " ", sizeof ptmp); + } + } + + if (p == p1) { + const size_t len = strlen(ptmp); + + psprintf_highlight(p, ptmp + len, + sizeof ptmp - len, "%s", parray[p1].name); + } else { + strlcat(ptmp, parray[p1].name, sizeof ptmp); + } + + AddPlayerLists(p1, ptmp, sizeof ptmp); + multicol_store(m, ptmp); + } + + multicol_pprint(m, p, 80, 2); + multicol_end(m); + + pprintf(p, "\n %d players displayed (of %d). (*) indicates system " + "administrator.\n", num, player_count(1)); +} + +PRIVATE void +who_verbose(int p, int num, int plist[]) +{ + char p1WithAttrs[255] = { '\0' }; + char playerLine[255] = { '\0' }; + char tmp[255] = { '\0' }; + int p1; + int ret, too_long; + + pprintf(p, " +---------------------------------------------------------------+\n"); + pprintf(p, " | User Standard Blitz On for Idle |\n"); + pprintf(p, " +---------------------------------------------------------------+\n"); + + for (int i = 0; i < num; i++) { + p1 = plist[i]; + strlcpy(playerLine, " |", sizeof playerLine); + + if (parray[p1].game >= 0) + snprintf(tmp, sizeof tmp, "%3d", parray[p1].game + 1); + else + strlcpy(tmp, " ", sizeof tmp); + + strlcat(playerLine, tmp, sizeof playerLine); + + if (!parray[p1].open) + strlcpy(tmp, "X", sizeof tmp); + else + strlcpy(tmp, " ", sizeof tmp); + + strlcat(playerLine, tmp, sizeof playerLine); + + if (parray[p1].registered) { + if (parray[p1].rated) + strlcpy(tmp, " ", sizeof tmp); + else + strlcpy(tmp, "u", sizeof tmp); + } else { + strlcpy(tmp, "U", sizeof tmp); + } + + strlcat(playerLine, tmp, sizeof playerLine); + strlcpy(p1WithAttrs, parray[p1].name, sizeof p1WithAttrs); + AddPlayerLists(p1, p1WithAttrs, sizeof p1WithAttrs); + p1WithAttrs[17] = '\0'; + + if (p == p1) { + size_t len; + + strlcpy(tmp, " ", sizeof tmp); + len = strlen(tmp); + psprintf_highlight(p, tmp + len, sizeof tmp - len, + "%-17s", p1WithAttrs); + } else { + ret = snprintf(tmp, sizeof tmp, " %-17s", p1WithAttrs); + + too_long = (ret < 0 || (size_t)ret >= sizeof tmp); + + if (too_long) { + fprintf(stderr, "FICS: %s: warning: " + "snprintf truncated\n", __func__); + } + } + + strlcat(playerLine, tmp, sizeof playerLine); + snprintf(tmp, sizeof tmp, " %4s %-4s %5s ", + ratstrii(parray[p1].s_stats.rating, + parray[p1].registered), + ratstrii(parray[p1].b_stats.rating, + parray[p1].registered), + hms(player_ontime(p1), 0, 0, 0)); + strlcat(playerLine, tmp, sizeof playerLine); + + if (player_idle(p1) >= 60) { + snprintf(tmp, sizeof tmp, "%5s |\n", + hms(player_idle(p1), 0, 0, 0)); + } else { + strlcpy(tmp, " |\n", sizeof tmp); + } + + strlcat(playerLine, tmp, sizeof playerLine); + pprintf(p, "%s", playerLine); + } + + pprintf(p, " | |\n"); + pprintf(p, " | %3d Players Displayed |\n", num); + pprintf(p, " +---------------------------------------------------------------+\n"); +} + +PRIVATE void +who_winloss(int p, int num, int plist[]) +{ + char p1WithAttrs[255] = { '\0' }; + char playerLine[255] = { '\0' }; + char tmp[255] = { '\0' }; + int p1; + + pprintf(p, "Name Stand win loss draw Blitz win loss draw idle\n"); + pprintf(p, "---------------- ----- ------------- ----- ------------- ----\n"); + + for (int i = 0; i < num; i++) { + playerLine[0] = '\0'; + p1 = plist[i]; + + /* Modified by hersco to include lists in 'who n.' */ + strlcpy(p1WithAttrs, parray[p1].name, sizeof p1WithAttrs); + AddPlayerLists(p1, p1WithAttrs, sizeof p1WithAttrs); + p1WithAttrs[17] = '\0'; + + if (p1 == p) { + psprintf_highlight(p, playerLine, sizeof playerLine, + "%-17s", p1WithAttrs); + } else { + snprintf(playerLine, sizeof playerLine, "%-17s", + p1WithAttrs); + } + + snprintf(tmp, sizeof tmp, " %4s %4d %4d %4d ", + ratstrii(parray[p1].s_stats.rating, parray[p1].registered), + parray[p1].s_stats.win, + parray[p1].s_stats.los, + parray[p1].s_stats.dra); + strlcat(playerLine, tmp, sizeof playerLine); + + snprintf(tmp, sizeof tmp, "%4s %4d %4d %4d ", + ratstrii(parray[p1].b_stats.rating, parray[p1].registered), + parray[p1].b_stats.win, + parray[p1].b_stats.los, + parray[p1].b_stats.dra); + strlcat(playerLine, tmp, sizeof playerLine); + + if (player_idle(p1) >= 60) { + snprintf(tmp, sizeof tmp, "%5s\n", hms(player_idle(p1), + 0, 0, 0)); + } else { + strlcpy(tmp, "\n", sizeof tmp); + } + + strlcat(playerLine, tmp, sizeof playerLine); + pprintf(p, "%s", playerLine); + } + + pprintf(p, " %3d Players Displayed.\n", num); +} + +PRIVATE int +who_ok(int p, unsigned int sel_bits) +{ + int p2; + + if (parray[p].status != PLAYER_PROMPT) + return 0; + if (sel_bits == WHO_ALL) + return 1; + + if (sel_bits & WHO_OPEN) { + if (!parray[p].open) + return 0; + } + if (sel_bits & WHO_CLOSED) { + if (parray[p].open) + return 0; + } + if (sel_bits & WHO_RATED) { + if (!parray[p].rated) + return 0; + } + if (sel_bits & WHO_UNRATED) { + if (parray[p].rated) + return 0; + } + if (sel_bits & WHO_FREE) { + if (parray[p].game >= 0) + return 0; + } + if (sel_bits & WHO_PLAYING) { + if (parray[p].game < 0) + return 0; + } + if (sel_bits & WHO_REGISTERED) { + if (!parray[p].registered) + return 0; + } + if (sel_bits & WHO_UNREGISTERED) { + if (parray[p].registered) + return 0; + } + if (sel_bits & WHO_BUGTEAM) { + if ((p2 = parray[p].partner) < 0 || parray[p2].partner != p) + return 0; + } + + return 1; +} + +PRIVATE int +blitz_cmp(const void *pp1, const void *pp2) +{ + register int p1 = *(int *) pp1; + register int p2 = *(int *) pp2; + + if (parray[p1].status != PLAYER_PROMPT) { + if (parray[p2].status != PLAYER_PROMPT) + return 0; + else + return -1; + } + if (parray[p2].status != PLAYER_PROMPT) + return 1; + if (parray[p1].b_stats.rating > parray[p2].b_stats.rating) + return -1; + if (parray[p1].b_stats.rating < parray[p2].b_stats.rating) + return 1; + if (parray[p1].registered > parray[p2].registered) + return -1; + if (parray[p1].registered < parray[p2].registered) + return 1; + return strcmp(parray[p1].login, parray[p2].login); +} + +PRIVATE int +light_cmp(const void *pp1, const void *pp2) +{ + register int p1 = *(int *) pp1; + register int p2 = *(int *) pp2; + + if (parray[p1].status != PLAYER_PROMPT) { + if (parray[p2].status != PLAYER_PROMPT) + return 0; + else + return -1; + } + if (parray[p2].status != PLAYER_PROMPT) + return 1; + if (parray[p1].l_stats.rating > parray[p2].l_stats.rating) + return -1; + if (parray[p1].l_stats.rating < parray[p2].l_stats.rating) + return 1; + if (parray[p1].registered > parray[p2].registered) + return -1; + if (parray[p1].registered < parray[p2].registered) + return 1; + return strcmp(parray[p1].login, parray[p2].login); +} + +PRIVATE int +stand_cmp(const void *pp1, const void *pp2) +{ + register int p1 = *(int *) pp1; + register int p2 = *(int *) pp2; + + if (parray[p1].status != PLAYER_PROMPT) { + if (parray[p2].status != PLAYER_PROMPT) + return 0; + else + return -1; + } + if (parray[p2].status != PLAYER_PROMPT) + return 1; + if (parray[p1].s_stats.rating > parray[p2].s_stats.rating) + return -1; + if (parray[p1].s_stats.rating < parray[p2].s_stats.rating) + return 1; + if (parray[p1].registered > parray[p2].registered) + return -1; + if (parray[p1].registered < parray[p2].registered) + return 1; + return strcmp(parray[p1].login, parray[p2].login); +} + +PRIVATE int +wild_cmp(const void *pp1, const void *pp2) +{ + register int p1 = *(int *) pp1; + register int p2 = *(int *) pp2; + + if (parray[p1].status != PLAYER_PROMPT) { + if (parray[p2].status != PLAYER_PROMPT) + return 0; + else + return -1; + } + if (parray[p2].status != PLAYER_PROMPT) + return 1; + if (parray[p1].w_stats.rating > parray[p2].w_stats.rating) + return -1; + if (parray[p1].w_stats.rating < parray[p2].w_stats.rating) + return 1; + if (parray[p1].registered > parray[p2].registered) + return -1; + if (parray[p1].registered < parray[p2].registered) + return 1; + return strcmp(parray[p1].login, parray[p2].login); +} + +PRIVATE int +alpha_cmp(const void *pp1, const void *pp2) +{ + register int p1 = *(int *) pp1; + register int p2 = *(int *) pp2; + + if (parray[p1].status != PLAYER_PROMPT) { + if (parray[p2].status != PLAYER_PROMPT) + return 0; + else + return -1; + } + if (parray[p2].status != PLAYER_PROMPT) + return 1; + return strcmp(parray[p1].login, parray[p2].login); +} + +PRIVATE void +sort_players(int players[PARRAY_SIZE], who_cmp_t cmp_func) +{ + if (p_num <= 0) { + warnx("%s: p_num <= 0", __func__); + return; + } + for (int i = 0; i < p_num; i++) + players[i] = i; + qsort(players, p_num, sizeof(int), cmp_func); +} + +/* + * This is the of the most compliclicated commands in terms of + * parameters. + */ +PUBLIC int +com_who(int p, param_list param) +{ + char c; + float start_perc = 0; + float stop_perc = 1.0; + int i, len; + int p1, count, num_who; + int plist[PARRAY_SIZE]; + int sort_type = blitz_rat; + int sortlist[PARRAY_SIZE]; + int startpoint; + int stoppoint; + int style = 0; + int tmpI, tmpJ; + unsigned int sel_bits = WHO_ALL; + who_cmp_t cmp_func = blitz_cmp; + + if (param[0].type != TYPE_NULL) { + len = strlen(param[0].val.string); + + for (i = 0; i < len; i++) { + c = param[0].val.string[i]; + + if (isdigit(c)) { + if (i == 0 || !isdigit(param[0].val.string[i - 1])) { + tmpI = (c - '0'); + + if (tmpI == 1) { + start_perc = 0.0; + stop_perc = 0.333333; + } else if (tmpI == 2) { + start_perc = 0.333333; + stop_perc = 0.6666667; + } else if (tmpI == 3) { + start_perc = 0.6666667; + stop_perc = 1.0; + } else if (i == (len - 1) || + !isdigit(param[0].val.string[i + 1])) + return COM_BADPARAMETERS; + } else { + tmpI = c - '0'; + tmpJ = (param[0].val.string[i - 1] - + '0'); + + if (tmpI == 0) + return COM_BADPARAMETERS; + if (tmpJ > tmpI) + return COM_BADPARAMETERS; + + start_perc = ((float) tmpJ - 1.0) / + (float) tmpI; + stop_perc = ((float) tmpJ) / + (float) tmpI; + } + } else { + switch (c) { + case ' ': + case '\n': + case '\t': + break; + case 'o': + if (sel_bits == WHO_ALL) + sel_bits = WHO_OPEN; + else + sel_bits |= WHO_OPEN; + break; + case 'r': + if (sel_bits == WHO_ALL) + sel_bits = WHO_RATED; + else + sel_bits |= WHO_RATED; + break; + case 'f': + if (sel_bits == WHO_ALL) + sel_bits = WHO_FREE; + else + sel_bits |= WHO_FREE; + break; + case 'a': + if (sel_bits == WHO_ALL) + sel_bits = WHO_FREE | WHO_OPEN; + else + sel_bits |= (WHO_FREE | WHO_OPEN); + break; + case 'R': + if (sel_bits == WHO_ALL) + sel_bits = WHO_REGISTERED; + else + sel_bits |= WHO_REGISTERED; + break; + case 'l': // Sort order + case 'A': + cmp_func = alpha_cmp; + sort_type = none; + break; + case 'w': // Sort order + cmp_func = wild_cmp; + sort_type = wild_rat; + break; + case 's': // Sort order + cmp_func = stand_cmp; + sort_type = std_rat; + break; + case 'b': // Sort order + cmp_func = blitz_cmp; + sort_type = blitz_rat; + break; + case 'L': // Sort order + cmp_func = light_cmp; + sort_type = light_rat; + break; + case 't': // format + style = 0; + break; + case 'v': // format + style = 1; + break; + case 'n': // format + style = 2; + break; + case 'U': + if (sel_bits == WHO_ALL) + sel_bits = WHO_UNREGISTERED; + else + sel_bits |= WHO_UNREGISTERED; + break; + case 'B': + if (sel_bits == WHO_ALL) + sel_bits = WHO_BUGTEAM; + else + sel_bits |= WHO_BUGTEAM; + break; + default: + return COM_BADPARAMETERS; + } + } + } + } + + sort_players(sortlist, cmp_func); + count = 0; + + for (p1 = 0; p1 < p_num; p1++) { + if (!who_ok(sortlist[p1], sel_bits)) + continue; + count++; + } + + startpoint = floorf((float) count * start_perc); + stoppoint = ceilf((float) count * stop_perc) - 1; + num_who = 0; + count = 0; + + for (p1 = 0; p1 < p_num; p1++) { + if (!who_ok(sortlist[p1], sel_bits)) + continue; + if (count >= startpoint && count <= stoppoint) + plist[num_who++] = sortlist[p1]; + + count++; + } + + if (num_who == 0) { + pprintf(p, "No logged in players match the flags in your who " + "request.\n"); + return COM_OK; + } + + switch (style) { + case 0: // terse + who_terse(p, num_who, plist, sort_type); + break; + case 1: // verbose + who_verbose(p, num_who, plist); + break; + case 2: // win-loss + who_winloss(p, num_who, plist); + break; + default: + return COM_BADPARAMETERS; + break; + } + + return COM_OK; +} + +PUBLIC int +com_refresh(int p, param_list param) +{ + int g, p1; + + if (param[0].type == TYPE_NULL) { + if (parray[p].game >= 0) { + send_board_to(parray[p].game, p); + } else { /* Do observing in here */ + if (parray[p].num_observe) { + for (g = 0; g < parray[p].num_observe; g++) { + send_board_to(parray[p].observe_list[g], + p); + } + } else { + pprintf(p, "You are neither playing, observing " + "nor examining a game.\n"); + return COM_OK; + } + } + } else { + if ((g = GameNumFromParam(p, &p1, ¶m[0])) < 0) + return COM_OK; + if (g >= g_num || + (garray[g].status != GAME_ACTIVE && + garray[g].status != GAME_EXAMINE)) { + pprintf(p, "No such game.\n"); + } else if (garray[g].private && parray[p].adminLevel == + ADMIN_USER) { + pprintf(p, "Sorry, game %d is a private game.\n", + (g + 1)); + } else { + if (garray[g].private) { + pprintf(p, "Refreshing PRIVATE game %d\n", + (g + 1)); + } + send_board_to(g, p); + } + } + + return COM_OK; +} + +PUBLIC int +com_prefresh(int p, param_list param) +{ + int retval, part; + + (void) param; + + if ((part = parray[p].partner) < 0) { + pprintf(p, "You do not have a partner.\n"); + return COM_OK; + } else if ((retval = pcommand(p, "refresh %s", parray[part].name)) == + COM_OK) + return COM_OK_NOPROMPT; + return retval; +} + +PUBLIC int +com_open(int p, param_list param) +{ + int retval; + + (void) param; + ASSERT(param[0].type == TYPE_NULL); + + if ((retval = pcommand(p, "set open")) != COM_OK) + return retval; + return COM_OK_NOPROMPT; +} + +PUBLIC int +com_simopen(int p, param_list param) +{ + int retval; + + (void) param; + ASSERT(param[0].type == TYPE_NULL); + + if ((retval = pcommand(p, "set simopen")) != COM_OK) + return retval; + return COM_OK_NOPROMPT; +} + +PUBLIC int +com_bell(int p, param_list param) +{ + int retval; + + (void) param; + ASSERT(param[0].type == TYPE_NULL); + + if ((retval = pcommand(p, "set bell")) != COM_OK) + return retval; + return COM_OK_NOPROMPT; +} + +PUBLIC int +com_flip(int p, param_list param) +{ + int retval; + + (void) param; + ASSERT(param[0].type == TYPE_NULL); + + if ((retval = pcommand(p, "set flip")) != COM_OK) + return retval; + return COM_OK_NOPROMPT; +} + +PUBLIC int +com_highlight(int p, param_list param) +{ + // XXX: unused + (void) param; + + pprintf(p, "Obsolete command. Please do set highlight <0-15>.\n"); + return COM_OK; +} + +PUBLIC int +com_style(int p, param_list param) +{ + int retval; + + ASSERT(param[0].type == TYPE_INT); + + if ((retval = pcommand(p, "set style %d", param[0].val.integer)) != + COM_OK) + return retval; + return COM_OK_NOPROMPT; +} + +PUBLIC int +com_promote(int p, param_list param) +{ + int retval; + + ASSERT(param[0].type == TYPE_WORD); + + if ((retval = pcommand(p, "set promote %s", param[0].val.word)) != + COM_OK) + return retval; + return COM_OK_NOPROMPT; +} + +PUBLIC int +com_alias(int p, param_list param) +{ + int al; + + if (param[0].type == TYPE_NULL) { + for (al = 0; al < parray[p].numAlias; al++) { + pprintf(p, "%s -> %s\n", + parray[p].alias_list[al].comm_name, + parray[p].alias_list[al].alias); + } + + return COM_OK; + } + + al = alias_lookup(param[0].val.word, parray[p].alias_list, + parray[p].numAlias); + + if (param[1].type == TYPE_NULL) { + if (al < 0) { + pprintf(p, "You have no alias named '%s'.\n", + param[0].val.word); + } else { + pprintf(p, "%s -> %s\n", + parray[p].alias_list[al].comm_name, + parray[p].alias_list[al].alias); + } + } else { + if (al < 0) { + if (parray[p].numAlias >= MAX_ALIASES - 1) { + pprintf(p, "You have your maximum of %d " + "aliases.\n", (MAX_ALIASES - 1)); + } else { + if (!strcmp(param[0].val.string, "quit")) { + pprintf(p, "You can't alias this " + "command.\n"); + } else if (!strcmp(param[0].val.string, + "unalias")) { + pprintf(p, "You can't alias this " + "command.\n"); + } else { + parray[p].alias_list[parray[p].numAlias].comm_name = + xstrdup(param[0].val.word); + parray[p].alias_list[parray[p].numAlias].alias = + xstrdup(param[1].val.string); + parray[p].numAlias++; + pprintf(p, "Alias set.\n"); + } + } + } else { + rfree(parray[p].alias_list[al].alias); + parray[p].alias_list[al].alias = + xstrdup(param[1].val.string); + pprintf(p, "Alias replaced.\n"); + } + + parray[p].alias_list[parray[p].numAlias].comm_name = NULL; + } + + return COM_OK; +} + +PUBLIC int +com_unalias(int p, param_list param) +{ + int al; + + ASSERT(param[0].type == TYPE_WORD); + + al = alias_lookup(param[0].val.word, parray[p].alias_list, + parray[p].numAlias); + + if (al < 0) { + pprintf(p, "You have no alias named '%s'.\n", + param[0].val.word); + } else { + bool removed = false; + const int sz = (int) ARRAY_SIZE(parray[0].alias_list); + + rfree(parray[p].alias_list[al].comm_name); + rfree(parray[p].alias_list[al].alias); + + parray[p].alias_list[al].comm_name = NULL; + parray[p].alias_list[al].alias = NULL; + + for (int i = al; i < parray[p].numAlias; i++) { + if (i >= sz || i + 1 >= sz) { + warnx("%s: overflowed array index read/write", + __func__); + break; + } + + parray[p].alias_list[i].comm_name = + parray[p].alias_list[i + 1].comm_name; + parray[p].alias_list[i].alias = + parray[p].alias_list[i + 1].alias; + removed = true; + } + + if (!removed) { + pprintf(p, "Remove error.\n"); + return COM_FAILED; + } + + parray[p].numAlias--; + parray[p].alias_list[parray[p].numAlias].comm_name = NULL; + pprintf(p, "Alias removed.\n"); + } + + return COM_OK; +} + +PUBLIC int +com_servers(int p, param_list param) +{ + pprintf(p, "There are no other servers known to this server.\n"); + (void) param; + return COM_OK; +} + +PUBLIC int +com_index(int p, param_list param) +{ + char *iwant, *filenames[1000]; + char index_default[] = "_index"; + int i; + + if (param[0].type == TYPE_NULL) { + iwant = index_default; + } else { + iwant = param[0].val.word; + + if (!safestring(iwant)) { + pprintf(p, "Illegal character in category %s.\n", + iwant); + return COM_OK; + } + } + + i = search_directory(index_dir, iwant, filenames, + ARRAY_SIZE(filenames)); + + if (i == 0) { + pprintf(p, "No index entry for \"%s\".\n", iwant); + } else if (i == 1 || !strcmp(*filenames, iwant)) { + if (psend_file(p, index_dir, *filenames)) { + /* + * We should never reach this unless the file + * was just deleted. + */ + pprintf(p, "Index file %s could not be found! ", + *filenames); + pprintf(p, "Please inform an admin of this. " + "Thank you.\n"); + } + } else { + pprintf(p, "Matches:"); + display_directory(p, filenames, i); + } + + return COM_OK; +} + +PUBLIC int +com_help(int p, param_list param) +{ + char *iwant, *filenames[1000]; + char help_default[] = "_help"; + int i, UseLang = parray[p].language; + + if (param[0].type == TYPE_NULL) { + iwant = help_default; + } else { + iwant = param[0].val.word; + + if (!safestring(iwant)) { + pprintf(p, "Illegal character in command %s.\n", iwant); + return COM_OK; + } + } + + i = search_directory(help_dir[UseLang], iwant, filenames, + ARRAY_SIZE(filenames)); + + if (i == 0) { + if (UseLang != LANG_DEFAULT) { + i += search_directory(help_dir[LANG_DEFAULT], iwant, + filenames, ARRAY_SIZE(filenames)); + + if (i > 0) { + pprintf(p, "No help available in %s; using %s " + "instead.\n", + Language(UseLang), + Language(LANG_DEFAULT)); + UseLang = LANG_DEFAULT; + } + } + + if (i == 0) { + pprintf(p, "No help available on \"%s\".\n", iwant); + return COM_OK; + } + } + + if (i == 1 || !strcmp(*filenames, iwant)) { + if (psend_file(p, help_dir[UseLang], *filenames)) { + /* + * We should never reach this unless the file + * was just deleted. + */ + pprintf(p, "Helpfile %s could not be found! ", + *filenames); + pprintf(p, "Please inform an admin of this. " + "Thank you.\n"); + } + } else { + pprintf(p, "Matches:"); + display_directory(p, filenames, i); + pprintf(p, "[Type \"info\" for a list of FICS general " + "information files.]\n"); + } + + return COM_OK; +} + +PUBLIC int +com_info(int p, param_list param) +{ + char *filenames[1000]; + int n; + + // XXX: unused + (void) param; + + if ((n = search_directory(info_dir, NULL, filenames, + ARRAY_SIZE(filenames))) > 0) + display_directory(p, filenames, n); + return COM_OK; +} + +PRIVATE int +FindAndShowFile(int p, param_list param, char *dir) +{ + char *iwant, *filenames[1000]; + int i; + + if (param[0].type == TYPE_NULL) { + iwant = NULL; + } else { + iwant = param[0].val.word; + + if (!safestring(iwant)) { + pprintf(p, "Illegal character in filename %s.\n", + iwant); + return COM_OK; + } + } + + i = search_directory(dir, iwant, filenames, ARRAY_SIZE(filenames)); + + if (i == 0) { + pprintf(p, "No information available on \"%s\".\n", + (iwant ? iwant : "")); + } else if (i == 1 || !strcmp(*filenames, iwant ? iwant : "")) { + if (psend_file(p, dir, *filenames)) { + /* + * We should never reach this unless the file + * was just deleted. + */ + pprintf(p, "File %s could not be found! ", *filenames); + pprintf(p, "Please inform an admin of this. " + "Thank you.\n"); + } + } else { + if (iwant && *iwant) + pprintf(p, "Matches:\n"); + display_directory(p, filenames, i); + } + + return COM_OK; +} + +PUBLIC int +com_uscf(int p, param_list param) +{ + return FindAndShowFile(p, param, uscf_dir); +} + +PUBLIC int +com_adhelp(int p, param_list param) +{ + return FindAndShowFile(p, param, adhelp_dir); +} + +PUBLIC int +com_mailsource(int p, param_list param) +{ + char *buffer[1000]; + char *iwant; + char fname[MAX_FILENAME_SIZE]; + char subj[120]; + int count; + + if (!parray[p].registered) { + pprintf(p, "Only registered people can use the mailsource " + "command.\n"); + return COM_OK; + } + + if (param[0].type == TYPE_NULL) + iwant = NULL; + else + iwant = param[0].val.word; + + if ((count = search_directory(source_dir, iwant, buffer, + ARRAY_SIZE(buffer))) == 0) { + pprintf(p, "Found no source file matching \"%s\".\n", + (iwant ? iwant : "")); + } else if ((count == 1) || !strcmp(iwant ? iwant : "", *buffer)) { + snprintf(subj, sizeof subj, "FICS source file from server " + "%s: %s", + fics_hostname, + *buffer); + snprintf(fname, sizeof fname, "%s/%s", + source_dir, + *buffer); + + mail_file_to_user(p, subj, fname); + + pprintf(p, "Source file %s sent to %s\n", *buffer, + parray[p].emailAddress); + } else { + pprintf(p, "Found %d source files matching that:\n", count); + + if (iwant && *iwant) { + display_directory(p, buffer, count); + } else { // this junk is to get *.c *.h + char *s; + multicol *m = multicol_start(count); + + for (int i = 0; i < count; i++) { + s = (buffer[i] + strlen(buffer[i]) - 2); + + if (s >= buffer[i] && + (!strcmp(s, ".c") || !strcmp(s, ".h"))) + multicol_store(m, buffer[i]); + } + + multicol_pprint(m, p, 78, 1); + multicol_end(m); + } + } + + return COM_OK; +} + +PUBLIC int +com_mailhelp(int p, param_list param) +{ + char *buffer[1000]; + char *iwant; + char fname[MAX_FILENAME_SIZE]; + char subj[120]; + int count; + int lang = parray[p].language; + + if (!parray[p].registered) { + pprintf(p, "Only registered people can use the mailhelp " + "command.\n"); + return COM_OK; + } + + if (param[0].type == TYPE_NULL) + iwant = NULL; + else + iwant = param[0].val.word; + + count = search_directory(help_dir[lang], iwant, buffer, + ARRAY_SIZE(buffer)); + + if (count == 0 && lang != LANG_DEFAULT) { + count += search_directory(help_dir[LANG_DEFAULT], iwant, buffer, + ARRAY_SIZE(buffer)); + + if (count > 0) { + pprintf(p, "No help available in %s; " + "using %s instead.\n", + Language(lang), + Language(LANG_DEFAULT)); + lang = LANG_DEFAULT; + } + } + + if (count == 0) { + pprintf(p, "Found no help file matching \"%s\".\n", + (iwant ? iwant : "")); + } else if (count == 1 || !strcmp(*buffer, iwant ? iwant : "")) { + snprintf(subj, sizeof subj, "FICS help file from server %s: %s", + fics_hostname, + *buffer); + snprintf(fname, sizeof fname, "%s/%s", + help_dir[lang], + *buffer); + + mail_file_to_user(p, subj, fname); + + pprintf(p, "Help file %s sent to %s\n", *buffer, + parray[p].emailAddress); + } else { + pprintf(p, "Found %d helpfiles matching that:\n", count); + display_directory(p, buffer, count); + } + + return COM_OK; +} + +PUBLIC int +com_handles(int p, param_list param) +{ + char *buffer[1000]; + char pdir[MAX_FILENAME_SIZE]; + int count; + + snprintf(pdir, sizeof pdir, "%s/%c", player_dir, param[0].val.word[0]); + + count = search_directory(pdir, param[0].val.word, buffer, + ARRAY_SIZE(buffer)); + pprintf(p, "Found %d names.\n", count); + + if (count > 0) + display_directory(p, buffer, count); + + return COM_OK; +} + +PUBLIC int +com_getpi(int p, param_list param) +{ + int p1; + + if (!in_list(p, L_TD, parray[p].name)) { + pprintf(p, "Only TD programs are allowed to use this command." + "\n"); + return COM_OK; + } + + if ((p1 = player_find_bylogin(param[0].val.word)) < 0 || + parray[p1].registered == 0) + return COM_OK; + + if (!parray[p1].registered) { + pprintf(p, "*getpi %s -1 -1 -1*\n", parray[p1].name); + } else { + pprintf(p, "*getpi %s %d %d %d*\n", parray[p1].name, + parray[p1].w_stats.rating, + parray[p1].b_stats.rating, + parray[p1].s_stats.rating); + } + + return COM_OK; +} + +PUBLIC int +com_getps(int p, param_list param) +{ + int p1; + + if (((p1 = player_find_bylogin(param[0].val.word)) < 0 || + parray[p1].registered == 0) || + parray[p1].game < 0) { + pprintf(p, "*status %s 1*\n", param[0].val.word); + return COM_OK; + } + + pprintf(p, "*status %s 0 %s*\n", parray[p1].name, + parray[(parray[p1].opponent)].name); + return COM_OK; +} + +PUBLIC int +com_limits(int p, param_list param) +{ + // XXX: unused + (void) param; + + pprintf(p, "\nCurrent hardcoded limits:\n"); + pprintf(p, " Max number of players: %d\n", MAX_PLAYER); + pprintf(p, " Max number of channels and max capacity: %d\n", + MAX_CHANNELS); + pprintf(p, " Max number of channels one can be in: %d\n", + MAX_INCHANNELS); + pprintf(p, " Max number of people on the notify list: %d\n", + MAX_NOTIFY); + pprintf(p, " Max number of aliases: %d\n", (MAX_ALIASES - 1)); + pprintf(p, " Max number of games you can observe at a time: %d\n", + MAX_OBSERVE); + pprintf(p, " Max number of requests pending: %d\n", MAX_PENDING); + pprintf(p, " Max number of people on the censor list: %d\n", + MAX_CENSOR); + pprintf(p, " Max number of people in a simul game: %d\n", MAX_SIMUL); + pprintf(p, " Max number of messages one can receive: %d\n", + MAX_MESSAGES); + pprintf(p, " Min number of games to be active: %d\n", PROVISIONAL); + + if (parray[p].adminLevel < ADMIN_ADMIN && + !titled_player(p, parray[p].login)) { + pprintf(p, " Size of journal (entries): %d\n", MAX_JOURNAL); + } else { + pprintf(p, " Size of journal (entries): 26\n"); + } + + pprintf(p, "\nAdmin settable limits:\n"); + pprintf(p, " Shout quota gives two shouts per %d seconds.\n", + quota_time); + + return COM_OK; +} |