diff options
author | Markus Uhlin <markus@nifty-networks.net> | 2025-09-15 18:50:32 +0200 |
---|---|---|
committer | Markus Uhlin <markus@nifty-networks.net> | 2025-09-15 18:50:32 +0200 |
commit | c3eee8e333866d92e5fd94ae83cef618758c11bb (patch) | |
tree | 234a06fd90bd61a6668490a0cbf8870e6c674b81 /FICS/obsproc.c |
FICS RPBLC v1.4.61.4.6
Diffstat (limited to 'FICS/obsproc.c')
-rw-r--r-- | FICS/obsproc.c | 2052 |
1 files changed, 2052 insertions, 0 deletions
diff --git a/FICS/obsproc.c b/FICS/obsproc.c new file mode 100644 index 0000000..8c7194c --- /dev/null +++ b/FICS/obsproc.c @@ -0,0 +1,2052 @@ +/* obsproc.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 + Dave Herscovici 95/11/26 Created + Markus Uhlin 23/12/13 Fixed bugs + Markus Uhlin 23/12/13 Reformatted functions + Markus Uhlin 24/04/28 Completed reformatting + Markus Uhlin 24/04/28 Replaced unbounded string + handling functions. + Markus Uhlin 24/04/29 Fixed compiler warnings + Markus Uhlin 24/07/07 Fixed unhandled return values of + fscanf(). + Markus Uhlin 24/12/02 Improved old_mail_moves() + Markus Uhlin 25/01/18 Fixed -Wshadow + Markus Uhlin 25/03/15 Fixed possible buffer overflow + in FindHistory2(). + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. +*/ + +#include "stdinclude.h" +#include "common.h" + +#include <err.h> +#include <limits.h> +#include <stdlib.h> + +#include "command.h" +#include "comproc.h" +#include "config.h" +#include "eco.h" +#include "ficsmain.h" +#include "formula.h" +#include "gamedb.h" +#include "gameproc.h" +#include "matchproc.h" +#include "maxxes-utils.h" +#include "movecheck.h" +#include "network.h" +#include "obsproc.h" +#include "playerdb.h" +#include "ratings.h" +#include "rmalloc.h" +#include "utils.h" + +PUBLIC int +GameNumFromParam(int p, int *p1, parameter *param) +{ + if (param->type == TYPE_WORD) { + *p1 = player_find_part_login(param->val.word); + + if (*p1 < 0) { + pprintf(p, "No user named \"%s\" is logged in.\n", + param->val.word); + return -1; + } + + if (parray[*p1].game < 0) { + pprintf(p, "%s is not playing a game.\n", + parray[*p1].name); + } + + return parray[*p1].game; + } else { // Must be an integer + *p1 = -1; + + if (param->val.integer <= 0) { + pprintf(p, "%d is not a valid game number.\n", + param->val.integer); + } + + return (param->val.integer - 1); + } +} + +PRIVATE int +gamesortfunc(const void *i, const void *j) +{ + const int x = *(int *)i; + const int y = *(int *)j; + + /* + * examine mode games moved to top of "games" output + */ + return (GetRating(&parray[garray[x].white], + garray[x].type) + + GetRating(&parray[garray[x].black], + garray[x].type) - + (garray[x].status == GAME_EXAMINE ? 10000 : 0) - + GetRating(&parray[garray[y].white], + garray[y].type) - + GetRating(&parray[garray[y].black], + garray[y].type) + + (garray[y].status == GAME_EXAMINE ? 10000 : 0)); +} + +PUBLIC int +com_games(int p, param_list param) +{ + char *s = NULL; + int *sortedgames; // for qsort + int count = 0; + int i, j; + int selected = 0; + int slen = 0; + int totalcount; + int wp, bp; + int ws, bs; + + totalcount = game_count(); + + if (totalcount == 0) { + pprintf(p, "There are no games in progress.\n"); + } else { + // for qsort + sortedgames = reallocarray(NULL, totalcount, sizeof(int)); + + if (sortedgames == NULL) + err(1, "%s", __func__); + else + malloc_count++; + + if (param[0].type == TYPE_WORD) { + s = param[0].val.word; + slen = strlen(s); + + if ((selected = atoi(s)) < 0) + selected = 0; + } + + for (i = 0; i < g_num; i++) { + if (garray[i].status != GAME_ACTIVE && + garray[i].status != GAME_EXAMINE) + continue; + if ((selected) && (selected != i + 1)) + continue; // not selected game number + + wp = garray[i].white; + bp = garray[i].black; + + UNUSED_VAR(wp); + UNUSED_VAR(bp); + + if ((!selected) && + s && + strncasecmp(s, garray[i].white_name, slen) && + strncasecmp(s, garray[i].black_name, slen)) + continue; // player names did not match + sortedgames[count++] = i; + } /* for */ + + if (!count) { + pprintf(p, "No matching games were found " + "(of %d in progress).\n", totalcount); + } else { + qsort(sortedgames, count, sizeof(int), gamesortfunc); + pprintf(p, "\n"); + + for (j = 0; j < count; j++) { + i = sortedgames[j]; + + wp = garray[i].white; + bp = garray[i].black; + + board_calc_strength(&garray[i].game_state, + &ws, &bs); + + if (garray[i].status != GAME_EXAMINE) { + pprintf_noformat(p, "%2d %4s %-11.11s " + "%4s %-10.10s [%c%c%c%3d %3d] ", + (i + 1), + ratstrii(GetRating(&parray[wp], + garray[i].type), + parray[wp].registered), + parray[wp].name, + ratstrii(GetRating(&parray[bp], + garray[i].type), + parray[bp].registered), + parray[bp].name, + (garray[i].private ? 'p' : ' '), + *bstr[garray[i].type], + *rstr[garray[i].rated], + (garray[i].wInitTime / 600), + (garray[i].wIncrement / 10)); + + game_update_time(i); + + pprintf_noformat(p, "%6s -", + tenth_str((garray[i].wTime > 0 ? + garray[i].wTime : 0), 0)); + + pprintf_noformat(p, "%6s (%2d-%2d) " + "%c: %2d\n", + tenth_str((garray[i].bTime > 0 ? + garray[i].bTime : 0), 0), + ws, + bs, + (garray[i].game_state.onMove == + WHITE ? 'W' : 'B'), + garray[i].game_state.moveNum); + } else { + pprintf_noformat(p, "%2d " + "(Exam. %4d %-11.11s %4d %-10.10s) " + "[%c%c%c%3d %3d] ", + (i + 1), + garray[i].white_rating, + garray[i].white_name, + garray[i].black_rating, + garray[i].black_name, + (garray[i].private ? 'p' : ' '), + *bstr[garray[i].type], + *rstr[garray[i].rated], + (garray[i].wInitTime / 600), + (garray[i].wIncrement / 10)); + + pprintf_noformat(p, "%c: %2d\n", + (garray[i].game_state.onMove == + WHITE ? 'W' : 'B'), + garray[i].game_state.moveNum); + } + } /* for */ + + if (count < totalcount) { + pprintf(p, "\n %d game%s displayed " + "(of %d in progress).\n", + count, + (count == 1 ? "" : "s"), + totalcount); + } else { + pprintf(p, "\n %d game%s displayed.\n", + totalcount, (totalcount == 1 ? "" : "s")); + } + } /* else */ + + rfree(sortedgames); + } + + return COM_OK; +} + +PRIVATE int +do_observe(int p, int obgame) +{ + if (garray[obgame].private && + parray[p].adminLevel < ADMIN_ADMIN) { + pprintf(p, "Sorry, game %d is a private game.\n", (obgame + 1)); + return COM_OK; + } + + if (garray[obgame].white == p || + garray[obgame].black == p) { + if (garray[obgame].status != GAME_EXAMINE) { + pprintf(p, "You cannot observe a game that you are " + "playing.\n"); + return COM_OK; + } + } + + if (player_is_observe(p, obgame)) { + pprintf(p, "Removing game %d from observation list.\n", + (obgame + 1)); + player_remove_observe(p, obgame); + } else { + if (!player_add_observe(p, obgame)) { + pprintf(p, "You are now observing game %d.\n", + (obgame + 1)); + send_board_to(obgame, p); + } else { + pprintf(p, "You are already observing the maximum " + "number of games.\n"); + } + } + + return COM_OK; +} + +PUBLIC void +unobserveAll(int p) +{ + for (int i = 0; i < parray[p].num_observe; i++) { + pprintf(p, "Removing game %d from observation list.\n", + (parray[p].observe_list[i] + 1)); + } + + parray[p].num_observe = 0; +} + +PUBLIC int +com_unobserve(int p, param_list param) +{ + int gNum, p1; + + if (param[0].type == TYPE_NULL) { + unobserveAll(p); + return COM_OK; + } + + if ((gNum = GameNumFromParam(p, &p1, ¶m[0])) < 0) + return COM_OK; + + if (!player_is_observe(p, gNum)) { + pprintf(p, "You are not observing game %d.\n", gNum); + } else { + player_remove_observe(p, gNum); + pprintf(p, "Removing game %d from observation list.\n", + (gNum + 1)); + } + + return COM_OK; +} + +PUBLIC int +com_observe(int p, param_list param) +{ + int i; + int p1, obgame; + + if (parray[p].game >= 0 && garray[parray[p].game].status == + GAME_EXAMINE) { + pprintf(p, "You are still examining a game.\n"); + return COM_OK; + } + + if (param[0].type == TYPE_NULL) { + unobserveAll(p); + return COM_OK; + } + + if ((obgame = GameNumFromParam(p, &p1, ¶m[0])) < 0) + return COM_OK; + + if ((obgame >= g_num) || + ((garray[obgame].status != GAME_ACTIVE) && + (garray[obgame].status != GAME_EXAMINE))) { + pprintf(p, "There is no such game.\n"); + return COM_OK; + } + + if (p1 >= 0 && parray[p1].simul_info.numBoards) { + for (i = 0; i < parray[p1].simul_info.numBoards; i++) + if (parray[p1].simul_info.boards[i] >= 0) + do_observe(p, parray[p1].simul_info.boards[i]); + } else { + do_observe(p, obgame); + } + + return COM_OK; +} + +PUBLIC int +com_allobservers(int p, param_list param) +{ + int first; + int g; + int obgame; + int p1; + int start, end; + + if (param[0].type == TYPE_NULL) { + obgame = -1; + } else { + obgame = GameNumFromParam(p, &p1, ¶m[0]); + + if (obgame < 0) + return COM_OK; + } + + if (obgame == -1) { + start = 0; + end = g_num; + } else if ((obgame >= g_num) || + (garray[obgame].status != GAME_ACTIVE && + garray[obgame].status != GAME_EXAMINE)) { + pprintf(p, "There is no such game.\n"); + return COM_OK; + } else { + start = obgame; + end = obgame + 1; + } + + /* + * list games being played + */ + for (g = start; g < end; g++) { + if ((garray[g].status == GAME_ACTIVE) && + ((parray[p].adminLevel > 0) || + (garray[g].private == 0))) { + for (first = 1, p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_EMPTY && + player_is_observe(p1, g)) { + if (first) { + pprintf(p, "Observing %2d " + "[%s vs. %s]:", + (g + 1), + parray[garray[g].white].name, + parray[garray[g].black].name); + first = 0; + } + + pprintf(p, " %s%s", + (parray[p1].game >= 0 ? "#" : ""), + parray[p1].name); + } + } + + if (!first) + pprintf(p, "\n"); + } + } /* for */ + + /* + * list games being examined last + */ + for (g = start; g < end; g++) { + if ((garray[g].status == GAME_EXAMINE) && + ((parray[p].adminLevel > 0) || + (garray[g].private == 0))) { + for (first = 1, p1 = 0; p1 < p_num; p1++) { + if ((parray[p1].status != PLAYER_EMPTY) && + (player_is_observe(p1, g) || + (parray[p1].game == g))) { + if (first) { + if (strcmp(garray[g].white_name, + garray[g].black_name)) { + pprintf(p, "Examining " + "%2d [%s vs %s]:", + (g + 1), + garray[g].white_name, + garray[g].black_name); + } else { + pprintf(p, "Examining " + "%2d (scratch):", + (g + 1)); + } + + first = 0; + } + + pprintf(p, " %s%s", + (parray[p1].game == g ? "#" : ""), + parray[p1].name); + } + } + + if (!first) + pprintf(p, "\n"); + } + } /* for */ + + return COM_OK; +} + +PUBLIC int +com_unexamine(int p, param_list param) +{ + int g, p1, flag = 0; + + if (parray[p].game < 0 || garray[parray[p].game].status != + GAME_EXAMINE) { + pprintf(p, "You are not examining any games.\n"); + return COM_OK; + } + + g = parray[p].game; + parray[p].game = -1; + + for (p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_PROMPT) + continue; + + if (parray[p1].game == g && p != p1) { + /* + * ok - there are other examiners to take over + * the game. + */ + flag = 1; + } + + if (player_is_observe(p1, g) || parray[p1].game == g) { + pprintf(p1, "%s stopped examining game %d.\n", + parray[p].name, (g + 1)); + } + } + + if (!flag) { + for (p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_PROMPT) + continue; + if (player_is_observe(p1, g)) { + pprintf(p1, "There are no examiners.\n"); + pcommand(p1, "unobserve %d", (g + 1)); + } + } + + game_remove(g); + } + + pprintf(p, "You are no longer examining game %d.\n", (g + 1)); + return COM_OK; +} + +PUBLIC int +com_mexamine(int p, param_list param) +{ + int g, p1, p2; + + if (parray[p].game < 0 || garray[parray[p].game].status != + GAME_EXAMINE) { + pprintf(p, "You are not examining any games.\n"); + return COM_OK; + } + + if ((p1 = player_find_part_login(param[0].val.word)) < 0) { + pprintf(p, "No user named \"%s\" is logged in.\n", + param[0].val.word); + return COM_OK; + } + + g = parray[p].game; + + if (!player_is_observe(p1, g)) { + pprintf(p, "%s must observe the game you are analysing.\n", + parray[p1].name); + return COM_OK; + } else { + if (parray[p1].game >= 0) { + pprintf(p, "%s is already analysing the game.\n", + parray[p1].name); + return COM_OK; + } + + /* + * If we get here - let's make him examiner of the + * game. + */ + unobserveAll(p1); // Fix for Xboard + + player_decline_offers(p1, -1, PEND_MATCH); + player_withdraw_offers(p1, -1, PEND_MATCH); + player_withdraw_offers(p1, -1, PEND_SIMUL); + + parray[p1].game = g; + pprintf(p1, "You are now examiner of game %d.\n", (g + 1)); + send_board_to(g, p1); // Pos not changed - but fixes Xboard + + for (p2 = 0; p2 < p_num; p2++) { + if (parray[p2].status != PLAYER_PROMPT) + continue; + if (p2 == p1) + continue; + if (player_is_observe(p2, g) || parray[p2].game == g) { + pprintf_prompt(p2, "%s is now examiner of " + "game %d.\n", parray[p1].name, (g + 1)); + } + } + } + + return COM_OK; +} + +PUBLIC int +com_moves(int p, param_list param) +{ + int g; + int p1; + + if (param[0].type == TYPE_NULL) { + if (parray[p].game >= 0) { + g = parray[p].game; + } else if (parray[p].num_observe) { + for (g = 0; g < parray[p].num_observe; g++) { + pprintf(p, "%s\n", + movesToString(parray[p].observe_list[g], + 0)); + } + + return COM_OK; + } else { + pprintf(p, "You are neither playing, observing nor " + "examining a game.\n"); + return COM_OK; + } + } else { + g = GameNumFromParam(p, &p1, ¶m[0]); + + if (g < 0) + return COM_OK; + } + + if ((g < 0) || + (g >= g_num) || + ((garray[g].status != GAME_ACTIVE) && + (garray[g].status != GAME_EXAMINE))) { + pprintf(p, "There is no such game.\n"); + return COM_OK; + } + + if ((garray[g].white != p) && + (garray[g].black != p) && + (garray[g].private) && + (parray[p].adminLevel < ADMIN_ADMIN)) { + pprintf(p, "Sorry, that is a private game.\n"); + return COM_OK; + } + + pprintf(p, "%s\n", movesToString(g, 0)); // pgn may break interfaces? + return COM_OK; +} + +PUBLIC int +com_mailmoves(int p, param_list param) +{ + char subj[81] = { '\0' }; + int g; + int p1; + + if (!parray[p].registered) { + pprintf(p, "Unregistered players cannot use mailmoves.\n"); + return COM_OK; + } + + if (param[0].type == TYPE_NULL) { + if (parray[p].game >= 0) { + g = parray[p].game; + } else { + pprintf(p, "You are neither playing, observing nor " + "examining a game.\n"); + return COM_OK; + } + } else { + g = GameNumFromParam(p, &p1, ¶m[0]); + + if (g < 0) + return COM_OK; + } + + if ((g < 0) || + (g >= g_num) || + ((garray[g].status != GAME_ACTIVE) && + (garray[g].status != GAME_EXAMINE))) { + pprintf(p, "There is no such game.\n"); + return COM_OK; + } + + if ((garray[g].white != p) && + (garray[g].black != p) && + (garray[g].private) && + (parray[p].adminLevel < ADMIN_ADMIN)) { + pprintf(p, "Sorry, that is a private game.\n"); + return COM_OK; + } + + msnprintf(subj, sizeof subj, "FICS game report %s vs %s", + garray[g].white_name, + garray[g].black_name); + + if (mail_string_to_user(p, subj, movesToString(g, parray[p].pgn))) { + pprintf(p, "Moves NOT mailed, perhaps your address is " + "incorrect.\n"); + } else { + pprintf(p, "Moves mailed.\n"); + } + + return COM_OK; +} + +PRIVATE int +old_mail_moves(int p, int mail, param_list param) +{ + FILE *fp = NULL; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + char tmp[2048] = { '\0' }; + char *ptmp = tmp; + int count = 0; + int p1, connected; + + if (mail && (!parray[p].registered)) { + pprintf(p, "Unregistered players cannot use mailoldmoves.\n"); + return COM_OK; + } + + if (param[0].type == TYPE_WORD) { + if (!FindPlayer(p, param[0].val.word, &p1, &connected)) + return COM_OK; + } else { + p1 = p; + connected = 1; + } + + (void) snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", + stats_dir, parray[p1].login[0], parray[p1].login, STATS_GAMES); + fp = fopen(fname, "r"); // old moves now looks in history to save mem + // - DAV + + if (!fp) { + pprintf(p, "There is no old game for %s.\n", parray[p1].name); + + if (!connected) + player_remove(p1); + return COM_OK; + } + + while (fgets(tmp, sizeof tmp, fp) != NULL) { + /* null */; + } + + if (sscanf(ptmp, "%d", &count) != 1) { + warnx("%s: sscanf() error", __func__); + fclose(fp); + return COM_FAILED; + } + + fclose(fp); + pprintf(p, "Last game for %s was history game %d.\n", parray[p1].name, + count); + + if (mail) + pcommand(p, "mailstored %s %d", parray[p1].name, count); + else + pcommand(p, "smoves %s %d", parray[p1].name, count); + + if (!connected) + player_remove(p1); + return COM_OK; +} + +PUBLIC int +com_oldmoves(int p, param_list param) +{ + return old_mail_moves(p, 0, param); +} + +PUBLIC int +com_mailoldmoves(int p, param_list param) +{ + return old_mail_moves(p, 1, param); +} + +PUBLIC void +ExamineScratch(int p, param_list param) +{ + char *val; + char board[100]; + char category[100]; + char parsebuf[100]; + int confused = 0; + int g = game_new(); + + unobserveAll(p); + + player_decline_offers(p, -1, PEND_MATCH); + player_withdraw_offers(p, -1, PEND_MATCH); + player_withdraw_offers(p, -1, PEND_SIMUL); + + garray[g].bInitTime = garray[g].bIncrement = 0; + garray[g].wInitTime = garray[g].wIncrement = 0; + + garray[g].startTime = tenth_secs(); + garray[g].timeOfStart = tenth_secs(); + + garray[g].clockStopped = 0; + garray[g].lastDecTime = garray[g].startTime; + garray[g].lastMoveTime = garray[g].startTime; + garray[g].rated = 0; + garray[g].status = GAME_EXAMINE; + garray[g].totalHalfMoves = 0; + garray[g].type = TYPE_UNTIMED; + garray[g].wTime = garray[g].bTime = 0; + garray[g].white = garray[g].black = p; + parray[p].game = g; + parray[p].side = WHITE; + + board[0] = '\0'; + category[0] = '\0'; + + if (param[0].val.string != parray[p].name && + param[1].type == TYPE_WORD) { + mstrlcpy(category, param[0].val.string, sizeof category); + mstrlcpy(board, param[1].val.string, sizeof board); + } else if (param[1].type != TYPE_NULL) { + val = param[1].val.string; + + while (!confused && sscanf(val, " %99s", parsebuf) == 1) { + val = eatword(eatwhite(val)); + + if (category[0] != '\0' && board[0] == '\0') { + mstrlcpy(board, parsebuf, sizeof board); + } else if (isdigit(*parsebuf)) { + pprintf(p, "You can't specify time controls." + "\n"); + return; + } else if (category[0] == '\0') { + mstrlcpy(category, parsebuf, sizeof category); + } else { + confused = 1; + } + } + + if (confused) { + pprintf(p, "Can't interpret %s in match command.\n", + parsebuf); + return; + } + } + + if (category[0] && !board[0]) { + pprintf(p, "You must specify a board and a category.\n"); + return; + } + + pprintf(p, "Starting a game in examine (scratch) mode.\n"); + + if (category[0]) { + pprintf(p, "Loading from catagory: %s, board: %s.\n", + category, + board); + } + if (board_init(&garray[g].game_state, category, board)) { + pprintf(p, "PROBLEM LOADING BOARD. Game Aborted.\n"); + fprintf(stderr, "FICS: PROBLEM LOADING BOARD. Game Aborted.\n"); + return; + } + + garray[g].game_state.gameNum = g; + mstrlcpy(garray[g].white_name, parray[p].name, + sizeof(garray[g].white_name)); + mstrlcpy(garray[g].black_name, parray[p].name, + sizeof(garray[g].black_name)); + garray[g].white_rating = garray[g].black_rating = + parray[p].s_stats.rating; + send_boards(g); + MakeFENpos(g, (char *)garray[g].FENstartPos, + ARRAY_SIZE(garray[g].FENstartPos)); +} + +PRIVATE int +ExamineStored(FILE *fp, int p, char *filename) +{ + char board[100]; + char category[100]; + game *gg; + int g; + + unobserveAll(p); + + player_decline_offers(p, -1, PEND_MATCH); + player_withdraw_offers(p, -1, PEND_MATCH); + player_withdraw_offers(p, -1, PEND_SIMUL); + + g = game_new(); + gg = &garray[g]; + + board[0] = '\0'; + category[0] = '\0'; + + if (board_init(&gg->game_state, category, board)) { + pprintf(p, "PROBLEM LOADING BOARD. Game Aborted.\n"); + fprintf(stderr, "FICS: PROBLEM LOADING BOARD %s %s. " + "Game Aborted.\n", category, board); + return -1; + } + + gg->status = GAME_EXAMINE; + + if (ReadGameAttrs(fp, filename, g) < 0) { + pprintf(p, "Gamefile is corrupt; please notify an admin.\n"); + return -1; + } + + gg->startTime = tenth_secs(); + gg->lastDecTime = gg->startTime; + gg->lastMoveTime = gg->startTime; + + gg->totalHalfMoves = gg->numHalfMoves; + gg->numHalfMoves = 0; + gg->revertHalfMove = 0; + + gg->black = p; + gg->white = p; + gg->game_state.gameNum = g; + + parray[p].game = g; + parray[p].side = WHITE; + + send_boards(g); + MakeFENpos(g, (char *)garray[g].FENstartPos, + ARRAY_SIZE(garray[g].FENstartPos)); + + return g; +} + +PRIVATE void +ExamineAdjourned(int p, int p1, int p2) +{ + FILE *fp; + char *p1Login, *p2Login; + char filename[1024] = { '\0' }; + int g; + + p1Login = parray[p1].login; + p2Login = parray[p2].login; + + (void)snprintf(filename, sizeof filename, "%s/%c/%s-%s", + adj_dir, *p1Login, p1Login, p2Login); + fp = fopen(filename, "r"); + + if (!fp) { + (void)snprintf(filename, sizeof filename, "%s/%c/%s-%s", + adj_dir, *p2Login, p1Login, p2Login); + fp = fopen(filename, "r"); + + if (!fp) { + (void)snprintf(filename, sizeof filename, + "%s/%c/%s-%s", + adj_dir, *p2Login, p2Login, p1Login); + fp = fopen(filename, "r"); + + if (!fp) { + (void)snprintf(filename, sizeof filename, + "%s/%c/%s-%s", + adj_dir, *p1Login, p2Login, p1Login); + fp = fopen(filename, "r"); + + if (!fp) { + pprintf(p, "No stored game between " + "\"%s\" and \"%s\".\n", + parray[p1].name, + parray[p2].name); + return; + } + } + } + } + + g = ExamineStored(fp, p, filename); + fclose(fp); + + if (g >= 0) { + if (garray[g].white_name[0] == '\0') { + mstrlcpy(garray[g].white_name, p1Login, + sizeof(garray[g].white_name)); + } + if (garray[g].black_name[0] == '\0') { + mstrlcpy(garray[g].black_name, p2Login, + sizeof(garray[g].black_name)); + } + } +} + +PRIVATE char * +FindHistory(int p, int p1, int p_game) +{ + FILE *fpHist; + int index = 0; + long int when = 0; + static char fileName[MAX_FILENAME_SIZE]; + + msnprintf(fileName, sizeof fileName, "%s/player_data/%c/%s.%s", + stats_dir, parray[p1].login[0], parray[p1].login, STATS_GAMES); + + if ((fpHist = fopen(fileName, "r")) == NULL) { + pprintf(p, "No games in history for %s.\n", parray[p1].name); + return NULL; + } + + do { + int ret; + + ret = fscanf(fpHist, "%d %*c %*d %*c %*d %*s %*s %*d %*d %*d " + "%*d %*s %*s %ld", &index, &when); + if (ret != 2) { + warnx("%s: %s: corrupt", __func__, fileName); + fclose(fpHist); + return NULL; + } + } while (!feof(fpHist) && + !ferror(fpHist) && + index != p_game); + + if (feof(fpHist) || ferror(fpHist)) { + pprintf(p, "There is no history game %d for %s.\n", p_game, + parray[p1].name); + fclose(fpHist); + return NULL; + } + + fclose(fpHist); + + if (when < 0 || when >= LONG_MAX) { + pprintf(p, "Corrupt history data for %s (invalid timestamp).\n", + parray[p1].name); + return NULL; + } + + msnprintf(fileName, sizeof fileName, "%s/%ld/%ld", hist_dir, + (when % 100), when); + return (&fileName[0]); +} + +PRIVATE char * +FindHistory2(int p, int p1, int p_game, char *End, const size_t End_size) +{ + FILE *fpHist; + char fmt[80] = { '\0' }; + char *resolvedPath; + int index = 0; + long int when = 0; + static char fileName[MAX_FILENAME_SIZE]; + + msnprintf(fileName, sizeof fileName, "%s/player_data/%c/%s.%s", + stats_dir, parray[p1].login[0], parray[p1].login, STATS_GAMES); + + if ((fpHist = fopen(fileName, "r")) == NULL) { + pprintf(p, "No games in history for %s.\n", parray[p1].name); + return NULL; + } + + msnprintf(fmt, sizeof fmt, "%%d %%*c %%*d %%*c %%*d %%*s %%*s %%*d " + "%%*d %%*d %%*d %%*s %%%zus %%ld\n", (End_size - 1)); + + do { + if (fscanf(fpHist, fmt, &index, End, &when) != 3) { + warnx("%s: %s: corrupt", __func__, fileName); + fclose(fpHist); + return NULL; + } + } while (!feof(fpHist) && + !ferror(fpHist) && + index != p_game); + + if (feof(fpHist) || ferror(fpHist)) { + pprintf(p, "There is no history game %d for %s.\n", p_game, + parray[p1].name); + fclose(fpHist); + return NULL; + } + + fclose(fpHist); + + if (when < 0 || when >= LONG_MAX) { + pprintf(p, "Invalid history timestamp for %s.\n", + parray[p1].name); + return NULL; + } + + msnprintf(fileName, sizeof fileName, "%s/%ld/%ld", hist_dir, + (when % 100), when); + + // Validate that the resolved path is within hist_dir + if ((resolvedPath = realpath(fileName, NULL)) == NULL) { + warn("%s: realpath", __func__); + return NULL; + } + + if (strncmp(resolvedPath, hist_dir, strlen(hist_dir)) != 0) { + warnx("%s: path traversal detected", __func__); + free(resolvedPath); + return NULL; + } + + // Copy 'resolvedPath' back to 'fileName' for return + mstrlcpy(fileName, resolvedPath, sizeof fileName); + free(resolvedPath); + + return (&fileName[0]); +} + +PRIVATE void +ExamineHistory(int p, int p1, int p_game) +{ + FILE *fpGame; + char *fileName; + + if ((fileName = FindHistory(p, p1, p_game)) != NULL) { + if ((fpGame = fopen(fileName, "r")) == NULL) { + pprintf(p, "History game %d not available for %s.\n", + p_game, parray[p1].name); + } else { + ExamineStored(fpGame, p, fileName); + fclose(fpGame); + } + } +} + +PRIVATE void +ExamineJournal(int p, int p1, char slot) +{ + FILE *fpGame; + char *name_from = parray[p1].login; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + + if ((parray[p1].jprivate) && + (p != p1) && + (parray[p].adminLevel < ADMIN_ADMIN)) { + pprintf(p, "Sorry, this journal is private.\n"); + return; + } + + if (((slot - 'a' - 1) > MAX_JOURNAL) && + (parray[p1].adminLevel < ADMIN_ADMIN) && + (!titled_player(p, parray[p1].login))) { + pprintf(p, "%s's maximum journal entry is %c\n", + parray[p1].name, + toupper((char)(MAX_JOURNAL + 'A' - 1))); + return; + } + + msnprintf(fname, sizeof fname, "%s/%c/%s.%c", journal_dir, name_from[0], + name_from, slot); + + if ((fpGame = fopen(fname, "r")) == NULL) { + pprintf(p, "Journal entry %c is not available for %s.\n", + toupper(slot), + parray[p1].name); + } else { + ExamineStored(fpGame, p, fname); + fclose(fpGame); + } +} + +PUBLIC int +com_examine(int p, param_list param) +{ + char *param2string; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + int p1, p2 = p, p1conn, p2conn = 1; + + if (parray[p].game >= 0 && garray[parray[p].game].status == + GAME_EXAMINE) { + pprintf(p, "You are already examining a game.\n"); + } else if (parray[p].game >= 0) { + pprintf(p, "You are playing a game.\n"); + } else if (param[0].type == TYPE_NULL) { + ExamineScratch(p, param); + } else if (param[0].type == TYPE_WORD) { + if (param[1].type == TYPE_WORD) { + msnprintf(fname, sizeof fname, "%s/%s/%s", board_dir, + param[0].val.word, param[1].val.word); + + if (file_exists(fname)) { + ExamineScratch(p, param); + return COM_OK; + } + } + + if (!FindPlayer(p, param[0].val.word, &p1, &p1conn)) + return COM_OK; + + if (param[1].type == TYPE_INT) + ExamineHistory(p, p1, param[1].val.integer); + else { + if (param[1].type == TYPE_WORD) { + /* + * Lets check the journal + */ + param2string = param[1].val.word; + + if (strlen(param2string) == 1 && + isalpha(param2string[0])) { + ExamineJournal(p, p1, param2string[0]); + + if (!p1conn) + player_remove(p1); + return COM_OK; + } else { + if (!FindPlayer(p, param[1].val.word, + &p2, &p2conn)) { + if (!p1conn) + player_remove(p1); + return COM_OK; + } + } + } + + ExamineAdjourned(p, p1, p2); + + if (!p2conn) + player_remove(p2); + } + + if (!p1conn) + player_remove(p1); + } + + return COM_OK; +} + +PUBLIC int +com_stored(int p, param_list param) +{ + DIR *dirp; + char dname[MAX_FILENAME_SIZE]; + int p1, connected; +#ifdef USE_DIRENT + struct dirent *dp; +#else + struct direct *dp; +#endif + + if (param[0].type == TYPE_WORD) { + if (!FindPlayer(p, param[0].val.word, &p1, &connected)) + return COM_OK; + } else { + p1 = p; + connected = 1; + } + + msnprintf(dname, sizeof dname, "%s/%c", adj_dir, parray[p1].login[0]); + dirp = opendir(dname); + + if (!dirp) { + pprintf(p, "Player %s has no games stored.\n", parray[p1].name); + + if (!connected) + player_remove(p1); + return COM_OK; + } + + pprintf(p, "Stored games for %s:\n", parray[p1].name); + + for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { + if (file_has_pname(dp->d_name, parray[p1].login)) { + pprintf(p, " %s vs. %s\n", + file_wplayer(dp->d_name), + file_bplayer(dp->d_name)); + } + } + + closedir(dirp); + pprintf(p, "\n"); + + if (!connected) + player_remove(p1); + return COM_OK; +} + +PRIVATE void +stored_mail_moves(int p, int mail, param_list param) +{ + FILE *fpGame; + char *fileName; + char *name_from; + char *param2string = NULL; + char fileName2[MAX_FILENAME_SIZE]; + int g = -1; + int wp, wconnected, bp, bconnected, gotit = 0; + + if (mail && !parray[p].registered) { + pprintf(p, "Unregistered players cannot use mailstored.\n"); + return; + } + + if (!FindPlayer(p, param[0].val.word, &wp, &wconnected)) + return; + + if (param[1].type == TYPE_INT) { /* look for a game from history */ + if ((fileName = FindHistory(p, wp, param[1].val.integer)) != + NULL) { + if ((fpGame = fopen(fileName, "r")) == NULL) { + pprintf(p, "History game %d not available " + "for %s.\n", + param[1].val.integer, + parray[wp].name); + } else { + g = game_new(); + + if (ReadGameAttrs(fpGame, fileName, g) < 0) + pprintf(p, "Gamefile is corrupt; " + "please notify an admin.\n"); + else + gotit = 1; + + fclose(fpGame); + } + } + } else { + /* + * Let's test for journal + */ + name_from = param[0].val.word; + param2string = param[1].val.word; + + if (strlen(param2string) == 1 && isalpha(param2string[0])) { + if (parray[wp].jprivate && + parray[p].adminLevel < ADMIN_ADMIN && + p != wp) { + pprintf(p, "Sorry, the journal from which you " + "are trying to fetch is private.\n"); + } else { + if ((param2string[0] - 'a' - 1) > MAX_JOURNAL && + parray[wp].adminLevel < ADMIN_ADMIN && + !titled_player(p, parray[wp].login)) { + pprintf(p, "%s's maximum journal entry " + "is %c\n", + parray[wp].name, + toupper((char)(MAX_JOURNAL + + 'A' - 1))); + } else { + msnprintf(fileName2, sizeof fileName2, + "%s/%c/%s.%c", + journal_dir, + name_from[0], + name_from, + param2string[0]); + + if ((fpGame = fopen(fileName2, "r")) == + NULL) { + pprintf(p, "Journal entry %c " + "is not available for %s.\n", + toupper(param2string[0]), + parray[wp].name); + } else { + g = game_new(); + + /* XXX: was 'fileName' */ + if (ReadGameAttrs(fpGame, + fileName2, g) < 0) + pprintf(p, "Journal " + "entry is corrupt; " + "please notify an " + "admin.\n"); + else + gotit = 1; + + fclose(fpGame); + } + } + } + } else { + /* + * look for a stored game between the players + */ + + if (FindPlayer(p, param[1].val.word, &bp, + &bconnected)) { + g = game_new(); + + if (game_read(g, wp, bp) >= 0) { + gotit = 1; + } else if (game_read(g, bp, wp) >= 0) { + gotit = 1; + } else { + pprintf(p, "There is no stored game " + "%s vs. %s\n", + parray[wp].name, + parray[bp].name); + } + + if (!bconnected) + player_remove(bp); + } + } + } + + if (gotit) { + if (strcasecmp(parray[p].name, garray[g].white_name) && + strcasecmp(parray[p].name, garray[g].black_name) && + garray[g].private && + parray[p].adminLevel < ADMIN_ADMIN) { + pprintf(p, "Sorry, that is a private game.\n"); + } else { + if (mail == 1) { /* Do mailstored */ + char subj[81]; + + if (param[1].type == TYPE_INT) { + msnprintf(subj, sizeof subj, "FICS " + "history game: %s %d", + parray[wp].name, + param[1].val.integer); + } else { + if (param2string == NULL) /* XXX */ + errx(1, "%s: param2string == " + "NULL", __func__); + if (strlen(param2string) == 1 && + isalpha(param2string[0])) { + msnprintf(subj, sizeof subj, + "FICS journal " + "game %s vs %s", + garray[g].white_name, + garray[g].black_name); + } else { + msnprintf(subj, sizeof subj, + "FICS adjourned " + "game %s vs %s", + garray[g].white_name, + garray[g].black_name); + } + } + if (mail_string_to_user(p, subj, movesToString + (g, parray[p].pgn))) + pprintf(p, "Moves NOT mailed, perhaps " + "your address is incorrect.\n"); + else + pprintf(p, "Moves mailed.\n"); + } else { + pprintf(p, "%s\n", movesToString(g, 0)); + } /* Do smoves */ + } + } + + if (!wconnected) + player_remove(wp); + if (g != -1) + game_remove(g); +} + +PUBLIC int +com_mailstored(int p, param_list param) +{ + stored_mail_moves(p, 1, param); + return COM_OK; +} + +PUBLIC int +com_smoves(int p, param_list param) +{ + stored_mail_moves(p, 0, param); + return COM_OK; +} + +PUBLIC int +com_sposition(int p, param_list param) +{ + int g; + int wp, wconnected, bp, bconnected, confused = 0; + + 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); + } + + g = game_new(); + + if (game_read(g, wp, bp) < 0) { // if no game white-black, + if (game_read(g, bp, wp) < 0) { // look for black-white + confused = 1; + + pprintf(p, "There is no stored game %s vs. %s\n", + parray[wp].name, + parray[bp].name); + } else { + int tmp; + + tmp = wp; + wp = bp; + bp = tmp; + tmp = wconnected; + wconnected = bconnected; + bconnected = tmp; + } + } + + if (!confused) { + if ((wp != p) && + (bp != p) && + (garray[g].private) && + (parray[p].adminLevel < ADMIN_ADMIN)) { + pprintf(p, "Sorry, that is a private game.\n"); + } else { + garray[g].white = wp; + garray[g].black = bp; + garray[g].startTime = tenth_secs(); + garray[g].lastMoveTime = garray[g].startTime; + garray[g].lastDecTime = garray[g].startTime; + + pprintf(p, "Position of stored game %s vs. %s\n", + parray[wp].name, + parray[bp].name); + + send_board_to(g, p); + } + } + + game_remove(g); + + if (!wconnected) + player_remove(wp); + if (!bconnected) + player_remove(bp); + return COM_OK; +} + +PUBLIC int +com_forward(int p, param_list param) +{ + int g, i; + int nHalfMoves = 1; + int p1; + unsigned int now; + + if (!(parray[p].game >= 0 && garray[parray[p].game].status == + GAME_EXAMINE)) { + pprintf(p, "You are not examining any games.\n"); + return COM_OK; + } + + g = parray[p].game; + + if (!strcmp(garray[g].white_name, garray[g].black_name)) { + pprintf(p, "You cannot go forward; no moves are stored.\n"); + return COM_OK; + } + + if (param[0].type == TYPE_INT) + nHalfMoves = param[0].val.integer; + + if (garray[g].numHalfMoves > garray[g].revertHalfMove) { + pprintf(p, "No more moves.\n"); + return COM_OK; + } + + if (garray[g].numHalfMoves < garray[g].totalHalfMoves) { + for (p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_PROMPT) + continue; + if (player_is_observe(p1, g) || parray[p1].game == g) { + pprintf(p1, "%s goes forward %d move%s.\n", + parray[p].name, + nHalfMoves, + (nHalfMoves == 1 ? "" : "s")); + } + } + } + + for (i = 0; i < nHalfMoves; i++) { + if (garray[g].numHalfMoves < garray[g].totalHalfMoves) { + execute_move(&garray[g].game_state, + &garray[g].moveList[garray[g].numHalfMoves], 1); + + if (garray[g].numHalfMoves + 1 > + garray[g].examMoveListSize) { + // Allocate 20 moves at a time. + garray[g].examMoveListSize += 20; + + if (!garray[g].examMoveList) { + garray[g].examMoveList = + reallocarray(NULL, + sizeof(move_t), + garray[g].examMoveListSize); + if (garray[g].examMoveList == NULL) + err(1, "%s", __func__); + malloc_count++; + } else { + garray[g].examMoveList = + reallocarray(garray[g].examMoveList, + sizeof(move_t), + garray[g].examMoveListSize); + if (garray[g].examMoveList == NULL) + err(1, "%s", __func__); + } + } + + garray[g].examMoveList[garray[g].numHalfMoves] = + garray[g].moveList[garray[g].numHalfMoves]; + garray[g].revertHalfMove++; + garray[g].numHalfMoves++; + } else { + for (p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_PROMPT) + continue; + if (player_is_observe(p1, g) || + parray[p1].game == g) + pprintf(p1, "End of game.\n"); + } + + break; + } + } + + // roll back time + if (garray[g].game_state.onMove == WHITE) { + garray[g].wTime += (garray[g].lastDecTime - + garray[g].lastMoveTime); + } else { + garray[g].bTime += (garray[g].lastDecTime - + garray[g].lastMoveTime); + } + + now = tenth_secs(); + + if (garray[g].numHalfMoves == 0) + garray[g].timeOfStart = now; + garray[g].lastMoveTime = now; + garray[g].lastDecTime = now; + + send_boards(g); + return COM_OK; +} + +PUBLIC int +com_backward(int p, param_list param) +{ + int g, i; + int nHalfMoves = 1; + int p1; + unsigned int now; + + if (!(parray[p].game >= 0 && garray[parray[p].game].status == + GAME_EXAMINE)) { + pprintf(p, "You are not examining any games.\n"); + return COM_OK; + } + + g = parray[p].game; + + if (param[0].type == TYPE_INT) + nHalfMoves = param[0].val.integer; + + if (garray[g].numHalfMoves != 0) { + for (p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_PROMPT) + continue; + if (player_is_observe(p1, g) || parray[p1].game == g) { + pprintf(p1, "%s backs up %d move%s.\n", + parray[p].name, + nHalfMoves, + (nHalfMoves == 1 ? "" : "s")); + } + } + } + + for (i = 0; i < nHalfMoves; i++) { + if (backup_move(g, REL_EXAMINE) != MOVE_OK) { + for (p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_PROMPT) + continue; + if (player_is_observe(p1, g) || + parray[p1].game == g) + pprintf(p1, "Beginning of game.\n"); + } + + break; + } + } + + if (garray[g].numHalfMoves < garray[g].revertHalfMove) + garray[g].revertHalfMove = garray[g].numHalfMoves; + + // roll back time + if (garray[g].game_state.onMove == WHITE) { + garray[g].wTime += (garray[g].lastDecTime - + garray[g].lastMoveTime); + } else { + garray[g].bTime += (garray[g].lastDecTime - + garray[g].lastMoveTime); + } + + now = tenth_secs(); + + if (garray[g].numHalfMoves == 0) + garray[g].timeOfStart = now; + garray[g].lastMoveTime = now; + garray[g].lastDecTime = now; + + send_boards(g); + return COM_OK; +} + +PUBLIC int +com_revert(int p, param_list param) +{ + int g, i; + int nHalfMoves = 1; + int p1; + unsigned int now; + + if (!(parray[p].game >= 0 && garray[parray[p].game].status == + GAME_EXAMINE)) { + pprintf(p, "You are not examining any games.\n"); + return COM_OK; + } + + g = parray[p].game; + nHalfMoves = garray[g].numHalfMoves - garray[g].revertHalfMove; + + if (nHalfMoves == 0) { + pprintf(p, "Already at mainline.\n"); + return COM_OK; + } + + if (nHalfMoves < 0) { // eek - should NEVER happen! + fprintf(stderr, "OUCH! in %s: nHalfMoves < 0\n", __func__); + return COM_OK; + } + + for (p1 = 0; p1 < p_num; p1++) { + if (parray[p1].status != PLAYER_PROMPT) + continue; + if (player_is_observe(p1, g) || parray[p1].game == g) { + pprintf(p1, "%s reverts to mainline.\n", + parray[p].name); + } + } + + for (i = 0; i < nHalfMoves; i++) + backup_move(g, REL_EXAMINE); + + // roll back time + if (garray[g].game_state.onMove == WHITE) { + garray[g].wTime += (garray[g].lastDecTime - + garray[g].lastMoveTime); + } else { + garray[g].bTime += (garray[g].lastDecTime - + garray[g].lastMoveTime); + } + + now = tenth_secs(); + + if (garray[g].numHalfMoves == 0) + garray[g].timeOfStart = now; + garray[g].lastMoveTime = now; + garray[g].lastDecTime = now; + + send_boards(g); + return COM_OK; +} + +PUBLIC int +com_history(int p, param_list param) +{ + char fname[MAX_FILENAME_SIZE] = { '\0' }; + 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; + } + + msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir, + parray[p1].login[0], parray[p1].login, STATS_GAMES); + pgames(p, p1, fname); + + if (!connected) + player_remove(p1); + return COM_OK; +} + +PUBLIC int +com_journal(int p, param_list param) +{ + char fname[MAX_FILENAME_SIZE]; + 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; + } + + if (!parray[p1].registered) { + pprintf(p, "Only registered players may keep a journal.\n"); + + if (!connected) + player_remove(p1); + return COM_OK; + } + + if (parray[p1].jprivate && + p != p1 && + parray[p].adminLevel < ADMIN_ADMIN) { + pprintf(p, "Sorry, this journal is private.\n"); + + if (!connected) + player_remove(p1); + return COM_OK; + } + + msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir, + parray[p1].login[0], parray[p1].login, STATS_JOURNAL); + pjournal(p, p1, fname); + + if (!connected) + player_remove(p1); + return COM_OK; +} + +PRIVATE void +jsave_journalentry(int p, char save_spot, int p1, char from_spot, char *to_file) +{ + FILE *Game; + char *name_from = parray[p1].login; + char *name_to = parray[p].login; + char command[MAX_FILENAME_SIZE * 2 + 3]; + char fname[MAX_FILENAME_SIZE]; + char fname2[MAX_FILENAME_SIZE]; + struct JGI_context ctx; + + msnprintf(fname, sizeof fname, "%s/%c/%s.%c", journal_dir, name_from[0], + name_from, from_spot); + + if ((Game = fopen(fname, "r")) == NULL) { + pprintf(p, "Journal entry %c not available for %s.\n", + toupper(from_spot), + parray[p1].name); + return; + } + + fclose(Game); + + msnprintf(fname2, sizeof fname2, "%s/%c/%s.%c", journal_dir, name_to[0], + name_to, save_spot); + unlink(fname2); + + msnprintf(command, sizeof command, "cp %s %s", fname, fname2); + if (system(command)) { // XXX + pprintf(p, "System command in jsave_journalentry failed!\n"); + pprintf(p, "Please report this to an admin.\n"); + fprintf(stderr, "FICS: System command failed in " + "jsave_journalentry\n"); + return; + } + + msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir, + name_to[0], name_to, STATS_JOURNAL); + + /* + * Init context + */ + ctx.p = p; + ctx.from_spot = from_spot; + ctx.WhiteRating = 0; + ctx.BlackRating = 0; + ctx.t = 0; + ctx.i = 0; + memset(ctx.WhiteName, 0, sizeof(ctx.WhiteName)); + memset(ctx.BlackName, 0, sizeof(ctx.BlackName)); + memset(ctx.type, 0, sizeof(ctx.type)); + memset(ctx.eco, 0, sizeof(ctx.eco)); + memset(ctx.ending, 0, sizeof(ctx.ending)); + memset(ctx.result, 0, sizeof(ctx.result)); + + if (!journal_get_info(&ctx, fname)) + return; + + addjournalitem(p, toupper(save_spot), + ctx.WhiteName, ctx.WhiteRating, + ctx.BlackName, ctx.BlackRating, + ctx.type, + ctx.t, ctx.i, + ctx.eco, + ctx.ending, + ctx.result, + to_file); + pprintf(p, "Journal entry %s %c saved in slot %c in journal.\n", + parray[p1].name, toupper(from_spot), toupper(save_spot)); +} + +PUBLIC void +jsave_history(int p, char save_spot, int p1, int from, char *to_file) +{ + FILE *Game; + char *EndSymbol; + char *HistoryFname; + char *name_to = parray[p].login; + char End[100] = { '\0' }; + char command[MAX_FILENAME_SIZE * 2 + 3] = { '\0' }; + char filename[MAX_FILENAME_SIZE + 1] = { '\0' }; // XXX + char jfname[MAX_FILENAME_SIZE] = { '\0' }; + char type[4]; + int g; + + if ((HistoryFname = FindHistory2(p, p1, from, End, sizeof End)) != + NULL) { + if ((Game = fopen(HistoryFname, "r")) == NULL) { + pprintf(p, "History game %d not available for %s.\n", + from, + parray[p1].name); + } else { + msnprintf(jfname, sizeof jfname, "%s/%c/%s.%c", + journal_dir, + name_to[0], + name_to, + save_spot); + unlink(jfname); + + msnprintf(command, sizeof command, "cp %s %s", + HistoryFname, jfname); + if (system(command)) { // XXX: A little messy, but works + pprintf(p, "System command in jsave_history " + "failed!\n"); + pprintf(p, "Please report this to an admin.\n"); + fprintf(stderr, "FICS: System command failed " + "in jsave_journalentry\n"); + fclose(Game); + return; + } + + g = game_new(); // Open a dummy game + + // XXX: is 'filename' right here? + if (ReadGameAttrs(Game, filename, g) < 0) { + pprintf(p, "Gamefile is corrupt. Please tell " + "an admin.\n"); + game_free(g); + fclose(Game); + return; + } + + fclose(Game); + + if (garray[g].private) { + type[0] = 'p'; + } else { + type[0] = ' '; + } + + if (garray[g].type == TYPE_BLITZ) { + type[1] = 'b'; + } else if (garray[g].type == TYPE_WILD) { + type[1] = 'w'; + } else if (garray[g].type == TYPE_STAND) { + type[1] = 's'; + } else { + if (garray[g].type == TYPE_NONSTANDARD) + type[1] = 'n'; + else + type[1] = 'u'; + } + + if (garray[g].rated) { + type[2] = 'r'; + } else { + type[2] = 'u'; + } + + type[3] = '\0'; + EndSymbol = EndSym(g); + addjournalitem(p, toupper(save_spot), + garray[g].white_name, garray[g].white_rating, + garray[g].black_name, garray[g].black_rating, + type, + garray[g].wInitTime, + garray[g].wIncrement, + getECO(g), End, EndSymbol, to_file); + game_free(g); + pprintf(p, "Game %s %d saved in slot %c in journal.\n", + parray[p1].name, from, toupper(save_spot)); + } + } +} + +PUBLIC int +com_jsave(int p, param_list param) +{ + char *from; + char *to = param[0].val.word; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + int p1, p1conn; + + if (!parray[p].registered) { + pprintf(p, "Only registered players may keep a journal.\n"); + return COM_OK; + } + + if (strlen(to) != 1 || !(isalpha(to[0]))) { + pprintf(p, "Journal entries are referenced by single " + "letters.\n"); + return COM_OK; + } + + if ((to[0] - 'a' - 1) > MAX_JOURNAL && + parray[p].adminLevel < ADMIN_ADMIN && + !titled_player(p, parray[p].login)) { + pprintf(p, "Your maximum journal entry is %c\n", + toupper((char)(MAX_JOURNAL + 'A' - 1))); + return COM_OK; + } + + if (!FindPlayer(p, param[1].val.word, &p1, &p1conn)) + return COM_OK; + + if (param[2].type == TYPE_INT) { + // grab from a history + msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", + stats_dir, parray[p].login[0], parray[p].login, + STATS_JOURNAL); + jsave_history(p, to[0], p1, param[2].val.integer, fname); + } else { + from = param[2].val.word; + + if (strlen(from) != 1 || !(isalpha(from[0]))) { + pprintf(p, "Journal entries are referenced by single " + "letters.\n"); + if (!p1conn) + player_remove(p1); + return COM_OK; + } + + if (parray[p1].jprivate && + parray[p].adminLevel < ADMIN_ADMIN && + p != p1) { + pprintf(p, "Sorry, the journal from which you are " + "trying to fetch is private.\n"); + if (!p1conn) + player_remove(p1); + return COM_OK; + } + + if ((to[0] - 'a' - 1) > MAX_JOURNAL && + parray[p1].adminLevel < ADMIN_ADMIN && + !titled_player(p, parray[p1].login)) { + pprintf(p, "%s's maximum journal entry is %c\n", + parray[p1].name, + toupper((char)(MAX_JOURNAL + 'A' - 1))); + + if (!p1conn) + player_remove(p1); + return COM_OK; + } + + if ((p == p1) && (to[0] == from[0])) { + pprintf(p, "Source and destination entries are the " + "same.\n"); + return COM_OK; + } + + // grab from a journal + msnprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", + stats_dir, parray[p].login[0], parray[p].login, + STATS_JOURNAL); + jsave_journalentry(p, to[0], p1, from[0], fname); + } + + if (!p1conn) + player_remove(p1); + return COM_OK; +} |