aboutsummaryrefslogtreecommitdiffstats
path: root/FICS/adminproc.c
diff options
context:
space:
mode:
authorMarkus Uhlin <markus@nifty-networks.net>2025-09-15 18:50:32 +0200
committerMarkus Uhlin <markus@nifty-networks.net>2025-09-15 18:50:32 +0200
commitc3eee8e333866d92e5fd94ae83cef618758c11bb (patch)
tree234a06fd90bd61a6668490a0cbf8870e6c674b81 /FICS/adminproc.c
FICS RPBLC v1.4.61.4.6
Diffstat (limited to 'FICS/adminproc.c')
-rw-r--r--FICS/adminproc.c2090
1 files changed, 2090 insertions, 0 deletions
diff --git a/FICS/adminproc.c b/FICS/adminproc.c
new file mode 100644
index 0000000..9f192bc
--- /dev/null
+++ b/FICS/adminproc.c
@@ -0,0 +1,2090 @@
+/*
+ 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.
+*/
+
+#include "stdinclude.h"
+#include "common.h"
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "adminproc.h"
+#include "command.h"
+#include "comproc.h"
+#include "fics_getsalt.h"
+#include "ficsmain.h"
+#include "gamedb.h"
+#include "gameproc.h"
+#include "maxxes-utils.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"
+
+#define PASSLEN 8
+
+PUBLIC int num_anews = -1;
+
+/*
+ * adjudicate
+ *
+ * Usage: adjudicate white_player black_player result
+ *
+ * Adjudicates a saved (stored) game between white_player and black_player.
+ * The result is one of: abort, draw, white, black. "Abort" cancels the game
+ * (no win, loss or draw), "white" gives white_player the win, "black" gives
+ * black_player the win, and "draw" gives a draw.
+ */
+PUBLIC int
+com_adjudicate(int p, param_list param)
+{
+ int wp, wconnected, bp, bconnected, g, inprogress, confused = 0;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &wp, &wconnected))
+ return COM_OK;
+ if (!FindPlayer(p, param[1].val.word, &bp, &bconnected)) {
+ if (!wconnected)
+ player_remove(wp);
+ return COM_OK;
+ }
+
+ inprogress = (parray[wp].game >= 0 && parray[wp].opponent == bp);
+
+ if (inprogress) {
+ g = parray[wp].game;
+ } else {
+ g = game_new();
+
+ if (game_read(g, wp, bp) < 0) {
+ confused = 1;
+ pprintf(p, "There is no stored game %s vs. %s\n",
+ parray[wp].name,
+ parray[bp].name);
+ } else {
+ garray[g].white = wp;
+ garray[g].black = bp;
+ }
+ }
+
+ if (!confused) {
+ if (strstr("abort", param[2].val.word) != NULL) {
+ game_ended(g, WHITE, END_ADJABORT);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been aborted.",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been aborted.",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else if (strstr("draw", param[2].val.word) != NULL) {
+ game_ended(g, WHITE, END_ADJDRAW);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a draw",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a draw",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else if (strstr("white", param[2].val.word) != NULL) {
+ game_ended(g, WHITE, END_ADJWIN);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a win",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a loss",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else if (strstr("black", param[2].val.word) != NULL) {
+ game_ended(g, BLACK, END_ADJWIN);
+
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a loss",
+ parray[wp].name,
+ parray[wp].name,
+ parray[bp].name);
+ pcommand(p, "message %s Your game \"%s vs. %s\" has "
+ "been adjudicated as a win",
+ parray[bp].name,
+ parray[wp].name,
+ parray[bp].name);
+ } else {
+ confused = 1;
+ pprintf(p, "Result must be one of: abort draw white "
+ "black\n");
+ }
+ } /* !confused */
+
+ if (!confused) {
+ pprintf(p, "Game adjudicated.\n");
+
+ if (!inprogress) {
+ game_delete(wp, bp);
+ } else {
+ return COM_OK;
+ }
+ }
+
+ game_remove(g);
+
+ if (!wconnected)
+ player_remove(wp);
+ if (!bconnected)
+ player_remove(bp);
+ return COM_OK;
+}
+
+/*
+ * create_news_file: Creates either a general or and admin news
+ * file, depending upon the admin switch.
+ */
+PRIVATE int
+create_news_file(int p, param_list param, int admin)
+{
+ FILE *fp;
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (admin) {
+ if (param[0].val.integer > num_anews) {
+ pprintf(p, "There must be an admin news index #%d "
+ "before you can create the file.",
+ param[0].val.integer);
+ } else {
+ msnprintf(filename, sizeof filename, "%s/adminnews.%d",
+ news_dir,
+ param[0].val.integer);
+ fd = open(filename, g_open_flags[1], g_open_modes);
+ if (fd < 0)
+ return COM_FAILED;
+ else if ((fp = fdopen(fd, "w")) != NULL) {
+ fprintf(fp, "%s\n", param[1].val.string);
+ fclose(fp);
+ } else
+ close(fd);
+ }
+ } else {
+ if (param[0].val.integer > num_news) {
+ pprintf(p, "There must be a news index #%d before "
+ "you can create the file.", param[0].val.integer);
+ } else {
+ msnprintf(filename, sizeof filename, "%s/news.%d",
+ news_dir,
+ param[0].val.integer);
+ fd = open(filename, g_open_flags[1], g_open_modes);
+ if (fd < 0)
+ return COM_FAILED;
+ else if ((fp = fdopen(fd, "w")) != NULL) {
+ fprintf(fp, "%s\n", param[1].val.string);
+ fclose(fp);
+ } else
+ close(fd);
+ }
+ }
+
+ return COM_OK;
+}
+
+PRIVATE int
+add_item(char *new_item, char *filename)
+{
+ FILE *new_fp, *old_fp;
+ char junk[MAX_LINE_SIZE] = { '\0' };
+ char tmp_file[MAX_FILENAME_SIZE] = { '\0' };
+ int fd;
+
+ msnprintf(tmp_file, sizeof tmp_file, "%s/.tmp.idx", news_dir);
+
+ fd = open(tmp_file, g_open_flags[1], g_open_modes);
+
+ if (fd < 0)
+ return 0;
+ else if ((new_fp = fdopen(fd, "w")) == NULL) {
+ close(fd);
+ return 0;
+ }
+
+ fprintf(new_fp, "%s", new_item);
+
+ if ((old_fp = fopen(filename, "r")) == NULL)
+ goto end;
+
+ while (1) {
+ if (fgets(junk, sizeof junk, old_fp) == NULL ||
+ feof(old_fp))
+ break;
+ fprintf(new_fp, "%s", junk);
+ }
+
+ end:
+ (void) fclose(new_fp);
+
+ if (old_fp) {
+ (void) fclose(old_fp);
+ (void) remove(filename);
+ }
+
+ (void) rename(tmp_file, filename);
+ return 1;
+}
+
+/*
+ * Adds a new item to either the general or admin news index file,
+ * depending upon the admin switch.
+ */
+PRIVATE int
+create_news_index(int p, param_list param, int admin)
+{
+ char filename[MAX_FILENAME_SIZE] = { '\0' };
+ char new_item[MAX_LINE_SIZE] = { '\0' };
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (admin) {
+ if (strlen(param[0].val.string) > 50) {
+ pprintf(p, "Sorry, you must limit an index to 50 "
+ "charaters! Admin news index not created.\n");
+ } else {
+ num_anews++;
+
+ snprintf(new_item, sizeof new_item, "%ld %d %s\n",
+ (long int)time(NULL),
+ num_anews,
+ param[0].val.string);
+ snprintf(filename, sizeof filename,
+ "%s/newadminnews.index", news_dir);
+
+ if (add_item(new_item, filename)) {
+ pprintf(p, "Index for admin news item #%d "
+ "created.\n", num_anews);
+ pprintf(p, "Please use 'canewsf' to include "
+ "more info.\n");
+ } else {
+ pprintf(p, "Something went wrong creating item."
+ "\nNotify Marsalis.\n");
+ }
+ }
+ } else {
+ if (strlen(param[0].val.string) > 50) {
+ pprintf(p, "Sorry, you must limit an index to 50 "
+ "charaters! News index not created.\n");
+ } else {
+ num_news++;
+
+ snprintf(filename, sizeof filename, "%s/newnews.index",
+ news_dir);
+ snprintf(new_item, sizeof new_item, "%ld %d %s\n",
+ (long int)time(NULL),
+ num_news,
+ param[0].val.string);
+
+ if (add_item(new_item, filename)) {
+ pprintf(p, "Index for news item #%d created.\n",
+ num_news);
+ pprintf(p, "Please use 'cnewsf' to include "
+ "more info.\n");
+ } else {
+ pprintf(p, "Something went wrong creating item."
+ "\nNotify Marsalis.\n");
+ }
+ }
+ }
+
+ return COM_OK;
+}
+
+/* cnewsi
+ *
+ * Usage: cnewsi message
+ *
+ *
+ * This command adds a new item to the news index. The message is limited to
+ * 45 characters for formating purposes. In essence, the news index works
+ * like a newspaper headline, giving the user enough information to know
+ * whether they should read the entire news file for that item. After
+ * creating the news item, the command reports the news item number along
+ * with a reminder to create a news file if necessary.
+ */
+PUBLIC int
+com_cnewsi(int p, param_list param)
+{
+ return create_news_index(p, param, 0);
+}
+
+/* cnewsf
+ *
+ * Usage: cnewsf # message
+ *
+ * This command allows you to add additional information about a news item
+ * that had previously been created using 'cnewsi'. The '#' is the number
+ * of the news index and 'message' is the additional text. You can also
+ * modify a previous news item description and thus update the news item
+ * easily.
+ */
+PUBLIC int
+com_cnewsf(int p, param_list param)
+{
+ return create_news_file(p, param, 0);
+}
+
+PUBLIC int
+com_canewsi(int p, param_list param)
+{
+ return create_news_index(p, param, 1);
+}
+
+PUBLIC int
+com_canewsf(int p, param_list param)
+{
+ return create_news_file(p, param, 1);
+}
+
+/*
+ * anews
+ *
+ *
+ * Usage: anews [#, all]
+ *
+ * This command is used to display anews (admin news) entries. The
+ * entries are numbered. "Anews #" displays that anews item. "Anews
+ * all" will display all items.
+ *
+ */
+PUBLIC int
+com_anews(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_junk = "%" SCNd64 " " "%9s";
+ int found = 0;
+ int64_t lval = 0;
+ time_t crtime = 0;
+
+ msnprintf(filename, sizeof filename, "%s/newadminnews.index", news_dir);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ fprintf(stderr, "Cant find news index.\n");
+ return COM_OK;
+ }
+
+ _Static_assert(9 < ARRAY_SIZE(count), "Array too small");
+
+ if (param[0].type == 0) {
+ /*
+ * No params - then just display index over news.
+ */
+
+ msnprintf(filename, sizeof filename, "%s/newadminnews.index",
+ news_dir);
+
+ pprintf(p, "Index of recent admin news items:\n");
+
+ if (fgets(junk, sizeof junk, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+ if (sscanf(junk, v_scan_junk, &lval, count) != 2) {
+ warnx("%s: sscanf() error: too few items", __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 all news items.
+ */
+
+ pprintf(p, "Index of all admin news items:\n");
+
+ if (fgets(junk, sizeof junk, fp) == NULL) {
+ warnx("%s: fgets() error", __func__);
+ fclose(fp);
+ return COM_FAILED;
+ }
+ if (sscanf(junk, v_scan_junk, &lval, count) != 2) {
+ warnx("%s: sscanf() error: too few items", __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 {
+ while (!feof(fp) && !found) {
+ junkp = junk;
+
+ if (fgets(junk, sizeof junk, fp) == NULL)
+ break;
+
+ if (strlen(junk) > 1) {
+ if (sscanf(junkp, v_scan_junk, &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, "ANEWS %3s (%s) %s\n", count,
+ fix_time(strltime(&crtime)), junkp);
+ }
+ }
+ }
+
+ fclose(fp);
+
+ if (!found) {
+ pprintf(p, "Bad index number!\n");
+ return COM_OK;
+ }
+
+ msnprintf(filename, sizeof filename, "%s/adminnews.%s",
+ news_dir,
+ param[0].val.word);
+
+ if ((fp = fopen(filename, "r")) == NULL) {
+ pprintf(p, "No more info.\n");
+ return COM_OK;
+ }
+
+ fclose(fp);
+ msnprintf(filename, sizeof filename, "adminnews.%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
+strcmpwild(char *mainstr, char *searchstr)
+{
+ size_t len[2];
+
+ len[0] = strlen(mainstr);
+ len[1] = strlen(searchstr);
+
+ if (len[0] < len[1])
+ return 1;
+
+ for (size_t i = 0; i < len[0]; i++) {
+ if (searchstr[i] == '*')
+ return 0;
+ if (mainstr[i] != searchstr[i])
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * chkip
+ *
+ * Usage: chkip ip_address
+ *
+ * This command returns the names of all users currently logged on
+ * from a given IP address.
+ */
+PUBLIC int
+com_checkIP(int p, param_list param)
+{
+ char *ipstr = param[0].val.word;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ pprintf(p, "Matches the following player(s):\n\n");
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (!strcmpwild(dotQuad(parray[p1].thisHost), ipstr) &&
+ (parray[p1].status != PLAYER_EMPTY)) {
+ pprintf(p, "%16.16s %s\n", parray[p1].name,
+ dotQuad(parray[p1].thisHost));
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_checkSOCKET(int p, param_list param)
+{
+ int fd = param[0].val.integer;
+ int p1, flag;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ flag = 0;
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].socket == fd) {
+ flag = 1;
+ pprintf(p, "Socket %d is used by %s\n", fd,
+ parray[p1].name);
+ }
+ }
+
+ if (!flag)
+ pprintf(p, "Socket %d is unused!\n", fd);
+ return COM_OK;
+}
+
+/*
+ * chkpl
+ *
+ * Usage: chkpl handle
+ *
+ * This command displays server information about a given user. Items
+ * displayed are:
+ *
+ * number X in parray of size Y
+ * name
+ * login
+ * fullName
+ * emailAddress
+ * socket
+ * registered
+ * last_tell
+ * last_channel
+ * logon_time
+ * adminLevel
+ * thisHost
+ * lastHost
+ * num_comments
+ */
+PUBLIC int
+com_checkPLAYER(int p, param_list param)
+{
+ char *v_player = param[0].val.word;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ p1 = player_search(p, param[0].val.word);
+
+ if (!p1)
+ return COM_OK;
+
+ if (p1 < 0) {
+ p1 = (-p1) - 1;
+ pprintf(p, "%s is not logged in.\n", v_player);
+ stolower(v_player);
+
+ pprintf(p, "name = %s\n", parray[p1].name);
+ pprintf(p, "login = %s\n", parray[p1].login);
+ pprintf(p, "fullName = %s\n",
+ (parray[p1].fullName
+ ? parray[p1].fullName
+ : "(none)"));
+ pprintf(p, "emailAddress = %s\n",
+ (parray[p1].emailAddress
+ ? parray[p1].emailAddress
+ : "(none)"));
+ pprintf(p, "adminLevel = %d\n", parray[p1].adminLevel);
+#if 0
+ pprintf(p, "network_player = %d\n", parray[p1].network_player);
+#endif
+ pprintf(p, "lastHost = %s\n", dotQuad(parray[p1].lastHost));
+ pprintf(p, "num_comments = %d\n", parray[p1].num_comments);
+
+ player_remove(p1);
+ return COM_OK;
+ } else {
+ char tbuf[30] = { '\0' };
+
+ p1 = p1 - 1;
+
+ pprintf(p, "%s is number %d in parray of size %d\n", v_player, p1,
+ (p_num + 1));
+ pprintf(p, "name = %s\n", parray[p1].name);
+ pprintf(p, "login = %s\n", parray[p1].login);
+ pprintf(p, "fullName = %s\n",
+ (parray[p1].fullName
+ ? parray[p1].fullName
+ : "(none)"));
+ pprintf(p, "emailAddress = %s\n",
+ (parray[p1].emailAddress
+ ? parray[p1].emailAddress
+ : "(none)"));
+ pprintf(p, "socket = %d\n", parray[p1].socket);
+ pprintf(p, "registered = %d\n", parray[p1].registered);
+ pprintf(p, "last_tell = %d\n", parray[p1].last_tell);
+ pprintf(p, "last_channel = %d\n", parray[p1].last_channel);
+ pprintf(p, "logon_time = %s",
+ (ctime_r(&parray[p1].logon_time, tbuf) != NULL
+ ? &tbuf[0]
+ : "n/a"));
+ pprintf(p, "adminLevel = %d\n", parray[p1].adminLevel);
+#if 0
+ pprintf(p, "network_player = %d\n", parray[p1].network_player);
+#endif
+ pprintf(p, "thisHost = %s\n", dotQuad(parray[p1].thisHost));
+ pprintf(p, "lastHost = %s\n", dotQuad(parray[p1].lastHost));
+ pprintf(p, "num_comments = %d\n", parray[p1].num_comments);
+ }
+
+ return COM_OK;
+}
+
+/*
+ * chkts
+ *
+ * Usage: chkts
+ *
+ * This command displays all current users who are using timeseal.
+ */
+PUBLIC int
+com_checkTIMESEAL(int p, param_list param)
+{
+ int p1, count = 0;
+
+ /* XXX: maybe unused */
+ (void) p1;
+ (void) count;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ pprintf(p, "The following player(s) are using timeseal:\n\n");
+
+#ifdef TIMESEAL
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (parray[p1].status != PLAYER_EMPTY &&
+ con[parray[p1].socket].timeseal) {
+ pprintf(p, "%s\n", parray[p1].name);
+ count++;
+ }
+ }
+ pprintf(p, "\nNumber of people using timeseal: %d\n", count);
+#endif
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_checkGAME(int p, param_list param)
+{
+ char tmp[10 + 1 + 7]; // enough to store number
+ // 'black: ' and '\0'
+ int found = 0;
+ int p1, g, link;
+ multicol *m;
+ time_t startTime;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (g_num == 0) {
+ pprintf(p, "No games are currently linked into the 'garray' "
+ "structure.\n");
+ return COM_OK;
+ }
+
+ if (param[0].type == TYPE_WORD) { // a player name
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s doesn't appear to be logged in.\n",
+ param[0].val.word);
+ pprintf(p, "Searching through garray to find matching "
+ "game numbers.\n");
+ pprintf(p, "Use chkgame <number> to view the results."
+ "\n");
+
+ m = multicol_start(g_num * 2); // Obviously no more
+ // than that
+
+ for (g = 0; g < g_num; g++) {
+ memset(tmp, 0, sizeof tmp); // XXX
+ multicol_store(m, tmp); // is this call right?
+
+ if (!strcasecmp(garray[g].white_name,
+ param[0].val.word)) {
+ msnprintf(tmp, sizeof tmp, "White: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+ if (!strcasecmp(garray[g].black_name,
+ param[0].val.word)) {
+ msnprintf(tmp, sizeof tmp, "Black: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+ }
+
+ if (found)
+ multicol_pprint(m, p, parray[p].d_width, 2);
+ else
+ pprintf(p, "No matching games were found.\n");
+ multicol_end(m);
+
+ return COM_OK;
+ }
+
+ if ((g = parray[p1].game) < 0) {
+ pprintf(p, "%s doesn't appear to be playing a game.\n",
+ parray[p1].name);
+ pprintf(p, "Searching through garray to find matching "
+ "game numbers.\n");
+ pprintf(p, "Use chkgame <number> to view the results."
+ "\n");
+
+ m = multicol_start(g_num * 2); // Obviously no more
+ // than that
+
+ for (g = 0; g < g_num; g++) {
+ if (garray[g].white == p1) {
+ msnprintf(tmp, sizeof tmp, "White: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+
+ if (garray[g].black == p1) {
+ msnprintf(tmp, sizeof tmp, "Black: %d",
+ g);
+ multicol_store(m, tmp);
+ found = 1;
+ }
+ }
+
+ if (found)
+ multicol_pprint(m, p, parray[p].d_width, 2);
+ else
+ pprintf(p, "No matching games were found.\n");
+ multicol_end(m);
+
+ return COM_OK;
+ }
+ } else {
+ if ((g = param[0].val.integer - 1) < 0 || g >= g_num) {
+ pprintf(p, "The current range of game numbers is 1 to "
+ "%d.\n", g_num);
+ return COM_OK;
+ }
+ }
+
+ startTime = untenths(garray[g].timeOfStart);
+
+ pprintf(p, "Current stored info for game %d (garray[%d]):\n", (g + 1),
+ g);
+ pprintf(p, "Initial white time: %d Initial white increment %d\n",
+ (garray[g].wInitTime / 600),
+ (garray[g].wIncrement / 10));
+ pprintf(p, "Initial black time: %d Initial black increment %d\n",
+ (garray[g].bInitTime / 600),
+ (garray[g].bIncrement / 10));
+ pprintf(p, "Time of starting: %s\n", strltime(&startTime));
+ pprintf(p, "Game is: %s (%d) vs. %s (%d)\n",
+ garray[g].white_name,
+ garray[g].white_rating,
+ garray[g].black_name,
+ garray[g].black_rating);
+ pprintf(p, "White parray entry: %d Black parray entry %d\n",
+ garray[g].white,
+ garray[g].black);
+
+ if ((link = garray[g].link) >= 0) {
+ pprintf(p, "Bughouse linked to game: %d\n",
+ (garray[g].link + 1));
+ pprintf(p, "Partner is playing game: %s (%d) vs. %s (%d)\n",
+ garray[link].white_name,
+ garray[link].white_rating,
+ garray[link].black_name,
+ garray[link].black_rating);
+ } else {
+ pprintf(p, "Game is not bughouse or link to partner's game not "
+ "found.\n");
+ }
+
+ pprintf(p, "Game is %s\n", (garray[g].rated ? "rated" : "unrated"));
+ pprintf(p, "Game is %s\n", (garray[g].private ? "private" :
+ "not private"));
+
+ if (garray[g].type == TYPE_UNTIMED)
+ pprintf(p, "Games is of type: untimed\n");
+ else if (garray[g].type == TYPE_BLITZ)
+ pprintf(p, "Games is of type: blitz\n");
+ else if (garray[g].type == TYPE_STAND)
+ pprintf(p, "Games is of type: standard\n");
+ else if (garray[g].type == TYPE_NONSTANDARD)
+ pprintf(p, "Games is of type: non-standard\n");
+ else if (garray[g].type == TYPE_WILD)
+ pprintf(p, "Games is of type: wild\n");
+ else if (garray[g].type == TYPE_LIGHT)
+ pprintf(p, "Games is of type: lightning\n");
+ else if (garray[g].type == TYPE_BUGHOUSE)
+ pprintf(p, "Games is of type: bughouse\n");
+ else
+ pprintf(p, "Games is of type: Unknown - Error!\n");
+
+ pprintf(p, "%d halfmove(s) have been made\n", garray[g].numHalfMoves);
+
+ if (garray[g].status == GAME_ACTIVE)
+ game_update_time(g);
+
+ pprintf(p, "White's time %s Black's time ",
+ tenth_str((garray[g].wTime > 0 ? garray[g].wTime : 0), 0));
+ pprintf(p, "%s\n",
+ tenth_str((garray[g].bTime > 0 ? garray[g].bTime : 0), 0));
+ pprintf(p, "The clock is%sticking\n", ((garray[g].clockStopped ||
+ garray[g].status != GAME_ACTIVE) ? " not " : " "));
+
+ if (garray[g].status == GAME_EMPTY)
+ pprintf(p, "Game status: GAME_EMPTY\n");
+ else if (garray[g].status == GAME_NEW)
+ pprintf(p, "Game status: GAME_NEW\n");
+ else if (garray[g].status == GAME_ACTIVE)
+ pprintf(p, "Game status: GAME_ACTIVE\n");
+ else if (garray[g].status == GAME_EXAMINE)
+ pprintf(p, "Game status: GAME_EXAMINE\n");
+ else
+ pprintf(p, "Game status: Unknown - Error!\n");
+ return COM_OK;
+}
+
+/*
+ * remplayer
+ *
+ * Usage: remplayer name
+ *
+ * Removes an account. A copy of its files are saved under .rem.* which can
+ * be found in the appropriate directory (useful in case of an accident).
+ *
+ * The account's details, messages, games and logons are all saved as
+ * 'zombie' files. These zombie accounts are not listed in handles or
+ * totals.
+ */
+PUBLIC int
+com_remplayer(int p, param_list param)
+{
+ char *v_player = param[0].val.word;
+ char playerlower[MAX_LOGIN_NAME] = { '\0' };
+ int p1, lookup;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ mstrlcpy(playerlower, v_player, sizeof(playerlower));
+ stolower(playerlower);
+ p1 = player_new();
+ lookup = player_read(p1, playerlower);
+
+ if (!lookup) {
+ if (parray[p].adminLevel <= parray[p1].adminLevel &&
+ !player_ishead(p)) {
+ pprintf(p, "You can't remove an admin with a level "
+ "higher than or equal to yourself.\n");
+ player_remove(p1);
+ return COM_OK;
+ }
+ }
+
+ player_remove(p1);
+
+ if (lookup) {
+ pprintf(p, "No player by the name %s is registered.\n", v_player);
+ return COM_OK;
+ }
+
+ if (player_find_bylogin(playerlower) >= 0) {
+ pprintf(p, "A player by that name is logged in.\n");
+ return COM_OK;
+ }
+
+ if (!player_kill(playerlower)) {
+ pprintf(p, "Player %s removed.\n", v_player);
+ UpdateRank(TYPE_BLITZ, NULL, NULL, v_player);
+ UpdateRank(TYPE_STAND, NULL, NULL, v_player);
+ UpdateRank(TYPE_WILD, NULL, NULL, v_player);
+ } else {
+ pprintf(p, "Remplayer failed.\n");
+ }
+ return COM_OK;
+}
+
+/*
+ * raisedead
+ *
+ * Usage: raisedead oldname [newname]
+ *
+ * Restores an account that has been previously removed using "remplayer".
+ * The zombie files from which it came are removed. Under most
+ * circumstances, you restore the account to the same handle it had
+ * before (oldname). However, in some circumstances you may need to
+ * restore the account to a different handle, in which case you include
+ * "newname" as the new handle. After "raisedead", you may need to use the
+ * "asetpasswd" command to get the player started again as a registered
+ * user, especially if the account had been locked
+ * by setting the password to *.
+ */
+PUBLIC int
+com_raisedead(int p, param_list param)
+{
+ char *v_player = param[0].val.word;
+ char newplayerlower[MAX_LOGIN_NAME] = { '\0' };
+ char playerlower[MAX_LOGIN_NAME] = { '\0' };
+ int p1, p2, lookup;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ mstrlcpy(playerlower, v_player, sizeof playerlower);
+ stolower(playerlower);
+
+ if (player_find_bylogin(playerlower) >= 0) {
+ pprintf(p, "A player by that name is logged in.\n");
+ pprintf(p, "Can't raise until they leave.\n");
+ return COM_OK;
+ }
+
+ p1 = player_new();
+ lookup = player_read(p1, playerlower);
+ player_remove(p1);
+
+ if (!lookup) {
+ pprintf(p, "A player by the name %s is already registered.\n",
+ v_player);
+ pprintf(p, "Obtain a new handle for the dead person.\n");
+ pprintf(p, "Then use raisedead [oldname] [newname].\n");
+ return COM_OK;
+ }
+
+ if (param[1].type == TYPE_NULL) {
+ if (!player_raise(playerlower)) {
+ pprintf(p, "Player %s raised from dead.\n", v_player);
+
+ p1 = player_new();
+
+ if (!player_read(p1, playerlower)) {
+ if (parray[p1].s_stats.rating > 0) {
+ UpdateRank(TYPE_STAND, v_player,
+ &parray[p1].s_stats, v_player);
+ }
+ if (parray[p1].b_stats.rating > 0) {
+ UpdateRank(TYPE_BLITZ, v_player,
+ &parray[p1].b_stats, v_player);
+ }
+ if (parray[p1].w_stats.rating > 0) {
+ UpdateRank(TYPE_WILD, v_player,
+ &parray[p1].w_stats, v_player);
+ }
+ }
+
+ player_remove(p1);
+ } else {
+ pprintf(p, "Raisedead failed.\n");
+ }
+
+ return COM_OK;
+ } else {
+ char *newplayer = param[1].val.word;
+
+ mstrlcpy(newplayerlower, newplayer, sizeof newplayerlower);
+ stolower(newplayerlower);
+
+ if (player_find_bylogin(newplayerlower) >= 0) {
+ pprintf(p, "A player by the requested name is "
+ "logged in.\n");
+ pprintf(p, "Can't reincarnate until they leave.\n");
+ return COM_OK;
+ }
+
+ p2 = player_new();
+ lookup = player_read(p2, newplayerlower);
+ player_remove(p2);
+
+ if (!lookup) {
+ pprintf(p, "A player by the name %s is already "
+ "registered.\n", v_player);
+ pprintf(p, "Obtain another new handle for the dead "
+ "person.\n");
+ return COM_OK;
+ }
+
+ if (!player_reincarn(playerlower, newplayerlower)) {
+ pprintf(p, "Player %s reincarnated to %s.\n", v_player,
+ newplayer);
+
+ p2 = player_new();
+
+ if (!player_read(p2, newplayerlower)) {
+ strfree(parray[p2].name);
+ parray[p2].name = xstrdup(newplayer);
+ player_save(p2);
+
+ if (parray[p2].s_stats.rating > 0) {
+ UpdateRank(TYPE_STAND, newplayer,
+ &parray[p2].s_stats, newplayer);
+ }
+ if (parray[p2].b_stats.rating > 0) {
+ UpdateRank(TYPE_BLITZ, newplayer,
+ &parray[p2].b_stats, newplayer);
+ }
+ if (parray[p2].w_stats.rating > 0) {
+ UpdateRank(TYPE_WILD, newplayer,
+ &parray[p2].w_stats, newplayer);
+ }
+ }
+
+ player_remove(p2);
+ } else {
+ pprintf(p, "Raisedead failed.\n");
+ }
+ }
+
+ return COM_OK;
+}
+
+/*
+ * addplayer
+ *
+ * Usage: addplayer playername emailaddress realname
+ *
+ * Adds a local player to the server with the handle of "playername". For
+ * example:
+ *
+ * addplayer Hawk u940456@daimi.aau.dk Henrik Gram
+ */
+PUBLIC int
+com_addplayer(int p, param_list param)
+{
+ char *newemail = param[1].val.word;
+ char *newname = param[2].val.string;
+ char *newplayer = param[0].val.word;
+ char newplayerlower[MAX_LOGIN_NAME];
+ char password[PASSLEN + 1];
+ char salt[FICS_SALT_SIZE];
+ char text[2048];
+ int i;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (strlen(newplayer) >= MAX_LOGIN_NAME) {
+ pprintf(p, "Player name is too long\n");
+ return COM_OK;
+ }
+
+ if (strlen(newplayer) < 3) {
+ pprintf(p, "Player name is too short\n");
+ return COM_OK;
+ }
+
+ if (!alphastring(newplayer)) {
+ pprintf(p, "Illegal characters in player name. "
+ "Only A-Za-z allowed.\n");
+ return COM_OK;
+ }
+
+ mstrlcpy(newplayerlower, newplayer, sizeof newplayerlower);
+ stolower(newplayerlower);
+
+ p1 = player_new();
+
+ if (!player_read(p1, newplayerlower)) {
+ pprintf(p, "A player by the name %s is already registered.\n",
+ newplayerlower);
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ parray[p1].name = xstrdup(newplayer);
+ parray[p1].login = xstrdup(newplayerlower);
+ parray[p1].fullName = xstrdup(newname);
+ parray[p1].emailAddress = xstrdup(newemail);
+
+ if (strcmp(newemail, "none")) {
+ for (i = 0; i < PASSLEN; i++)
+ password[i] = ('a' + arc4random_uniform(26));
+ password[i] = '\0';
+
+ mstrlcpy(salt, fics_getsalt(), sizeof salt);
+
+ parray[p1].passwd = xstrdup(crypt(password, salt));
+ } else {
+ password[0] = '\0';
+ parray[p1].passwd = xstrdup(password);
+ }
+
+ parray[p1].registered = 1;
+ parray[p1].rated = 1;
+
+ player_add_comment(p, p1, "Player added by addplayer.");
+ player_save(p1);
+ player_remove(p1);
+
+ pprintf(p, "Added: >%s< >%s< >%s< >%s<\n", newplayer, newname, newemail,
+ password);
+
+ if (strcmp(newemail, "none")) {
+ msnprintf(text, sizeof text,
+ "\nYour player account has been created.\n\n"
+ "Login Name: %s\n"
+ "Full Name: %s\n"
+ "Email Address: %s\n"
+ "Initial Password: %s\n\n"
+
+ "If any of this information is incorrect, please\n"
+ "contact the administrator to get it corrected.\n\n"
+
+ "You may change your password with the password\n"
+ "command on the server.\n\n"
+
+ "Please be advised that if this is an unauthorized\n"
+ "duplicate account for you, by using it you take\n"
+ "the risk of being banned from accessing this chess\n"
+ "server.\n\n"
+
+ "To connect to the server and use this account:\n\n"
+ "\ttelnet %s 5000\n\n"
+ "and enter your handle name and password.\n\n"
+
+ "Regards,\n\nThe FICS admins\n", newplayer, newname,
+ newemail, password, fics_hostname);
+
+ mail_string_to_address(newemail, "FICS Account Created", text);
+
+ if ((p1 = player_find_part_login(newplayer)) >= 0) {
+ pprintf_prompt(p1, "\n\nYou are now registered! "
+ "Confirmation together with\npassword is sent to "
+ "your email address.\n\n");
+ return COM_OK;
+ }
+
+ return COM_OK;
+ } else {
+ if ((p1 = player_find_part_login(newplayer)) >= 0) {
+ pprintf_prompt(p1, "\n\nYou are now registered! "
+ "You have NO password!\n\n");
+ return COM_OK;
+ }
+ }
+
+ return COM_OK;
+}
+
+PUBLIC int
+com_pose(int p, param_list param)
+{
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s is not logged in.\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only pose as players below your adminlevel."
+ "\n");
+ return COM_OK;
+ }
+
+ pprintf(p, "Command issued as %s\n", parray[p1].name);
+ pcommand(p1, "%s\n", param[1].val.string);
+ return COM_OK;
+}
+
+/*
+ * asetv
+ *
+ * Usage: asetv user instructions
+ *
+ * This command executes "set" instructions as if they had been made by the
+ * user indicated. For example, "asetv DAV shout 0" would set DAV's shout
+ * variable to 0.
+ */
+PUBLIC int
+com_asetv(int p, param_list param)
+{
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s is not logged in.\n", param[0].val.word);
+ return COM_OK;
+ }
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only aset players below your adminlevel."
+ "\n");
+ return COM_OK;
+ }
+
+ pprintf(p, "Command issued as %s\n", parray[p1].name);
+ pcommand(p1, "set %s\n", param[1].val.string);
+ return COM_OK;
+}
+
+/*
+ * announce
+ *
+ * Usage: announce message
+ *
+ * Broadcasts your message to all logged on users. Announcements reach all
+ * users and cannot be censored in any way (such as by "set shout 0").
+ */
+PUBLIC int
+com_announce(int p, param_list param)
+{
+ int count = 0;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!printablestring(param[0].val.string)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ count++;
+ pprintf_prompt(p1, "\n\n **ANNOUNCEMENT** from %s: %s\n\n",
+ parray[p].name, param[0].val.string);
+ }
+
+ pprintf(p, "\n(%d) **ANNOUNCEMENT** from %s: %s\n\n", count,
+ parray[p].name, param[0].val.string);
+ return COM_OK;
+}
+
+/*
+ * annunreg
+ *
+ * Usage: annunreg message
+ *
+ * Broadcasts your message to all logged on unregistered users, and admins,
+ * too. Announcements reach all unregistered users and admins and cannot be
+ * censored in any way (such as by "set shout 0").
+ */
+PUBLIC int
+com_annunreg(int p, param_list param)
+{
+ int count = 0;
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!printablestring(param[0].val.string)) {
+ pprintf(p, "Your message contains some unprintable "
+ "character(s).\n");
+ return COM_OK;
+ }
+
+ for (p1 = 0; p1 < p_num; p1++) {
+ if (p1 == p)
+ continue;
+ if (parray[p1].status != PLAYER_PROMPT)
+ continue;
+ if ((parray[p1].registered) &&
+ (parray[p1].adminLevel < ADMIN_ADMIN))
+ continue;
+ count++;
+ pprintf_prompt(p1, "\n\n **UNREG ANNOUNCEMENT** from %s: %s"
+ "\n\n", parray[p].name, param[0].val.string);
+ }
+
+ pprintf(p, "\n(%d) **UNREG ANNOUNCEMENT** from %s: %s\n\n", count,
+ parray[p].name, param[0].val.string);
+ return COM_OK;
+}
+
+PUBLIC int
+com_muzzle(int p, param_list param)
+{
+ pprintf(p, "Obsolete command: Please use +muzzle and -muzzle.\n");
+ return COM_OK;
+}
+
+PUBLIC int
+com_cmuzzle(int p, param_list param)
+{
+ pprintf(p, "Obsolete command: Please use +cmuzzle and -cmuzzle.\n");
+ return COM_OK;
+}
+
+/*
+ * asetpasswd
+ *
+ * Usage: asetpasswd player {password,*}
+ *
+ * This command sets the password of the player to the password given.
+ * If '*' is specified then the player's account is locked, and no password
+ * will work until a new one is set by asetpasswd.
+ *
+ * If the player is connected, he is told of the new password and the name
+ * of the admin who changed it, or likewise of his account status. An
+ * email message is mailed to the player's email address as well.
+ */
+PUBLIC int
+com_asetpasswd(int p, param_list param)
+{
+ char salt[FICS_SALT_SIZE];
+ char subject[400];
+ char text[10100];
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) && !player_ishead(p)) {
+ pprintf(p, "You can only set password for players below your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (!parray[p1].registered) {
+ pprintf(p, "You cannot set the password of an unregistered "
+ "player!\n");
+ return COM_OK;
+ }
+
+ if (parray[p1].passwd)
+ rfree(parray[p1].passwd);
+
+ if (param[1].val.word[0] == '*') {
+ parray[p1].passwd = xstrdup(param[1].val.word);
+ pprintf(p, "Account %s locked!\n", parray[p1].name);
+ msnprintf(text, sizeof text, "Password of %s is now useless. "
+ "Your account at our FICS has been locked.\n",
+ parray[p1].name);
+ } else {
+ mstrlcpy(salt, fics_getsalt(), sizeof salt);
+
+ parray[p1].passwd = xstrdup(crypt(param[1].val.word, salt));
+
+ msnprintf(text, sizeof text, "Password of %s changed to "
+ "\"%s\".\n",
+ parray[p1].name, param[1].val.word);
+ pprintf(p, "%s", text);
+ }
+
+ if (param[1].val.word[0] == '*') {
+ msnprintf(subject, sizeof subject, "FICS: %s has locked your "
+ "account.", parray[p].name);
+ if (connected)
+ pprintf_prompt(p1, "\n%s\n", subject);
+ } else {
+ msnprintf(subject, sizeof subject, "FICS: %s has changed your "
+ "password.", parray[p].name);
+ if (connected)
+ pprintf_prompt(p1, "\n%s\n", subject);
+ }
+
+ mail_string_to_address(parray[p1].emailAddress, subject, text);
+ player_save(p1);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetemail
+ *
+ * Usage: asetemail player [address]
+ *
+ * Sets the email address of the player to the address given. If the
+ * address is omited, then the player's email address is cleared. The
+ * person's email address is revealed to them when they use the "finger"
+ * command, but no other users -- except admins -- will have another
+ * player's email address displayed.
+ */
+PUBLIC int
+com_asetemail(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only set email addr for players below "
+ "your adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (parray[p1].emailAddress)
+ rfree(parray[p1].emailAddress);
+
+ if (param[1].type == TYPE_NULL) {
+ parray[p1].emailAddress = NULL;
+ pprintf(p, "Email address for %s removed\n", parray[p1].name);
+ } else {
+ parray[p1].emailAddress = xstrdup(param[1].val.word);
+ pprintf(p, "Email address of %s changed to \"%s\".\n",
+ parray[p1].name,
+ param[1].val.word);
+ }
+
+ player_save(p1);
+
+ if (connected) {
+ if (param[1].type == TYPE_NULL) {
+ pprintf_prompt(p1, "\n\n%s has removed your email "
+ "address.\n\n", parray[p].name);
+ } else {
+ pprintf_prompt(p1, "\n\n%s has changed your email "
+ "address.\n\n", parray[p].name);
+ }
+ } else {
+ player_remove(p1);
+ }
+
+ return COM_OK;
+}
+
+/*
+ * asetrealname
+ *
+ * Usage: asetrealname user newname
+ *
+ * This command sets the user's real name (as displayed to admins on finger
+ * notes) to "newname".
+ */
+PUBLIC int
+com_asetrealname(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only set real names for players below your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if (parray[p1].fullName)
+ rfree(parray[p1].fullName);
+
+ if (param[1].type == TYPE_NULL) {
+ parray[p1].fullName = NULL;
+ pprintf(p, "Real name for %s removed\n", parray[p1].name);
+ } else {
+ parray[p1].fullName = xstrdup(param[1].val.word);
+ pprintf(p, "Real name of %s changed to \"%s\".\n",
+ parray[p1].name,
+ param[1].val.word);
+ }
+
+ player_save(p1);
+
+ if (connected) {
+ if (param[1].type == TYPE_NULL) {
+ pprintf_prompt(p1, "\n\n%s has removed your real name."
+ "\n\n", parray[p].name);
+ } else {
+ pprintf_prompt(p1, "\n\n%s has changed your real name."
+ "\n\n", parray[p].name);
+ }
+ } else {
+ player_remove(p1);
+ }
+
+ return COM_OK;
+}
+
+/*
+ * asethandle
+ *
+ * Usage: asethandle oldname newname
+ *
+ * This command changes the handle of the player from oldname to
+ * newname. The various player information, messages, logins, comments
+ * and games should be automatically transferred to the new account.
+ */
+PUBLIC int
+com_asethandle(int p, param_list param)
+{
+ char *newplayer = param[1].val.word;
+ char *v_player = param[0].val.word;
+ char newplayerlower[MAX_LOGIN_NAME] = { '\0' };
+ char playerlower[MAX_LOGIN_NAME] = { '\0' };
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ mstrlcpy(playerlower, v_player, sizeof playerlower);
+ stolower(playerlower);
+
+ mstrlcpy(newplayerlower, newplayer, sizeof newplayerlower);
+ stolower(newplayerlower);
+
+ if (player_find_bylogin(playerlower) >= 0) {
+ pprintf(p, "A player by that name is logged in.\n");
+ return COM_OK;
+ }
+
+ if (player_find_bylogin(newplayerlower) >= 0) {
+ pprintf(p, "A player by that new name is logged in.\n");
+ return COM_OK;
+ }
+
+ p1 = player_new();
+
+ if (player_read(p1, playerlower)) {
+ pprintf(p, "No player by the name %s is registered.\n", v_player);
+ player_remove(p1);
+ return COM_OK;
+ } else {
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can't set handles for an admin with "
+ "a level higher than or equal to yourself.\n");
+ player_remove(p1);
+ return COM_OK;
+ }
+ }
+
+ player_remove(p1);
+ p1 = player_new();
+
+ if ((!player_read(p1, newplayerlower)) &&
+ (strcmp(playerlower, newplayerlower))) {
+ pprintf(p, "Sorry that handle is already taken.\n");
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ player_remove(p1);
+
+ if ((!player_rename(playerlower, newplayerlower)) &&
+ (!player_read(p1, newplayerlower))) {
+ pprintf(p, "Player %s renamed to %s.\n", v_player, newplayer);
+ strfree(parray[p1].name);
+ parray[p1].name = xstrdup(newplayer);
+ player_save(p1);
+
+ if (parray[p1].s_stats.rating > 0) {
+ UpdateRank(TYPE_STAND, newplayer, &parray[p1].s_stats,
+ v_player);
+ }
+ if (parray[p1].b_stats.rating > 0) {
+ UpdateRank(TYPE_BLITZ, newplayer, &parray[p1].b_stats,
+ v_player);
+ }
+ if (parray[p1].w_stats.rating > 0) {
+ UpdateRank(TYPE_WILD, newplayer, &parray[p1].w_stats,
+ v_player);
+ }
+ } else {
+ pprintf(p, "Asethandle failed.\n");
+ }
+
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetadmin
+ *
+ * Usage: asetadmin player AdminLevel
+ *
+ * Sets the admin level of the player with the following restrictions.
+ * 1. You can only set the admin level of players lower than yourself.
+ * 2. You can only set the admin level to a level that is lower than
+ * yourself.
+ */
+PUBLIC int
+com_asetadmin(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_GOD);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ if ((parray[p].adminLevel <= parray[p1].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can only set adminlevel for players below your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ if ((parray[p1].login) == (parray[p].login)) {
+ pprintf(p, "You can't change your own adminlevel.\n");
+ return COM_OK;
+ }
+
+ if ((param[1].val.integer >= parray[p].adminLevel) &&
+ !player_ishead(p)) {
+ pprintf(p, "You can't promote someone to or above your "
+ "adminlevel.\n");
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+ }
+
+ parray[p1].adminLevel = param[1].val.integer;
+ pprintf(p, "Admin level of %s set to %d.\n",
+ parray[p1].name,
+ parray[p1].adminLevel);
+ player_save(p1);
+
+ if (connected) {
+ pprintf_prompt(p1, "\n\n%s has set your admin level to %d.\n\n",
+ parray[p].name,
+ parray[p1].adminLevel);
+ } else {
+ player_remove(p1);
+ }
+
+ return COM_OK;
+}
+
+PRIVATE void
+SetRating(int p1, param_list param, statistics *s)
+{
+ s->rating = param[1].val.integer;
+
+ if (s->ltime == 0L)
+ s->sterr = 70.0;
+
+ if (param[2].type == TYPE_INT) {
+ s->win = param[2].val.integer;
+ if (param[3].type == TYPE_INT) {
+ s->los = param[3].val.integer;
+ if (param[4].type == TYPE_INT) {
+ s->dra = param[4].val.integer;
+ if (param[5].type == TYPE_INT) {
+ s->sterr = (double) param[5].val.integer;
+ }
+ }
+ }
+ }
+
+ s->num = s->win + s->los + s->dra;
+
+ if (s->num == 0) {
+ s->ltime = 0L;
+ } else {
+ s->ltime = time(NULL);
+ }
+}
+
+/*
+ * asetblitz
+ *
+ * Usage: asetblitz handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Blitz games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses,
+ * # of draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetblitz(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].b_stats);
+ player_save(p1);
+ UpdateRank(TYPE_BLITZ, parray[p1].name, &parray[p1].b_stats,
+ parray[p1].name);
+ pprintf(p, "Blitz rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetwild
+ *
+ * Usage: asetwild handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Wild games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses,
+ * # of draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetwild(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].w_stats);
+ player_save(p1);
+ UpdateRank(TYPE_WILD, parray[p1].name, &parray[p1].w_stats,
+ parray[p1].name);
+ pprintf(p, "Wild rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetstd
+ *
+ * Usage: asetstd handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Standard games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses, # of
+ * draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetstd(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].s_stats);
+ player_save(p1);
+ UpdateRank(TYPE_STAND, parray[p1].name, &parray[p1].s_stats,
+ parray[p1].name);
+ pprintf(p, "Standard rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * asetlight
+ *
+ * Usage: asetlight handle rating won lost drew RD
+ *
+ * This command allows admins to set a user's statistics for Lightning games.
+ * The parameters are self-explanatory: rating, # of wins, # of losses, # of
+ * draws, and ratings deviation.
+ */
+PUBLIC int
+com_asetlight(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ SetRating(p1, param, &parray[p1].l_stats);
+ player_save(p1);
+ pprintf(p, "Lightning rating for %s modified.\n", parray[p1].name);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * nuke
+ *
+ * Usage: nuke user
+ *
+ * This command disconnects the user from the server. The user is informed
+ * that she/he has been nuked by the admin named and a comment is
+ * automatically placed in the user's files (if she/he is a registered
+ * user, of course).
+ */
+PUBLIC int
+com_nuke(int p, param_list param)
+{
+ int p1, fd;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s isn't logged in.\n", param[0].val.word);
+ } else {
+ if ((parray[p].adminLevel > parray[p1].adminLevel) ||
+ player_ishead(p)) {
+ pprintf(p, "Nuking: %s\n", param[0].val.word);
+ pprintf(p, "Please leave a comment explaining why %s "
+ "was nuked.\n", parray[p1].name);
+ pprintf(p1, "\n\n**** You have been kicked out by %s! "
+ "****\n\n", parray[p].name);
+ pcommand(p, "addcomment %s Nuked\n", parray[p1].name);
+ fd = parray[p1].socket;
+ process_disconnection(fd);
+ net_close_connection(fd);
+ return COM_OK;
+ } else {
+ pprintf(p, "You need a higher adminlevel to nuke %s!\n",
+ param[0].val.word);
+ }
+ }
+ return COM_OK;
+}
+
+/*
+ * summon
+ *
+ * Usage: summon player
+ *
+ * This command gives a beep and a message to the player indicating that you
+ * want to talk with him/her. The command is useful for waking someone up,
+ * for example a sleepy admin or an ignorant player.
+ */
+PUBLIC int
+com_summon(int p, param_list param)
+{
+ int p1;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
+ pprintf(p, "%s isn't logged in.\n", param[0].val.word);
+ return COM_OK;
+ } else {
+ pprintf(p1, "\a\n");
+ pprintf_highlight(p1, "%s", parray[p].name);
+ pprintf_prompt(p1, " needs to talk with you. Use tell %s "
+ "<message> to reply.\a\n", parray[p].name);
+ pprintf(p, "Summoning sent to %s.\n", parray[p1].name);
+ return COM_OK;
+ }
+}
+
+/*
+ * addcomment
+ *
+ * Usage: addcomment user comment
+ *
+ * Places "comment" in the user's comments. If a user has comments, the
+ * number of comments is indicated to admins using the "finger" command.
+ * The comments themselves are displayed by the "showcomments" command.
+ */
+PUBLIC int
+com_addcomment(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+ if (player_add_comment(p, p1, param[1].val.string)) {
+ pprintf(p, "Error adding comment!\n");
+ } else {
+ pprintf(p, "Comment added for %s.\n", parray[p1].name);
+ player_save(p1);
+ }
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * showcomment
+ *
+ * Usage: showcomment user
+ *
+ * This command will display all of the comments added to the user's account.
+ */
+PUBLIC int
+com_showcomment(int p, param_list param)
+{
+ int p1, connected;
+
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+ ASSERT(param[0].type == TYPE_WORD);
+
+ if (!FindPlayer(p, param[0].val.word, &p1, &connected))
+ return COM_OK;
+
+ player_show_comments(p, p1);
+
+ if (!connected)
+ player_remove(p1);
+ return COM_OK;
+}
+
+/*
+ * admin
+ *
+ * Usage: admin
+ *
+ * This command toggles your admin symbol (*) on/off. This symbol appears
+ * in who listings.
+ */
+PUBLIC int
+com_admin(int p, param_list param)
+{
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ parray[p].i_admin = !(parray[p].i_admin);
+
+ if (parray[p].i_admin) {
+ pprintf(p, "Admin mode (*) is now shown\n");
+ } else {
+ pprintf(p, "Admin mode (*) is now not shown\n");
+ }
+
+ return COM_OK;
+}
+
+/*
+ * quota
+ *
+ * Usage: quota [n]
+ *
+ * The command sets the number of seconds (n) for the shout quota, which
+ * affects only those persons on the shout quota list. If no parameter
+ * (n) is given, the current setting is displayed.
+ */
+PUBLIC int
+com_quota(int p, param_list param)
+{
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (param[0].type == TYPE_NULL) {
+ pprintf(p, "The current shout quota is 2 shouts per %d "
+ "seconds.\n", quota_time);
+ return COM_OK;
+ }
+
+ quota_time = param[0].val.integer;
+
+ pprintf(p, "The shout quota is now 2 shouts per %d seconds.\n",
+ quota_time);
+ return COM_OK;
+}
+
+/*
+ * asetmaxplayer
+ *
+ * Usage: asetmaxplayer [n]
+ *
+ * The command sets the maximum number of players (n) who can connect.
+ */
+PUBLIC int
+com_asetmaxplayer(int p, param_list param)
+{
+ ASSERT(parray[p].adminLevel >= ADMIN_ADMIN);
+
+ if (param[0].type != TYPE_NULL) {
+ pprintf(p, "Previously %d total conenctions allowed...\n",
+ max_connections);
+ max_connections = param[0].val.integer;
+
+ if ((max_connections > MAX_PLAYER) ||
+ (max_connections > getdtablesize() - 4)) {
+ max_connections = MIN(MAX_PLAYER, getdtablesize() - 4);
+ pprintf(p, "Value too high. System OS limits us to "
+ "%d.\n", max_connections);
+ pprintf(p, "For saftey's sake, it should not be "
+ "higher than %d.\n", max_connections - 2);
+ }
+ }
+
+ pprintf(p, "There are currently %d regular and %d admin connections "
+ "available,\n", max_connections - 10, 10);
+ pprintf(p, "with %d maximum logins before unregistered login "
+ "restrictions begin.\n", MAX(max_connections - 50, 200));
+
+ pprintf(p, "Total allowed connections: %d.\n", max_connections);
+ return COM_OK;
+}