diff options
Diffstat (limited to 'FICS')
-rw-r--r-- | FICS/ISC_LICENSE | 2 | ||||
-rw-r--r-- | FICS/adminproc.c | 111 | ||||
-rw-r--r-- | FICS/algcheck.c | 15 | ||||
-rw-r--r-- | FICS/board.c | 15 | ||||
-rw-r--r-- | FICS/build.mk | 40 | ||||
-rw-r--r-- | FICS/command.c | 50 | ||||
-rw-r--r-- | FICS/comproc.c | 94 | ||||
-rw-r--r-- | FICS/fics_addplayer.c | 8 | ||||
-rw-r--r-- | FICS/ficsmain.c | 8 | ||||
-rw-r--r-- | FICS/ficsmain.h | 13 | ||||
-rw-r--r-- | FICS/formula.c | 2 | ||||
-rw-r--r-- | FICS/gamedb.c | 114 | ||||
-rw-r--r-- | FICS/gamedb.h | 7 | ||||
-rw-r--r-- | FICS/gameproc.c | 6 | ||||
-rw-r--r-- | FICS/legal2.c | 2 | ||||
-rw-r--r-- | FICS/lists.c | 16 | ||||
-rw-r--r-- | FICS/makerank.c | 18 | ||||
-rw-r--r-- | FICS/matchproc.c | 5 | ||||
-rw-r--r-- | FICS/network.c | 44 | ||||
-rw-r--r-- | FICS/obsproc.c | 67 | ||||
-rw-r--r-- | FICS/playerdb.c | 221 | ||||
-rw-r--r-- | FICS/ratings.c | 113 | ||||
-rw-r--r-- | FICS/ratings.h | 5 | ||||
-rw-r--r-- | FICS/talkproc.c | 6 | ||||
-rw-r--r-- | FICS/utils.c | 144 | ||||
-rw-r--r-- | FICS/utils.h | 11 | ||||
-rw-r--r-- | FICS/vers.c | 2 |
27 files changed, 860 insertions, 279 deletions
diff --git a/FICS/ISC_LICENSE b/FICS/ISC_LICENSE index eba7139..915b183 100644 --- a/FICS/ISC_LICENSE +++ b/FICS/ISC_LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023, 2024 Markus Uhlin <maxxe@rpblc.net> +Copyright (c) 2023 - 2025 Markus Uhlin <maxxe@rpblc.net> Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/FICS/adminproc.c b/FICS/adminproc.c index 94a9448..9f192bc 100644 --- a/FICS/adminproc.c +++ b/FICS/adminproc.c @@ -19,11 +19,16 @@ #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" @@ -171,6 +176,7 @@ 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); @@ -183,10 +189,14 @@ create_news_file(int p, param_list param, int admin) msnprintf(filename, sizeof filename, "%s/adminnews.%d", news_dir, param[0].val.integer); - if ((fp = fopen(filename, "w")) != NULL) { + 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) { @@ -196,10 +206,14 @@ create_news_file(int p, param_list param, int admin) msnprintf(filename, sizeof filename, "%s/news.%d", news_dir, param[0].val.integer); - if ((fp = fopen(filename, "w")) != NULL) { + 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); } } @@ -212,11 +226,19 @@ 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); - if ((new_fp = fopen(tmp_file, "w")) == NULL) + 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) @@ -367,12 +389,13 @@ PUBLIC int com_anews(int p, param_list param) { FILE *fp = NULL; - char *junkp = 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; - long int lval = 0; + int64_t lval = 0; time_t crtime = 0; msnprintf(filename, sizeof filename, "%s/newadminnews.index", news_dir); @@ -382,7 +405,6 @@ com_anews(int p, param_list param) return COM_OK; } -#define SCAN_JUNK "%ld %9s" _Static_assert(9 < ARRAY_SIZE(count), "Array too small"); if (param[0].type == 0) { @@ -400,7 +422,7 @@ com_anews(int p, param_list param) fclose(fp); return COM_FAILED; } - if (sscanf(junk, SCAN_JUNK, &lval, count) != 2) { + if (sscanf(junk, v_scan_junk, &lval, count) != 2) { warnx("%s: sscanf() error: too few items", __func__); fclose(fp); return COM_FAILED; @@ -429,7 +451,7 @@ com_anews(int p, param_list param) fclose(fp); return COM_FAILED; } - if (sscanf(junk, SCAN_JUNK, &lval, count) != 2) { + if (sscanf(junk, v_scan_junk, &lval, count) != 2) { warnx("%s: sscanf() error: too few items", __func__); fclose(fp); return COM_FAILED; @@ -453,8 +475,11 @@ com_anews(int p, param_list param) break; if (strlen(junk) > 1) { - if (sscanf(junkp, SCAN_JUNK, &lval, count) != 2) - warnx("%s: sscanf() error...", __func__); + if (sscanf(junkp, v_scan_junk, &lval, + count) != 2) { + warnx("%s: sscanf() error...", + __func__); + } crtime = lval; @@ -613,45 +638,57 @@ com_checkPLAYER(int p, param_list param) 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); + 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); + 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((time_t *) &parray[p1].logon_time)); - pprintf(p, "adminLevel = %d\n", parray[p1].adminLevel); + 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); + 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; diff --git a/FICS/algcheck.c b/FICS/algcheck.c index 250c129..15642fd 100644 --- a/FICS/algcheck.c +++ b/FICS/algcheck.c @@ -21,11 +21,16 @@ name email yy/mm/dd Change Richard Nash 93/10/22 Created Markus Uhlin 24/05/05 Revised + Markus Uhlin 25/04/05 alg_parse_move: + return ambiguous move on + out-of-bounds array read/write. */ #include "stdinclude.h" #include "common.h" +#include <err.h> + #include "algcheck.h" #include "board.h" #include "maxxes-utils.h" @@ -250,6 +255,11 @@ alg_parse_move(char *mstr, game_state_t *gs, move_t *mt) NextPieceLoop(gs->board, &f, &r, gs->onMove);) { if ((ff != ALG_UNKNOWN) && (ff != f)) continue; + if (r < 0 || r >= 8) { + warnx("%s: out-of-bounds array read/write: " + "r=%d", __func__, r); + return MOVE_AMBIGUOUS; + } if (piecetype(gs->board[f][r]) != piece) continue; if (gs->onMove == WHITE) { @@ -257,6 +267,11 @@ alg_parse_move(char *mstr, game_state_t *gs, move_t *mt) } else { tmpr = r - 1; } + if (tmpr < 0 || tmpr >= 8) { + warnx("%s: out-of-bounds array read/write: " + "tmpr=%d", __func__, tmpr); + return MOVE_AMBIGUOUS; + } if (gs->board[tf][tmpr] == NOPIECE) { if ((gs->ep_possible[((gs->onMove == WHITE) ? diff --git a/FICS/board.c b/FICS/board.c index f687b3e..2423024 100644 --- a/FICS/board.c +++ b/FICS/board.c @@ -25,6 +25,9 @@ Markus Uhlin 24/04/13 Added usage of the functions from 'maxxes-utils.h'. Markus Uhlin 24/06/01 Added and made use of brand(). + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. + Markus Uhlin 25/09/02 wild_update: fixed file created + without restricting permissions. */ #include "stdinclude.h" @@ -34,6 +37,7 @@ #include <limits.h> #include "board.h" +#include "ficsmain.h" #include "gamedb.h" #include "maxxes-utils.h" #include "playerdb.h" @@ -1133,7 +1137,7 @@ board_read_file(char *category, char *gname, game_state_t *gs) case 'g': case 'h': onFile = (c - 'a'); - onRank = -1; + onRank = -1; // NOLINT: dead store break; case '1': case '2': @@ -1161,7 +1165,7 @@ board_read_file(char *category, char *gname, game_state_t *gs) onColor = -1; onPiece = -1; onFile = -1; - onRank = -1; + onRank = -1; // NOLINT: dead store break; default: break; @@ -1320,13 +1324,18 @@ wild_update(int style) { FILE *fp; char fname[MAX_FILENAME_SIZE + 1]; + int fd; int onPiece; msnprintf(fname, sizeof fname, "%s/wild/%d", board_dir, style); - if ((fp = fopen(fname, "w")) == NULL) { + if ((fd = open(fname, g_open_flags[1], g_open_modes)) < 0) { warn("%s: can't write file name: %s", __func__, fname); return; + } else if ((fp = fdopen(fd, "w")) == NULL) { + warn("%s: can't write file name: %s", __func__, fname); + close(fd); + return; } fprintf(fp, "W:"); diff --git a/FICS/build.mk b/FICS/build.mk index f8c7d05..5d3d9b2 100644 --- a/FICS/build.mk +++ b/FICS/build.mk @@ -37,6 +37,40 @@ OBJS = $(SRC_DIR)adminproc.o\ $(SRC_DIR)variable.o\ $(SRC_DIR)vers.o +SRCS = $(SRC_DIR)adminproc.c\ + $(SRC_DIR)algcheck.c\ + $(SRC_DIR)assert_error.c\ + $(SRC_DIR)board.c\ + $(SRC_DIR)command.c\ + $(SRC_DIR)comproc.c\ + $(SRC_DIR)eco.c\ + $(SRC_DIR)fics_getsalt.cpp\ + $(SRC_DIR)ficslim.cpp\ + $(SRC_DIR)ficsmain.c\ + $(SRC_DIR)formula.c\ + $(SRC_DIR)gamedb.c\ + $(SRC_DIR)gameproc.c\ + $(SRC_DIR)iset.cpp\ + $(SRC_DIR)legal.c\ + $(SRC_DIR)legal2.c\ + $(SRC_DIR)lists.c\ + $(SRC_DIR)matchproc.c\ + $(SRC_DIR)maxxes-utils.c\ + $(SRC_DIR)movecheck.c\ + $(SRC_DIR)multicol.c\ + $(SRC_DIR)network.c\ + $(SRC_DIR)obsproc.c\ + $(SRC_DIR)playerdb.c\ + $(SRC_DIR)rating_conv.c\ + $(SRC_DIR)ratings.c\ + $(SRC_DIR)rmalloc.c\ + $(SRC_DIR)shutdown.c\ + $(SRC_DIR)sought.cpp\ + $(SRC_DIR)talkproc.c\ + $(SRC_DIR)utils.c\ + $(SRC_DIR)variable.c\ + $(SRC_DIR)vers.c + AP_OBJS = $(SRC_DIR)fics_addplayer.o MR_OBJS = $(SRC_DIR)makerank.o @@ -53,7 +87,9 @@ fics_addplayer: $(INCLUDE_DIR)ficspaths.h $(OBJS) $(AP_OBJS) $(E) " LINK " $@ $(Q) $(CXX) $(CXXFLAGS) -o $@ $(OBJS) \ $(AP_OBJS) $(AP_LDFLAGS) $(AP_LDLIBS) -makerank: $(INCLUDE_DIR)ficspaths.h $(MR_OBJS) +makerank: $(INCLUDE_DIR)ficspaths.h $(OBJS) $(MR_OBJS) + strip --strip-symbol=main $(SRC_DIR)ficsmain.o $(E) " LINK " $@ - $(Q) $(CXX) $(CXXFLAGS) -o $@ $(MR_OBJS) $(MR_LDFLAGS) $(MR_LDLIBS) + $(Q) $(CXX) $(CXXFLAGS) -o $@ $(OBJS) \ + $(MR_OBJS) $(MR_LDFLAGS) $(MR_LDLIBS) # EOF diff --git a/FICS/command.c b/FICS/command.c index 528bdf9..df5d4c2 100644 --- a/FICS/command.c +++ b/FICS/command.c @@ -36,6 +36,9 @@ Markus Uhlin 25/03/09 Fixed double free() Markus Uhlin 25/03/11 Fixed memleak Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t' + Markus Uhlin 25/07/28 Usage of 'int64_t' + Markus Uhlin 25/08/23 Fixed file created without + restricting permissions. */ #include "stdinclude.h" @@ -44,6 +47,8 @@ #include <sys/param.h> #include <err.h> +#include <inttypes.h> +#include <stdint.h> #include "command.h" #include "command_list.h" @@ -132,7 +137,11 @@ parse_command(char *com_string, char **comm, char **parameters) PUBLIC int alias_lookup(char *tmp, alias_type *alias_list, int numalias) { - for (int i = 0; (alias_list[i].comm_name && i < numalias); i++) { + if (numalias >= MAX_ALIASES) + return -1; + for (int i = 0; + (i < numalias && alias_list[i].comm_name != NULL); + i++) { if (!strcmp(tmp, alias_list[i].comm_name)) return i; } @@ -712,10 +721,11 @@ boot_out(int p, int p1) PUBLIC void rscan_news(FILE *fp, int p, time_t lc) { - char *junkp = NULL; char count[10] = { '\0' }; char junk[MAX_LINE_SIZE] = { '\0' }; - long int lval = 0; + char *junkp = NULL; + const char *scan_fmt = "%" SCNd64 " " "%9s"; + int64_t lval = 0; time_t crtime = 0; if (fgets(junk, sizeof junk, fp) == NULL || @@ -724,7 +734,7 @@ rscan_news(FILE *fp, int p, time_t lc) _Static_assert(ARRAY_SIZE(count) > 9, "Unexpected array size"); - if (sscanf(junk, "%ld %9s", &lval, count) != 2) { + if (sscanf(junk, scan_fmt, &lval, count) != 2) { warnx("%s: sscanf() error: too few items", __func__); return; } @@ -748,13 +758,13 @@ rscan_news(FILE *fp, int p, time_t lc) PRIVATE void check_news(int p, int admin) { -#define SCAN_JUNK "%ld %9s" FILE *fp = NULL; char count[10] = { '\0' }; char filename[MAX_FILENAME_SIZE] = { '\0' }; char junk[MAX_LINE_SIZE] = { '\0' }; char *junkp = NULL; - long int lval = 0; + const char *v_scan_fmt = "%" SCNd64 " " "%9s"; + int64_t lval = 0; time_t crtime = 0; time_t lc = player_lastconnect(p); @@ -784,7 +794,7 @@ check_news(int p, int admin) warnx("%s: fgets() error", __func__); fclose(fp); return; - } else if (sscanf(junk, SCAN_JUNK, &lval, count) != 2) { + } else if (sscanf(junk, v_scan_fmt, &lval, count) != 2) { warnx("%s: sscanf() error", __func__); fclose(fp); return; @@ -834,7 +844,7 @@ check_news(int p, int admin) warnx("%s: fgets() error", __func__); fclose(fp); return; - } else if (sscanf(junk, SCAN_JUNK, &lval, count) != 2) { + } else if (sscanf(junk, v_scan_fmt, &lval, count) != 2) { warnx("%s: sscanf() error", __func__); fclose(fp); return; @@ -1295,23 +1305,32 @@ commands_init(void) { FILE *fp, *afp; char fname[MAX_FILENAME_SIZE]; + int fd[2]; int i = 0; + fp = afp = NULL; snprintf(fname, sizeof fname, "%s/commands", comhelp_dir); - if ((fp = fopen(fname, "w")) == NULL) { + if ((fd[0] = open(fname, g_open_flags[1], g_open_modes)) < 0) { + warn("%s: open: %s", __func__, fname); + return; + } else if ((fp = fdopen(fd[0], "w")) == NULL) { warn("%s: could not write commands help file (%s)", __func__, fname); + close(fd[0]); return; } snprintf(fname, sizeof fname, "%s/admin_commands", adhelp_dir); - if ((afp = fopen(fname, "w")) == NULL) { + if ((fd[1] = open(fname, g_open_flags[1], g_open_modes)) < 0) { + warn("%s: open: %s", __func__, fname); + goto clean_up; + } else if ((afp = fdopen(fd[1], "w")) == NULL) { warn("%s: could not write admin_commands help file (%s)", __func__, fname); - fclose(fp); - return; + close(fd[1]); + goto clean_up; } while (command_list[i].comm_name) { @@ -1322,8 +1341,11 @@ commands_init(void) i++; } - fclose(fp); - fclose(afp); + clean_up: + if (fp) + fclose(fp); + if (afp) + fclose(afp); } /* Need to save rated games */ diff --git a/FICS/comproc.c b/FICS/comproc.c index 31f6064..b2504fd 100644 --- a/FICS/comproc.c +++ b/FICS/comproc.c @@ -43,6 +43,11 @@ Markus Uhlin 25/03/16 Fixed untrusted array index. Markus Uhlin 25/03/25 com_unalias: fixed overflowed array index read/write. + Markus Uhlin 25/07/21 com_who: fixed multiplication + result converted to larger type. + Markus Uhlin 25/07/24 Fixed use of potentially + dangerous functions. + Markus Uhlin 25/07/29 Usage of 'int64_t'. */ #include "stdinclude.h" @@ -51,6 +56,9 @@ #include <sys/resource.h> #include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <stdint.h> #include "board.h" #include "command.h" @@ -125,17 +133,18 @@ com_more(int p, param_list param) PUBLIC void rscan_news2(FILE *fp, int p, int num) { - char *junkp; char count[10] = { '\0' }; char junk[MAX_LINE_SIZE] = { '\0' }; - long int lval; + char *junkp; + const char *v_scan_fmt = "%" SCNd64 " " "%9s"; + int64_t lval; time_t crtime; if (num == 0) return; if (fgets(junk, sizeof junk, fp) == NULL || feof(fp) || - sscanf(junk, "%ld %9s", &lval, count) != 2) + sscanf(junk, v_scan_fmt, &lval, count) != 2) return; rscan_news2(fp, p, num - 1); @@ -152,12 +161,13 @@ PUBLIC int com_news(int p, param_list param) { FILE *fp = NULL; - char *junkp = NULL; char count[10] = { '\0' }; char filename[MAX_FILENAME_SIZE] = { '\0' }; char junk[MAX_LINE_SIZE] = { '\0' }; + char *junkp = NULL; + const char *v_scan_fmt = "%" SCNd64 " " "%9s"; int found = 0; - long int lval = 0; + int64_t lval = 0; time_t crtime = 0; snprintf(filename, sizeof filename, "%s/newnews.index", news_dir); @@ -167,7 +177,6 @@ com_news(int p, param_list param) return COM_OK; } -#define SCAN_JUNK "%ld %9s" _Static_assert(9 < ARRAY_SIZE(count), "'count' too small"); if (param[0].type == 0) { @@ -179,7 +188,7 @@ com_news(int p, param_list param) pprintf(p, "Index of recent news items:\n"); if (fgets(junk, sizeof junk, fp) == NULL || - sscanf(junk, SCAN_JUNK, &lval, count) != 2) { + sscanf(junk, v_scan_fmt, &lval, count) != 2) { warnx("%s: error: fgets() or sscanf()", __func__); fclose(fp); return COM_FAILED; @@ -204,7 +213,7 @@ com_news(int p, param_list param) pprintf(p, "Index of all news items:\n"); if (fgets(junk, sizeof junk, fp) == NULL || - sscanf(junk, SCAN_JUNK, &lval, count) != 2) { + sscanf(junk, v_scan_fmt, &lval, count) != 2) { warnx("%s: error: fgets() or sscanf()", __func__); fclose(fp); return COM_FAILED; @@ -230,7 +239,7 @@ com_news(int p, param_list param) if (fgets(junk, sizeof junk, fp) == NULL || feof(fp)) break; - if (sscanf(junkp, SCAN_JUNK, &lval, count) != 2) + if (sscanf(junkp, v_scan_fmt, &lval, count) != 2) warnx("%s: sscanf() error...", __func__); crtime = lval; @@ -387,11 +396,18 @@ com_stats_rating(char *hdr, statistics *stats, char *dest, const size_t dsize) stats->num); if (stats->whenbest) { + struct tm res = {0}; + snprintf(tmp, sizeof tmp, " %d", stats->best); strlcat(dest, tmp, dsize); - strftime(tmp, sizeof tmp, " (%d-%b-%y)", - localtime((time_t *) &stats->whenbest)); - strlcat(dest, tmp, dsize); + + errno = 0; + + if (localtime_r(&stats->whenbest, &res) != NULL) { + if (strftime(tmp, sizeof tmp, " (%d-%b-%y)", &res) != 0) + strlcat(dest, tmp, dsize); + } else + warn("%s: localtime_r", __func__); } if (strlcat(dest, "\n", dsize) >= dsize) { @@ -581,9 +597,14 @@ com_stats(int p, param_list param) if (connected && parray[p1].registered && (p == p1 || parray[p].adminLevel > 0)) { - char *timeToStr = ctime((time_t *) &parray[p1].timeOfReg); + char timeToStr[30] = { '\0' }; + + errno = 0; + + if (ctime_r(&parray[p1].timeOfReg, timeToStr) == NULL) + warn("%s: ctime_r", __func__); + timeToStr[strcspn(timeToStr, "\n")] = '\0'; - timeToStr[strlen(timeToStr) - 1] = '\0'; pprintf(p, "\n"); onTime = ((time(NULL) - parray[p1].logon_time) + @@ -745,8 +766,10 @@ plogins(int p, char *fname) FILE *fp = NULL; char ipstr[20] = { '\0' }; char loginName[MAX_LOGIN_NAME + 1] = { '\0' }; + const char *v_scan_fmt = "%" SCNu16 " %19s " "%" SCNd64 " " + "%d %19s\n"; int registered = 0; - long int lval = 0; + int64_t lval = 0; time_t tval = 0; uint16_t inout = 0; @@ -759,8 +782,8 @@ plogins(int p, char *fname) _Static_assert(19 < ARRAY_SIZE(loginName), "'loginName' too small"); while (!feof(fp)) { - if (fscanf(fp, "%hu %19s %ld %d %19s\n", &inout, loginName, - &lval, ®istered, ipstr) != 5) { + if (fscanf(fp, v_scan_fmt, &inout, loginName, &lval, + ®istered, ipstr) != 5) { fprintf(stderr, "FICS: Error in login info format. " "%s\n", fname); fclose(fp); @@ -1320,10 +1343,7 @@ com_who(int p, param_list param) sel_bits |= WHO_REGISTERED; break; case 'l': // Sort order - cmp_func = alpha_cmp; - sort_type = none; - break; - case 'A': // Sort order + case 'A': cmp_func = alpha_cmp; sort_type = none; break; @@ -1380,8 +1400,8 @@ com_who(int p, param_list param) count++; } - startpoint = floor((float) count * start_perc); - stoppoint = ceil((float) count * stop_perc) - 1; + startpoint = floorf((float) count * start_perc); + stoppoint = ceilf((float) count * stop_perc) - 1; num_who = 0; count = 0; @@ -1810,10 +1830,9 @@ FindAndShowFile(int p, param_list param, char *dir) { char *iwant, *filenames[1000]; int i; - static char nullify = '\0'; if (param[0].type == TYPE_NULL) { - iwant = &nullify; + iwant = NULL; } else { iwant = param[0].val.word; @@ -1827,8 +1846,9 @@ FindAndShowFile(int p, param_list param, char *dir) i = search_directory(dir, iwant, filenames, ARRAY_SIZE(filenames)); if (i == 0) { - pprintf(p, "No information available on \"%s\".\n", iwant); - } else if (i == 1 || !strcmp(*filenames, iwant)) { + pprintf(p, "No information available on \"%s\".\n", + (iwant ? iwant : "")); + } else if (i == 1 || !strcmp(*filenames, iwant ? iwant : "")) { if (psend_file(p, dir, *filenames)) { /* * We should never reach this unless the file @@ -1839,7 +1859,7 @@ FindAndShowFile(int p, param_list param, char *dir) "Thank you.\n"); } } else { - if (*iwant) + if (iwant && *iwant) pprintf(p, "Matches:\n"); display_directory(p, filenames, i); } @@ -1867,7 +1887,6 @@ com_mailsource(int p, param_list param) char fname[MAX_FILENAME_SIZE]; char subj[120]; int count; - static char nullify = '\0'; if (!parray[p].registered) { pprintf(p, "Only registered people can use the mailsource " @@ -1876,14 +1895,15 @@ com_mailsource(int p, param_list param) } if (param[0].type == TYPE_NULL) - iwant = &nullify; + iwant = NULL; else iwant = param[0].val.word; if ((count = search_directory(source_dir, iwant, buffer, ARRAY_SIZE(buffer))) == 0) { - pprintf(p, "Found no source file matching \"%s\".\n", iwant); - } else if ((count == 1) || !strcmp(iwant, *buffer)) { + pprintf(p, "Found no source file matching \"%s\".\n", + (iwant ? iwant : "")); + } else if ((count == 1) || !strcmp(iwant ? iwant : "", *buffer)) { snprintf(subj, sizeof subj, "FICS source file from server " "%s: %s", fics_hostname, @@ -1899,7 +1919,7 @@ com_mailsource(int p, param_list param) } else { pprintf(p, "Found %d source files matching that:\n", count); - if (*iwant) { + if (iwant && *iwant) { display_directory(p, buffer, count); } else { // this junk is to get *.c *.h char *s; @@ -1930,7 +1950,6 @@ com_mailhelp(int p, param_list param) char subj[120]; int count; int lang = parray[p].language; - static char nullify = '\0'; if (!parray[p].registered) { pprintf(p, "Only registered people can use the mailhelp " @@ -1939,7 +1958,7 @@ com_mailhelp(int p, param_list param) } if (param[0].type == TYPE_NULL) - iwant = &nullify; + iwant = NULL; else iwant = param[0].val.word; @@ -1960,8 +1979,9 @@ com_mailhelp(int p, param_list param) } if (count == 0) { - pprintf(p, "Found no help file matching \"%s\".\n", iwant); - } else if (count == 1 || !strcmp(*buffer, iwant)) { + pprintf(p, "Found no help file matching \"%s\".\n", + (iwant ? iwant : "")); + } else if (count == 1 || !strcmp(*buffer, iwant ? iwant : "")) { snprintf(subj, sizeof subj, "FICS help file from server %s: %s", fics_hostname, *buffer); diff --git a/FICS/fics_addplayer.c b/FICS/fics_addplayer.c index ad6c43f..160b28d 100644 --- a/FICS/fics_addplayer.c +++ b/FICS/fics_addplayer.c @@ -40,6 +40,7 @@ #include "command.h" #include "config.h" #include "fics_getsalt.h" +#include "ficsmain.h" #include "playerdb.h" #include "utils.h" @@ -60,12 +61,17 @@ add_handle_to_list(const char *handle) { FILE *fp; char path[1024]; + int fd; snprintf(path, sizeof path, "%s/admin", DEFAULT_LISTS); - if ((fp = fopen(path, "a")) == NULL) { + if ((fd = open(path, g_open_flags[0], g_open_modes)) < 0) { warn("%s: unable to open %s", __func__, path); return; + } else if ((fp = fdopen(fd, "a")) == NULL) { + warn("%s: unable to open %s", __func__, path); + close(fd); + return; } fprintf(fp, "%s\n", handle); diff --git a/FICS/ficsmain.c b/FICS/ficsmain.c index 4110635..664874f 100644 --- a/FICS/ficsmain.c +++ b/FICS/ficsmain.c @@ -36,9 +36,7 @@ #include <err.h> #include <errno.h> -#include <fcntl.h> #include <stdint.h> -#include <unistd.h> #include "board.h" #include "command.h" @@ -61,6 +59,12 @@ #include <bsd/string.h> #endif +PUBLIC const int g_open_flags[2] = { + (O_WRONLY|O_CREAT|O_APPEND), + (O_WRONLY|O_CREAT|O_TRUNC), +}; +PUBLIC const mode_t g_open_modes = (S_IWUSR | S_IRUSR); + /* Arguments */ PUBLIC int port; PUBLIC int withConsole; diff --git a/FICS/ficsmain.h b/FICS/ficsmain.h index d47bbcc..08ea466 100644 --- a/FICS/ficsmain.h +++ b/FICS/ficsmain.h @@ -26,6 +26,14 @@ #ifndef _FICSMAIN_H #define _FICSMAIN_H +#include <sys/types.h> +#include <sys/stat.h> + +#include <fcntl.h> +#include <unistd.h> + +#include "common.h" + /* * Heartbead functions occur approx in this time, including checking * for new connections and decrementing timeleft counters. @@ -57,8 +65,13 @@ #define STATS_GAMES "games" #define STATS_JOURNAL "journal" +__FICS_BEGIN_DECLS +extern const int g_open_flags[2]; +extern const mode_t g_open_modes; + /* Arguments */ extern int port; extern int withConsole; +__FICS_END_DECLS #endif /* _FICSMAIN_H */ diff --git a/FICS/formula.c b/FICS/formula.c index 5f9fdb7..dd5ff23 100644 --- a/FICS/formula.c +++ b/FICS/formula.c @@ -574,7 +574,7 @@ ChooseClauses(player *who, char *formula) return ret; for (i = 0; formula[i] != '\0' && formula[i] != '#'; i++) { - if (formula[i] != 'f' || (i > 0 && isalnum(formula[i - 1])) || + if ((i > 0 && isalnum(formula[i - 1])) || formula[i] != 'f' || !isdigit(formula[i + 1]) || sscanf(&formula[i], "f%d", &which) != 1) continue; diff --git a/FICS/gamedb.c b/FICS/gamedb.c index ce0842c..f3a351b 100644 --- a/FICS/gamedb.c +++ b/FICS/gamedb.c @@ -40,6 +40,14 @@ Markus Uhlin 25/03/25 ReadGameState: fixed truncated stdio return value. Markus Uhlin 25/04/01 Fixed call of risky function + Markus Uhlin 25/04/01 ReadV1GameFmt: guard num half + moves. + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. + Markus Uhlin 25/07/28 Fixed use of potentially + dangerous functions. + Markus Uhlin 25/07/29 Usage of 'int64_t'. + Markus Uhlin 25/08/16 Fixed uncontrolled data used in + path expressions. */ #include "stdinclude.h" @@ -47,7 +55,9 @@ #include <err.h> #include <errno.h> +#include <inttypes.h> #include <limits.h> +#include <stdint.h> #include "command.h" #include "config.h" @@ -560,12 +570,12 @@ EndSym(int g) PUBLIC char * movesToString(int g, int pgn) { + char tmp[160] = { '\0' }; char *serv_loc = SERVER_LOCATION; char *serv_name = SERVER_NAME; - char tmp[160] = { '\0' }; int i, col; int wr, br; - struct tm *tm_ptr = NULL; + struct tm v_tm = {0}; time_t curTime; wr = garray[g].white_rating; @@ -583,14 +593,16 @@ movesToString(int g, int pgn) serv_name, serv_loc); - if ((tm_ptr = localtime(&curTime)) != NULL) { + errno = 0; + + if (localtime_r(&curTime, &v_tm) != NULL) { strftime(tmp, sizeof(tmp), "[Date \"%Y.%m.%d\"]\n" "[Time \"%H:%M:%S\"]\n", - tm_ptr); + &v_tm); mstrlcat(gameString, tmp, sizeof gameString); } else - warn("%s: localtime", __func__); + warn("%s: localtime_r()", __func__); msnprintf(tmp, sizeof tmp, "[Round \"-\"]\n" @@ -665,11 +677,13 @@ movesToString(int g, int pgn) mstrlcat(gameString, tmp, sizeof gameString); mstrlcat(gameString, "--- ", sizeof gameString); - if ((tm_ptr = localtime(&curTime)) != NULL) { - strftime(tmp, sizeof tmp, "%Y.%m.%d %H:%M:%S", tm_ptr); + errno = 0; + + if (localtime_r(&curTime, &v_tm) != NULL) { + strftime(tmp, sizeof tmp, "%Y.%m.%d %H:%M:%S", &v_tm); mstrlcat(gameString, tmp, sizeof gameString); } else - warn("%s: localtime", __func__); + warn("%s: localtime_r()", __func__); if (garray[g].rated) { mstrlcat(gameString, "\nRated ", sizeof gameString); @@ -1065,10 +1079,16 @@ got_attr_value(int g, char *attr, char *value, FILE *fp, char *file) } else if (!strcmp(attr, "type:")) { garray[g].type = atoi(value); } else if (!strcmp(attr, "halfmoves:")) { - garray[g].numHalfMoves = atoi(value); - - if (garray[g].numHalfMoves == 0) + if ((garray[g].numHalfMoves = atoi(value)) == 0) return 0; + else if (garray[g].numHalfMoves < 0 || + (size_t)garray[g].numHalfMoves > INT_MAX / sizeof(move_t)) { + warnx("%s: num half moves out-of-bounds (%d)", __func__, + garray[g].numHalfMoves); + return -1; + } else { + /* null */; + } garray[g].moveListSize = garray[g].numHalfMoves; garray[g].moveList = reallocarray(NULL, sizeof(move_t), @@ -1267,14 +1287,16 @@ PRIVATE int ReadV1GameFmt(game *g, FILE *fp, const char *file, int version) { int ret[3]; - long int lval; + int64_t lval; _Static_assert(17 < ARRAY_SIZE(g->white_name), "Unexpected array size"); _Static_assert(17 < ARRAY_SIZE(g->black_name), "Unexpected array size"); ret[0] = fscanf(fp, "%17s %17s", g->white_name, g->black_name); - ret[1] = fscanf(fp, "%d %d", &g->white_rating, &g->black_rating); - ret[2] = fscanf(fp, "%d %d %d %d", + ret[1] = fscanf(fp, "%d %d", // NOLINT + &g->white_rating, + &g->black_rating); + ret[2] = fscanf(fp, "%d %d %d %d", // NOLINT &g->wInitTime, &g->wIncrement, &g->bInitTime, @@ -1289,7 +1311,7 @@ ReadV1GameFmt(game *g, FILE *fp, const char *file, int version) if (version < 3 && !g->bInitTime) g->bInitTime = g->wInitTime; - if (fscanf(fp, "%ld", &lval) != 1) { + if (fscanf(fp, "%" SCNd64, &lval) != 1) { warnx("%s: %s: failed to get time of start", __func__, file); return -1; } else @@ -1317,7 +1339,7 @@ ReadV1GameFmt(game *g, FILE *fp, const char *file, int version) ret[0] = fscanf(fp, "%d %d %d %d", &g->private, &g->type, &g->rated, &g->clockStopped); - ret[1] = fscanf(fp, "%d", &g->numHalfMoves); + ret[1] = fscanf(fp, "%d", &g->numHalfMoves); // NOLINT if (ret[0] != 4 || ret[1] != 1) { warnx("%s: fscanf error: %s", __func__, file); return -1; @@ -1503,7 +1525,7 @@ PRIVATE void WriteGameFile(FILE *fp, int g) { game *gg = &garray[g]; - long int lval; + int64_t lval; player *bp = &parray[gg->black]; player *wp = &parray[gg->white]; @@ -1514,7 +1536,7 @@ WriteGameFile(FILE *fp, int g) gg->bInitTime, gg->bIncrement); lval = gg->timeOfStart; - fprintf(fp, "%ld\n", lval); + fprintf(fp, "%" PRId64 "\n", lval); #ifdef TIMESEAL fprintf(fp, "%d %d\n", @@ -1540,6 +1562,7 @@ game_save(int g) char fname[MAX_FILENAME_SIZE]; char lname[MAX_FILENAME_SIZE]; game *gg = &garray[g]; + int fd; player *wp, *bp; wp = &parray[gg->white]; @@ -1550,11 +1573,13 @@ game_save(int g) msnprintf(lname, sizeof lname, "%s/%c/%s-%s", adj_dir, bp->login[0], wp->login, bp->login); - fp = fopen(fname, "w"); - - if (!fp) { + if ((fd = open(fname, g_open_flags[1], g_open_modes)) < 0) { + warn("%s: open: %s", __func__, fname); + return -1; + } else if ((fp = fdopen(fd, "w")) == NULL) { fprintf(stderr, "FICS: Problem opening file %s for write\n", fname); + close(fd); return -1; } @@ -1604,12 +1629,16 @@ OldestHistGame(char *login) char pFile[MAX_FILENAME_SIZE] = { '\0' }; long int when; + /* Centralized validation of login */ + if (!is_valid_login_name(login)) { + warnx("%s: invalid login value: '%s'", __func__, login); + return 0L; + } + msnprintf(pFile, sizeof pFile, "%s/player_data/%c/%s.%s", stats_dir, login[0], login, STATS_GAMES); - fp = fopen(pFile, "r"); - - if (fp == NULL) { + if ((fp = fopen(pFile, "r")) == NULL) { msnprintf(pFile, sizeof pFile, "%s/player_data/%c/.rem.%s.%s", stats_dir, login[0], login, STATS_GAMES); fp = fopen(pFile, "r"); @@ -1637,10 +1666,9 @@ RemoveHistGame(char *file, int maxlines) char Opponent[MAX_LOGIN_NAME + 1] = { '\0' }; char line[MAX_LINE_SIZE] = { '\0' }; int count = 0; - long int When, oppWhen; + long int When = 0, oppWhen = 0; _Static_assert(20 < ARRAY_SIZE(Opponent), "Not within bounds"); - When = oppWhen = 0; if ((fp = fopen(file, "r")) == NULL) { return; @@ -1695,11 +1723,19 @@ RemHist(char *who) if (ret != 2) { warnx("%s: fscanf() error (%s:%ld)", __func__, fName, iter_no); -// iter_no++; break; } stolower(Opp); + + // Centralized validation: only allow safe login names + if (!is_valid_login_name(Opp)) { + warnx("%s: invalid value: " + "Opp = '%s' (skipping)", __func__, Opp); + iter_no++; + continue; + } + oppWhen = OldestHistGame(Opp); if (oppWhen > When || oppWhen <= 0L) { @@ -1725,12 +1761,13 @@ write_g_out(int g, char *file, int maxlines, int isDraw, char *EndSymbol, char *name, time_t *now) { FILE *fp; - char *goteco; char cResult; char tmp[2048] = { '\0' }; - char *ptmp = tmp; char type[4]; + char *goteco; + char *ptmp = tmp; int count = -1; + int fd; int wp, bp; int wr, br; @@ -1797,8 +1834,14 @@ write_g_out(int g, char *file, int maxlines, int isDraw, char *EndSymbol, count = (count + 1) % 100; - if ((fp = fopen(file, "a")) == NULL) + if ((fd = open(file, g_open_flags[0], g_open_modes)) < 0) { + warn("%s: open: %s", __func__, file); + return; + } else if ((fp = fdopen(fd, "a")) == NULL) { + close(fd); return; + } + goteco = getECO(g); /* @@ -1917,17 +1960,22 @@ addjournalitem(int p, char count2, char *WhiteName2, int WhiteRating2, char result[100] = { '\0' }; char type[100] = { '\0' }; int WhiteRating, BlackRating; + int fd; int have_output = 0; int t, i; mstrlcpy(fname2, fname, sizeof fname2); mstrlcat(fname2, ".w", sizeof fname2); - if ((fp2 = fopen(fname2, "w")) == NULL) { + if ((fd = open(fname2, g_open_flags[1], g_open_modes)) < 0) { + warn("%s: open", __func__); + return; + } else if ((fp2 = fdopen(fd, "w")) == NULL) { fprintf(stderr, "FICS: Problem opening file %s for write\n", fname); pprintf(p, "Couldn't update journal! Report this to an admin." "\n"); + close(fd); return; } @@ -2113,6 +2161,8 @@ pgames(int p, int p1, char *fname) _Static_assert(ARRAY_SIZE(ending) > 99, "'ending' too small"); while (!feof(fp)) { + char tbuf[30] = { '\0' }; + if (fscanf(fp, "%d %1s %d %1s %d %19s %99s %d %d %d %d %99s " "%99s %ld\n", &count, result, &MyRating, MyColor, @@ -2135,7 +2185,7 @@ pgames(int p, int p1, char *fname) count, result, MyRating, MyColor, OppRating, OppName, type, (wt / 600), (wi / 10), eco, ending, - ctime(&t)); + ctime_r(&t, tbuf) != NULL ? &tbuf[0] : ""); } fclose(fp); diff --git a/FICS/gamedb.h b/FICS/gamedb.h index 2d0edc8..8dc84d3 100644 --- a/FICS/gamedb.h +++ b/FICS/gamedb.h @@ -27,6 +27,7 @@ #ifndef _GAMEDB_H #define _GAMEDB_H +#include <stdint.h> #include <time.h> #include "board.h" @@ -140,9 +141,9 @@ typedef struct _game { move_t *examMoveList; // Extra movelist for examine int examMoveListSize; - unsigned int startTime; // The relative time the game started - unsigned int lastMoveTime; // Last time a move was made - unsigned int lastDecTime; // Last time a players clock was + uint64_t startTime; // The relative time the game started + uint64_t lastMoveTime; // Last time a move was made + uint64_t lastDecTime; // Last time a players clock was // decremented int result; diff --git a/FICS/gameproc.c b/FICS/gameproc.c index eea5205..cd6398e 100644 --- a/FICS/gameproc.c +++ b/FICS/gameproc.c @@ -1834,7 +1834,11 @@ com_goboard(int p, param_list param) } on = parray[p].simul_info.onBoard; - g = parray[p].simul_info.boards[on]; + + if ((g = parray[p].simul_info.boards[on]) < 0) { + pprintf(p, "Internal error! Unexpected negative value!\n"); + return COM_OK; + } if (p1 == garray[g].black) { pprintf(p, "You are already at that board!\n"); diff --git a/FICS/legal2.c b/FICS/legal2.c index 528814a..15c0c16 100644 --- a/FICS/legal2.c +++ b/FICS/legal2.c @@ -1,7 +1,7 @@ #include "legal2.h" const char legalNotice2[] = - "Copyright (c) 2023, 2024 Markus Uhlin <maxxe@rpblc.net>\n" + "Copyright (c) 2023 - 2025 Markus Uhlin <maxxe@rpblc.net>\n" "\n" "Permission to use, copy, modify, and distribute this software for any\n" "purpose with or without fee is hereby granted, provided that the above\n" diff --git a/FICS/lists.c b/FICS/lists.c index d9879d4..c11d75b 100644 --- a/FICS/lists.c +++ b/FICS/lists.c @@ -13,6 +13,7 @@ #include "command.h" #include "comproc.h" +#include "ficsmain.h" #include "gamedb.h" #include "lists.h" #include "maxxes-utils.h" @@ -60,7 +61,14 @@ list_find(int p, enum ListWhich l) int personal; personal = ListArray[l].rights == P_PERSONAL; - starter = (personal ? &parray[p].lists : &firstGlobalList); + + if (personal) { + if (p < 0) + return NULL; + starter = &parray[p].lists; + } else { + starter = &firstGlobalList; + } for (tempList = *starter; tempList != NULL; tempList = tempList->next) { if (l == tempList->which) { @@ -363,6 +371,7 @@ list_addsub(int p, char *list, char *who, int addsub) if (!personal) { FILE *fp; char filename[MAX_FILENAME_SIZE] = { '\0' }; + int fd; switch (gl->which) { case L_MUZZLE: @@ -426,8 +435,11 @@ list_addsub(int p, char *list, char *who, int addsub) msnprintf(filename, sizeof filename, "%s/%s", lists_dir, listname); - if ((fp = fopen(filename, "w")) == NULL) { + if ((fd = open(filename, g_open_flags[1], g_open_modes)) < 0) { + fprintf(stderr, "Couldn't save %s list.\n", listname); + } else if ((fp = fdopen(fd, "w")) == NULL) { fprintf(stderr, "Couldn't save %s list.\n", listname); + close(fd); } else { for (int i = 0; i < gl->numMembers; i++) fprintf(fp, "%s\n", gl->member[i]); diff --git a/FICS/makerank.c b/FICS/makerank.c index 4458f31..076e16e 100644 --- a/FICS/makerank.c +++ b/FICS/makerank.c @@ -13,7 +13,9 @@ #endif #include "common.h" +#include "ficsmain.h" #include "makerank.h" +#include "utils.h" static ENTRY **list; static ENTRY **sortme; @@ -184,6 +186,16 @@ LoadEntries(void) e.name[strcspn(e.name, "\n")] = '\0'; + /* + * Validate that e.name does not contain path + * traversal or separators + */ + if (!is_valid_filename(e.name, false)) { + printf("Skipping invalid filename: %s\n", + e.name); + continue; + } + if (e.name[0] != letter1) { printf("File %c/%s: wrong directory.\n", letter1, e.name); @@ -263,6 +275,7 @@ makerank(void) { FILE *fp; char fName[200]; + int fd; int sortnum, sortmesize, i, n; printf("Loading players\n"); @@ -302,8 +315,9 @@ makerank(void) snprintf(fName, sizeof fName, "%s/rank.%s", DEFAULT_STATS, rnames[rtype]); - if ((fp = fopen(fName, "w")) == NULL) - err(1, "%s: fopen", __func__); + if ((fd = open(fName, g_open_flags[1], g_open_modes)) < 0 || + (fp = fdopen(fd, "w")) == NULL) + err(1, "%s: rank file open error", __func__); for (i = 0; i < sortnum; i++) { fprintf(fp, "%s %d %d %d\n", diff --git a/FICS/matchproc.c b/FICS/matchproc.c index fb13439..1b1eab8 100644 --- a/FICS/matchproc.c +++ b/FICS/matchproc.c @@ -1079,7 +1079,10 @@ com_match(int p, param_list param) .wt = wt, }; - print_bughouse(p, p1, &ctx, colorstr); + if (ctx.white >= 0) + print_bughouse(p, p1, &ctx, colorstr); + else + warnx("%s: cannot print bughouse", __func__); } if (in_list(p, L_COMPUTER, parray[p].name)) { diff --git a/FICS/network.c b/FICS/network.c index b795e99..19c6f22 100644 --- a/FICS/network.c +++ b/FICS/network.c @@ -12,6 +12,7 @@ #include <err.h> #include <errno.h> +#include <limits.h> #include "common.h" #include "config.h" @@ -267,7 +268,7 @@ net_send_string(int fd, char *str, int format) if ((which = findConnection(fd)) < 0) return -1; while (*str) { - const int upbound = strlen(str); + const int upbound = (int)strlen(str); for (i = 0; i < upbound && str[i] >= ' '; i++) { /* null */; @@ -314,6 +315,7 @@ net_send_string(int fd, char *str, int format) break; case '\033': con[which].outPos -= 3; + // XXX: fallthrough here? default: sendme(which, str, 1); } @@ -334,7 +336,8 @@ net_send_string(int fd, char *str, int format) PUBLIC int readline2(comstr_t *cs, int who) { - int howmany, state, fd, v_pending; + int bytes_received, state, fd, v_pending; + ssize_t ret; static const uint8_t ayt[] = "[Responding to AYT: Yes, I'm here.]\n"; static const uint8_t will_sga[] = { IAC, WILL, TELOPT_SGA, '\0' }; static const uint8_t will_tm[] = { IAC, WILL, TELOPT_TM, '\0' }; @@ -352,27 +355,34 @@ readline2(comstr_t *cs, int who) v_pending = con[who].numPending; fd = con[who].fd; - if ((howmany = recv(fd, start + v_pending, MAX_STRING_LENGTH - 1 - - v_pending, 0)) == 0) { // error: they've disconnected + ret = recv(fd, start + v_pending, MAX_STRING_LENGTH - 1 - v_pending, 0); + if (ret < INT_MIN || ret > INT_MAX) + errx(1, "%s: return out of bounds", __func__); + bytes_received = (int)ret; + + if (bytes_received == 0) { // error: they've disconnected return -1; - } else if (howmany == -1) { + } else if (bytes_received == -1) { if (errno != EWOULDBLOCK) { // some other error return -1; } else if (con[who].processed) { // nothing new and nothing old return 0; } else { // nothing new // but some unprocessed old - howmany = 0; + bytes_received = 0; } } if (con[who].processed) s += v_pending; - else - howmany += v_pending; + else { + if (bytes_received > INT_MAX - v_pending) + errx(1, "%s: integer overflow", __func__); + bytes_received += v_pending; + } d = s; - for (; howmany-- > 0; s++) { + while (bytes_received-- > 0) { switch (state) { case 0: // haven't skipped over any control chars or // telnet commands @@ -383,10 +393,10 @@ readline2(comstr_t *cs, int who) *s = '\0'; msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s", start); - if (howmany) - memmove(start, s + 1, howmany); + if (bytes_received > 0) + memmove(start, s + 1, bytes_received); con[who].state = 0; - con[who].numPending = howmany; + con[who].numPending = bytes_received; con[who].processed = 0; con[who].outPos = 0; return 1; @@ -422,10 +432,10 @@ readline2(comstr_t *cs, int who) *d = '\0'; msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s", start); - if (howmany) - memmove(start, s + 1, howmany); + if (bytes_received > 0) + memmove(start, s + 1, bytes_received); con[who].state = 0; - con[who].numPending = howmany; + con[who].numPending = bytes_received; con[who].processed = 0; con[who].outPos = 0; return 1; @@ -448,7 +458,9 @@ readline2(comstr_t *cs, int who) state = 2; break; } - } + + s++; + } // while if (state == 0) d = s; diff --git a/FICS/obsproc.c b/FICS/obsproc.c index 966e30e..8c7194c 100644 --- a/FICS/obsproc.c +++ b/FICS/obsproc.c @@ -32,12 +32,15 @@ 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" @@ -152,6 +155,9 @@ com_games(int p, param_list param) wp = garray[i].white; bp = garray[i].black; + UNUSED_VAR(wp); + UNUSED_VAR(bp); + if ((!selected) && s && strncasecmp(s, garray[i].white_name, slen) && @@ -380,9 +386,8 @@ com_allobservers(int p, param_list param) start = 0; end = g_num; } else if ((obgame >= g_num) || - ((obgame < g_num) && - ((garray[obgame].status != GAME_ACTIVE) && - (garray[obgame].status != GAME_EXAMINE)))) { + (garray[obgame].status != GAME_ACTIVE && + garray[obgame].status != GAME_EXAMINE)) { pprintf(p, "There is no such game.\n"); return COM_OK; } else { @@ -980,11 +985,16 @@ FindHistory(int p, int p1, int p_game) ret = fscanf(fpHist, "%d %*c %*d %*c %*d %*s %*s %*d %*d %*d " "%*d %*s %*s %ld", &index, &when); - if (ret != 2) - warn("%s: %s: corrupt", __func__, &fileName[0]); - } while (!feof(fpHist) && index != p_game); + if (ret != 2) { + warnx("%s: %s: corrupt", __func__, fileName); + fclose(fpHist); + return NULL; + } + } while (!feof(fpHist) && + !ferror(fpHist) && + index != p_game); - if (feof(fpHist)) { + if (feof(fpHist) || ferror(fpHist)) { pprintf(p, "There is no history game %d for %s.\n", p_game, parray[p1].name); fclose(fpHist); @@ -993,6 +1003,12 @@ FindHistory(int p, int p1, int p_game) 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]); @@ -1003,6 +1019,7 @@ 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]; @@ -1019,11 +1036,16 @@ FindHistory2(int p, int p1, int p_game, char *End, const size_t End_size) "%%*d %%*d %%*d %%*s %%%zus %%ld\n", (End_size - 1)); do { - if (fscanf(fpHist, fmt, &index, End, &when) != 3) - warn("%s: %s: corrupt", __func__, &fileName[0]); - } while (!feof(fpHist) && index != p_game); + 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)) { + if (feof(fpHist) || ferror(fpHist)) { pprintf(p, "There is no history game %d for %s.\n", p_game, parray[p1].name); fclose(fpHist); @@ -1032,8 +1054,31 @@ FindHistory2(int p, int p1, int p_game, char *End, const size_t End_size) 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]); } diff --git a/FICS/playerdb.c b/FICS/playerdb.c index 309a8fc..9919bfd 100644 --- a/FICS/playerdb.c +++ b/FICS/playerdb.c @@ -43,6 +43,12 @@ Markus Uhlin 25/03/29 player_remove_request: fixed overflowed array index read/write. + Markus Uhlin 25/04/02 add_to_list: added an upper + limit for the list size. + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. + Markus Uhlin 25/07/28 Restricted file permissions upon + creation. + Markus Uhlin 25/07/30 Usage of 'int64_t'. */ #include "stdinclude.h" @@ -50,6 +56,8 @@ #include <err.h> #include <errno.h> +#include <fcntl.h> +#include <inttypes.h> #include <stdint.h> #include "command.h" @@ -59,6 +67,7 @@ #include "ficsmain.h" #include "gamedb.h" #include "lists.h" +#include "maxxes-utils.h" #include "network.h" #include "playerdb.h" #include "ratings.h" @@ -409,10 +418,12 @@ add_to_list(FILE *fp, enum ListWhich lw, int *size, int p) #define SCAN_STR "%1023s" - if (*size <= 0) + if (*size <= 0 || *size > MAX_GLOBAL_LIST_SIZE) return -2; + while ((*size)-- > 0 && fscanf(fp, SCAN_STR, buf) == 1) list_add(p, lw, buf); + return (*size <= 0 ? 0 : -1); } @@ -435,7 +446,7 @@ ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version) /* * Name */ - if (fgets(tmp2, sizeof tmp2, fp) != NULL && + if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT strcmp(tmp2, "NONE\n") != 0) { tmp2[strcspn(tmp2, "\n")] = '\0'; pp->name = xstrdup(tmp2); @@ -446,7 +457,7 @@ ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version) /* * Full name */ - if (fgets(tmp2, sizeof tmp2, fp) != NULL && + if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT strcmp(tmp2, "NONE\n") != 0) { tmp2[strcspn(tmp2, "\n")] = '\0'; pp->fullName = xstrdup(tmp2); @@ -457,7 +468,7 @@ ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version) /* * Password */ - if (fgets(tmp2, sizeof tmp2, fp) != NULL && + if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT strcmp(tmp2, "NONE\n") != 0) { tmp2[strcspn(tmp2, "\n")] = '\0'; pp->passwd = xstrdup(tmp2); @@ -468,7 +479,7 @@ ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version) /* * Email */ - if (fgets(tmp2, sizeof tmp2, fp) != NULL && + if (fgets(tmp2, sizeof tmp2, fp) != NULL && // NOLINT strcmp(tmp2, "NONE\n") != 0) { tmp2[strcspn(tmp2, "\n")] = '\0'; pp->emailAddress = xstrdup(tmp2); @@ -476,7 +487,9 @@ ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version) pp->emailAddress = NULL; } - if (fscanf(fp, "%d %d %d %d %d %d %jd %d %jd %d %d %d %d %d %d %jd %d %jd " + if (feof(fp) || + ferror(fp) || + fscanf(fp, "%d %d %d %d %d %d %jd %d %jd %d %d %d %d %d %d %jd %d %jd " "%d %d %d %d %d %d %jd %d %jd %d %d %d %d %d %d %jd %d %jd %d %d %d %d " "%d %d %jd %d %jd %u\n", &pp->s_stats.num, &pp->s_stats.win, &pp->s_stats.los, @@ -578,6 +591,23 @@ ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version) pp->timeOfReg = array[0]; pp->totalTime = array[1]; + if (pp->num_plan >= MAX_PLAN) { + warnx("Player %s is corrupt\nToo many plans (%d)", + parray[p].name, + pp->num_plan); + return; + } else if (pp->num_formula >= MAX_FORMULA) { + warnx("Player %s is corrupt\nToo many formulas (%d)", + parray[p].name, + pp->num_formula); + return; + } else if (pp->numAlias >= MAX_ALIASES) { + warnx("Player %s is corrupt\nToo many aliases (%d)", + parray[p].name, + pp->numAlias); + return; + } + if (pp->num_plan > 0) { for (i = 0; i < pp->num_plan; i++) { if (fgets(tmp2, sizeof tmp2, fp) == NULL) { @@ -642,14 +672,13 @@ ReadV1PlayerFmt(int p, player *pp, FILE *fp, char *file, int version) return; } - if (!(len = strlen(tmp2))) { + if (!strlen(tmp2)) { // XXX fprintf(stderr, "FICS: Error bad alias in " "file %s\n", file); i--; pp->numAlias--; } else { tmp2[strcspn(tmp2, "\n")] = '\0'; - tmp = tmp2; tmp = eatword(tmp2); *tmp = '\0'; tmp++; @@ -834,9 +863,11 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) * num_plan */ - parray[p].num_plan = atoi(value); - - if (parray[p].num_plan > 0) { + if ((parray[p].num_plan = atoi(value)) >= MAX_PLAN) { + warnx("%s: %s: too many plans (%d)", __func__, file, + parray[p].num_plan); + return -1; + } else if (parray[p].num_plan > 0) { for (i = 0; i < parray[p].num_plan; i++) { if (fgets(tmp, sizeof tmp, fp) == NULL) { @@ -858,15 +889,19 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) xstrdup(tmp) : NULL); } } + } else { + /* null */; } } else if (!strcmp(attr, "num_formula:")) { /* * num_formula */ - parray[p].num_formula = atoi(value); - - if (parray[p].num_formula > 0) { + if ((parray[p].num_formula = atoi(value)) >= MAX_FORMULA) { + warnx("%s: %s: too many formulas (%d)", __func__, file, + parray[p].num_formula); + return -1; + } else if (parray[p].num_formula > 0) { for (i = 0; i < parray[p].num_formula; i++) { if (fgets(tmp, sizeof tmp, fp) == NULL) { warnx("%s: bad formula: feof %s", @@ -887,6 +922,8 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) xstrdup(tmp) : NULL); } } + } else { + /* null */; } } else if (!strcmp(attr, "formula:")) { /* @@ -899,9 +936,11 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) * num_alias */ - parray[p].numAlias = atoi(value); - - if (parray[p].numAlias > 0) { + if ((parray[p].numAlias = atoi(value)) >= MAX_ALIASES) { + warnx("%s: %s: too many aliases (%d)", __func__, file, + parray[p].numAlias); + return -1; + } else if (parray[p].numAlias > 0) { for (i = 0; i < parray[p].numAlias; i++) { if (fgets(tmp, sizeof tmp, fp) == NULL) { warnx("%s: bad alias: feof %s", @@ -909,7 +948,7 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) return -1; } - if (!(len = strlen(tmp))) { + if (!strlen(tmp)) { // XXX fprintf(stderr, "FICS: Error bad alias " "in file %s\n", file); i--; @@ -928,13 +967,21 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) xstrdup(tmp1); } } + } else { + /* null */; } } else if (!strcmp(attr, "num_censor:")) { /* * num_censor */ - i = atoi(value); + if ((i = atoi(value)) < 0) { + warnx("%s: num censor negative", __func__); + return -1; + } else if (i > MAX_CENSOR) { + warnx("%s: num censor too large", __func__); + return -1; + } while (i--) { if (fgets(tmp, sizeof tmp, fp) == NULL) { @@ -953,7 +1000,13 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) } } } else if (!strcmp(attr, "num_notify:")) { - i = atoi(value); + if ((i = atoi(value)) < 0) { + warnx("%s: num notify negative", __func__); + return -1; + } else if (i > MAX_NOTIFY) { + warnx("%s: num notify too large", __func__); + return -1; + } while (i--) { if (fgets(tmp, sizeof tmp, fp) == NULL) { @@ -972,7 +1025,10 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) } } } else if (!strcmp(attr, "num_noplay:")) { - i = atoi(value); + if ((i = atoi(value)) < 0) { + warnx("%s: num noplay negative", __func__); + return -1; + } while (i--) { if (fgets(tmp, sizeof tmp, fp) == NULL) { @@ -991,7 +1047,10 @@ got_attr_value_player(int p, char *attr, char *value, FILE *fp, char *file) } } } else if (!strcmp(attr, "num_gnotify:")) { - i = atoi(value); + if ((i = atoi(value)) < 0) { + warnx("%s: num gnotify negative", __func__); + return -1; + } while (i--) { if (fgets(tmp, sizeof tmp, fp) == NULL) { @@ -1021,17 +1080,35 @@ PUBLIC int player_read(int p, char *name) { FILE *fp = NULL; - char *attr, *value; char fname[MAX_FILENAME_SIZE] = { '\0' }; char line[MAX_LINE_SIZE] = { '\0' }; + char *attr, *value; + char *resolvedPath = NULL; int len = 0; int version = 0; - parray[p].login = stolower(xstrdup(name)); + parray[p].login = stolower(xstrdup(name)); // free on error? + + if (!is_valid_login_name(parray[p].login)) { + warnx("%s: invalid login name: %s", __func__, parray[p].login); + return -1; + } snprintf(fname, sizeof fname, "%s/%c/%s", player_dir, parray[p].login[0], parray[p].login); + if ((resolvedPath = realpath(fname, NULL)) != NULL) { + if (strncmp(resolvedPath, player_dir, + strlen(player_dir)) != 0) { + warnx("%s: path traversal detected", __func__); + free(resolvedPath); + return -1; + } + mstrlcpy(fname, resolvedPath, sizeof fname); + free(resolvedPath); + resolvedPath = NULL; + } + if ((fp = fopen(fname, "r")) == NULL) { // Unregistered player parray[p].name = xstrdup(name); parray[p].registered = 0; @@ -1119,6 +1196,7 @@ player_markdeleted(int p) FILE *fp; char fname[MAX_FILENAME_SIZE]; char fname2[MAX_FILENAME_SIZE]; + int fd; if (!parray[p].registered) // Player must not be registered return -1; @@ -1129,9 +1207,17 @@ player_markdeleted(int p) parray[p].login[0], parray[p].login); xrename(__func__, fname, fname2); - if ((fp = fopen(fname2, "a")) != NULL) { // Touch the file + errno = 0; + fd = open(fname2, g_open_flags[0], g_open_modes); + + if (fd < 0) { + warn("%s: open", __func__); + return -1; + } else if ((fp = fdopen(fd, "a")) != NULL) { // Touch the file fprintf(fp, "\n"); fclose(fp); + } else { + close(fd); } return 0; @@ -1235,6 +1321,7 @@ player_save(int p) { FILE *fp; char fname[MAX_FILENAME_SIZE]; + int fd; if (!player_num_ok_chk(p)) { warnx("%s: invalid player number %d", __func__, p); @@ -1259,9 +1346,16 @@ player_save(int p) snprintf(fname, sizeof fname, "%s/%c/%s", player_dir, parray[p].login[0], parray[p].login); - if ((fp = fopen(fname, "w")) == NULL) { + errno = 0; + fd = open(fname, g_open_flags[1], g_open_modes); + + if (fd < 0) { warn("%s: Problem opening file %s for write", __func__, fname); return -1; + } else if ((fp = fdopen(fd, "w")) == NULL) { + warn("%s: Problem opening file %s for write", __func__, fname); + close(fd); + return -1; } WritePlayerFile(fp, p); @@ -1546,10 +1640,20 @@ player_ontime(int p) PRIVATE void write_p_inout(int inout, int p, char *file, int maxlines) { - FILE *fp; + FILE *fp; + int fd; - if ((fp = fopen(file, "a")) == NULL) + errno = 0; + fd = open(file, g_open_flags[0], g_open_modes); + + if (fd < 0) { + warn("%s: open", __func__); + return; + } else if ((fp = fdopen(fd, "a")) == NULL) { + warn("%s: fdopen", __func__); + close(fd); return; + } fprintf(fp, "%d %s %ld %d %s\n", inout, parray[p].name, (long int)time(NULL), parray[p].registered, @@ -1608,7 +1712,7 @@ player_lastconnect(int p) char loginName[MAX_LOGIN_NAME]; int inout, registered; int ret, too_long; - long int lval; + int64_t lval = 0; time_t last = 0; ret = snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", @@ -1634,8 +1738,8 @@ player_lastconnect(int p) _Static_assert(19 < ARRAY_SIZE(ipstr), "'ipstr' too small"); - if (fscanf(fp, "%d %19s %ld %d %19s\n", &inout, loginName, - &lval, ®istered, ipstr) != 5) { + if (fscanf(fp, ("%d %19s " "%" SCNd64 " %d %19s\n"), &inout, + loginName, &lval, ®istered, ipstr) != 5) { fprintf(stderr, "FICS: Error in login info format. %s" "\n", fname); fclose(fp); @@ -1656,7 +1760,7 @@ player_lastdisconnect(int p) char loginName[MAX_LOGIN_NAME]; int inout, registered; int ret, too_long; - long int lval; + int64_t lval; time_t last = 0; ret = snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", @@ -1677,8 +1781,8 @@ player_lastdisconnect(int p) _Static_assert(19 < ARRAY_SIZE(ipstr), "'ipstr' too small"); - if (fscanf(fp, "%d %19s %ld %d %19s\n", &inout, loginName, - &lval, ®istered, ipstr) != 5) { + if (fscanf(fp, ("%d %19s " "%" SCNd64 " %d %19s\n"), &inout, + loginName, &lval, ®istered, ipstr) != 5) { fprintf(stderr, "FICS: Error in login info format. %s" "\n", fname); fclose(fp); @@ -2315,7 +2419,6 @@ player_goto_next_board(int p) on = parray[p].simul_info.onBoard; start = on; - g = -1; do { on++; @@ -2344,7 +2447,6 @@ player_goto_prev_board(int p) on = parray[p].simul_info.onBoard; start = on; - g = -1; do { --on; @@ -2520,9 +2622,10 @@ PUBLIC int player_add_message(int top, int fromp, char *message) { FILE *fp; - char fname[MAX_FILENAME_SIZE]; - char messbody[1024]; - char subj[256]; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + char messbody[1024] = { '\0' }; + char subj[256] = { '\0' }; + int fd; time_t t = time(NULL); if (!parray[top].registered) @@ -2535,8 +2638,16 @@ player_add_message(int top, int fromp, char *message) if (lines_file(fname) >= MAX_MESSAGES && parray[top].adminLevel == 0) return -1; - if ((fp = fopen(fname, "a")) == NULL) + errno = 0; + fd = open(fname, g_open_flags[0], g_open_modes); + + if (fd < 0) return -1; + else if ((fp = fdopen(fd, "a")) == NULL) { + close(fd); + return -1; + } + fprintf(fp, "%s at %s: %s\n", parray[fromp].name, strltime(&t), message); fclose(fp); @@ -2699,13 +2810,21 @@ PRIVATE int WriteMsgFile(int p, textlist *Head) { FILE *fp; - char fName[MAX_FILENAME_SIZE]; + char fName[MAX_FILENAME_SIZE] = { '\0' }; + int fd; textlist *Cur; GetMsgFile(p, fName, sizeof fName, __func__); - if ((fp = fopen(fName, "w")) == NULL) + errno = 0; + fd = open(fName, g_open_flags[1], g_open_modes); + + if (fd < 0) return 0; + else if ((fp = fdopen(fd, "w")) == NULL) { + close(fd); + return 0; + } for (Cur = Head; Cur != NULL; Cur = Cur->next) fprintf(fp, "%s", Cur->text); fclose(fp); @@ -2783,7 +2902,7 @@ player_show_messages(int p) PUBLIC int ShowMsgsBySender(int p, param_list param) { - int nFrom, nTo; + int nFrom = -1, nTo = -1; int p1, connected; textlist *Head; @@ -2796,8 +2915,6 @@ ShowMsgsBySender(int p, param_list param) return -1; /* no need to disconnect */ } - nFrom = nTo = -1; - if (p != p1) { if ((nTo = LoadMsgs(p1, p + 1, &Head)) <= 0) { pprintf(p, "%s has no messages from you.\n", @@ -3110,7 +3227,8 @@ PUBLIC int player_add_comment(int p_by, int p_to, char *comment) { FILE *fp; - char fname[MAX_FILENAME_SIZE]; + char fname[MAX_FILENAME_SIZE] = { '\0' }; + int fd; time_t t = time(NULL); if (!parray[p_to].registered) @@ -3119,8 +3237,17 @@ player_add_comment(int p_by, int p_to, char *comment) snprintf(fname, sizeof fname, "%s/player_data/%c/%s.%s", stats_dir, parray[p_to].login[0], parray[p_to].login, "comments"); - if ((fp = fopen(fname, "a")) == NULL) + errno = 0; + fd = open(fname, g_open_flags[0], g_open_modes); + + if (fd < 0) { + warn("%s: open", __func__); + return -1; + } else if ((fp = fdopen(fd, "a")) == NULL) { + warn("%s: fdopen", __func__); + close(fd); return -1; + } fprintf(fp, "%s at %s: %s\n", parray[p_by].name, strltime(&t), comment); fclose(fp); diff --git a/FICS/ratings.c b/FICS/ratings.c index 3a7cdd0..a85776d 100644 --- a/FICS/ratings.c +++ b/FICS/ratings.c @@ -32,6 +32,11 @@ fixed ignored retvals. Markus Uhlin 24/11/28 Added null checks Markus Uhlin 25/03/16 Fixed use of 32-bit 'time_t'. + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. + Markus Uhlin 25/07/28 Fixed missing return-value check + for a 'scanf'-like function. + Markus Uhlin 25/07/28 Restricted file permissions upon + creation. */ #include "stdinclude.h" @@ -39,11 +44,14 @@ #include <err.h> #include <errno.h> +#include <fcntl.h> +#include <limits.h> #include <stdint.h> #include "command.h" #include "comproc.h" #include "config.h" +#include "ficsmain.h" #include "gamedb.h" #include "lists.h" #include "playerdb.h" @@ -347,49 +355,52 @@ load_ratings(void) return; } - for (int i = 0; i < MAXHIST; i++) { - int ret, errno_save; + for (int i = 0; i < MAXHIST && !feof(fp) && !ferror(fp); i++) { + int ret; sHist[i] = bHist[i] = wHist[i] = lHist[i] = 0; - errno = 0; ret = fscanf(fp, "%d %d %d %d", &sHist[i], &bHist[i], &wHist[i], &lHist[i]); - errno_save = errno; if (ret != 4) { - if (feof(fp) || ferror(fp)) - break; - errno = errno_save; - warn("%s: too few items assigned (iteration: %d)", - __func__, i); + warnx("%s: %s: too few items assigned (iteration: %d)", + __func__, fname, i); + fclose(fp); + return; } } + if (ferror(fp)) { + warnx("%s: %s: the error indicator is set", __func__, fname); + fclose(fp); + return; + } + fclose(fp); - if (Rs_count) { - Ratings_S_StdDev = sqrt(Rs_S / Rs_count); + if (Rs_count != 0) { + Ratings_S_StdDev = sqrt(Rs_S / Rs_count); // NOLINT Ratings_S_Average = (Rs_total / (double)Rs_count); } else { Ratings_S_StdDev = 0; Ratings_S_Average = 0; } - if (Rb_count) { - Ratings_B_StdDev = sqrt(Rb_S / Rb_count); + if (Rb_count != 0) { + Ratings_B_StdDev = sqrt(Rb_S / Rb_count); // NOLINT Ratings_B_Average = (Rb_total / (double)Rb_count); } else { Ratings_B_StdDev = 0; Ratings_B_Average = 0; } - if (Rw_count) { - Ratings_W_StdDev = sqrt(Rw_S / Rw_count); + if (Rw_count != 0) { + Ratings_W_StdDev = sqrt(Rw_S / Rw_count); // NOLINT Ratings_W_Average = (Rw_total / (double)Rw_count); } else { Ratings_W_StdDev = 0; Ratings_W_Average = 0; } - if (Rl_count) { - Ratings_L_StdDev = sqrt(Rl_S / Rl_count); + if (Rl_count != 0) { + Ratings_L_StdDev = sqrt(Rl_S / Rl_count); // NOLINT Ratings_L_Average = (Rl_total / (double)Rl_count); } else { Ratings_L_StdDev = 0; @@ -402,13 +413,21 @@ save_ratings(void) { FILE *fp; char fname[MAX_FILENAME_SIZE] = { '\0' }; + int fd; snprintf(fname, sizeof fname, "%s/newratingsV%d_data", stats_dir, STATS_VERSION); - if ((fp = fopen(fname, "w")) == NULL) { + errno = 0; + fd = open(fname, g_open_flags[1], g_open_modes); + + if (fd < 0) { warn("%s: can't write ratings data", __func__); return; + } else if ((fp = fdopen(fd, "w")) == NULL) { + warn("%s: can't write ratings data", __func__); + close(fd); + return; } fprintf(fp, "%10f %10f %10f %d\n", Rb_M, Rb_S, Rb_total, Rb_count); @@ -1303,6 +1322,18 @@ com_statistics(int p, param_list param) return COM_OK; } +/* + * Return the difference of 'a - b' + */ +PUBLIC int +int_diff(const char *fn, const int a, const int b) +{ + if ((b > 0 && a < INT_MIN + b) || + (b < 0 && a > INT_MAX + b)) + errx(1, "%s: integer overflow (%d - %d)", fn, a, b); + return (a - b); +} + PUBLIC int com_fixrank(int p, param_list param) { @@ -1351,10 +1382,8 @@ DisplayRank(int p, param_list param, int showComputers) end = -1; ret = sscanf(param[0].val.word, "%d-%d", &start, &end); - if (ret != 2) { -// warnx("%s: sscanf() == %d", __func__, ret); + if (ret != 2) return COM_FAILED; - } if (end > 0 && (param[1].type != TYPE_NULL)) show = ShowFromString(param[1].val.word); @@ -1458,7 +1487,8 @@ UpdateRank(int type, char *addName, statistics *sNew, char *delName) char command[MAX_STRING_LENGTH]; char line[MAX_RANK_LINE] = { '\0' }; char login[MAX_LOGIN_NAME] = { '\0' }; - int comp; + int comp = 0; + int fd = -1; statistics sCur; if (GetRankFileName(RankFile, sizeof RankFile, type) < 0) @@ -1471,9 +1501,17 @@ UpdateRank(int type, char *addName, statistics *sNew, char *delName) snprintf(TmpRankFile, sizeof TmpRankFile, "%s/tmpRank", sdir); - if ((fptemp = fopen(TmpRankFile, "w")) == NULL) { + errno = 0; + fd = open(TmpRankFile, g_open_flags[1], g_open_modes); + + if (fd < 0) { + warn("%s: open", __func__); + fclose(fp); + return; + } else if ((fptemp = fdopen(fd, "w")) == NULL) { warn("%s: unable to open rank file for updating", __func__); fclose(fp); + close(fd); return; } @@ -1550,7 +1588,8 @@ GetRank(FILE *fp, char *target, int countComp) char line[MAX_RANK_LINE] = { '\0' }; char login[MAX_LOGIN_NAME] = { '\0' }; int count = 0; - int nGames, is_computer; + int is_computer = 0; + int nGames = 0; int playerFound = 0; while (fgets(line, sizeof line, fp) != NULL && @@ -1558,8 +1597,7 @@ GetRank(FILE *fp, char *target, int countComp) _Static_assert(ARRAY_SIZE(login) > 19, "'login' too small"); if (sscanf(line, "%19s %*d %d %d", login, &nGames, &is_computer) - != 1) { -// warnx("%s: sscanf() error", __func__); + < 3) { continue; } @@ -1582,16 +1620,20 @@ PositionFilePtr(FILE *fp, int count, int *last, int *nTied, int showComp) return; rating = nGames = is_computer = 0; + errno = 0; rewind(fp); + if (errno) { + warn("%s: rewind", __func__); + return; + } for (int i = 1; i < count; i++) { do { _Static_assert(ARRAY_SIZE(login) > 19, "'login' too small"); - if (fgets(line, sizeof line, fp) == NULL || - feof(fp) || - ferror(fp)) + if (feof(fp) || ferror(fp) || + fgets(line, sizeof line, fp) == NULL) break; else if (sscanf(line, "%19s %d %d %d", login, &rating, &nGames, &is_computer) != 4) { @@ -1600,6 +1642,11 @@ PositionFilePtr(FILE *fp, int count, int *last, int *nTied, int showComp) } } while (!CountRankLine(showComp, login, nGames, is_computer)); + if (ferror(fp)) { + warnx("%s: the error indicator is set", __func__); + return; + } + if (rating != *last) { *nTied = 1; *last = rating; @@ -1618,7 +1665,7 @@ ShowRankEntry(int p, FILE *fp, int count, int comp, char *target, // XXX rating = 0; - findable = (count > 0 && !feof(fp)); + findable = (count > 0 && !feof(fp) && !ferror(fp)); nGames = 0; is_comp = 0; @@ -1815,9 +1862,9 @@ DisplayTargetRank(int p, char *target, int show, int showComp) numAbove = CountAbove(numToShow, blitzRank, stdRank, wildRank, show); - blitzCount = (blitzRank - numAbove); - stdCount = (stdRank - numAbove); - wildCount = (wildRank - numAbove); + blitzCount = int_diff(__func__, blitzRank, numAbove); + stdCount = int_diff(__func__, stdRank, numAbove); + wildCount = int_diff(__func__, wildRank, numAbove); ShowRankLines(p, fb, fs, fw, blitzCount, stdCount, wildCount, numToShow, showComp, show, target); diff --git a/FICS/ratings.h b/FICS/ratings.h index 4cf47a8..7f2a521 100644 --- a/FICS/ratings.h +++ b/FICS/ratings.h @@ -26,6 +26,8 @@ #ifndef _RATINGS_H #define _RATINGS_H +#include "common.h" + #define STATS_VERSION 2 #define RESULT_WIN 0 @@ -49,6 +51,7 @@ typedef struct _rateStruct { int rating; } rateStruct; +__FICS_BEGIN_DECLS extern int Best(int, param_list, int); extern int DisplayRank(int, param_list, int); extern int DisplayRankedPlayers(int, int, int, int, int); @@ -61,6 +64,7 @@ extern int com_hbest(int, param_list); extern int com_hrank(int, param_list); extern int com_rank(int, param_list); extern int com_statistics(int, param_list); +extern int int_diff(const char *, const int, const int); extern int is_active(int); extern int rating_delta(int, int, int, int, int); extern int rating_update(int); @@ -72,5 +76,6 @@ extern void rating_recalc(void); extern void rating_remove(int, int); extern void rating_sterr_delta(int, int, int, time_t, int, int *, double *); extern void save_ratings(void); +__FICS_END_DECLS #endif /* _RATINGS_H */ diff --git a/FICS/talkproc.c b/FICS/talkproc.c index 3d16be1..601f43b 100644 --- a/FICS/talkproc.c +++ b/FICS/talkproc.c @@ -34,6 +34,8 @@ Markus Uhlin 24/04/14 Refactored and reformatted ALL functions. Markus Uhlin 25/01/18 Fixed -Wshadow + Markus Uhlin 25/04/04 tell: fixed constant expression + result */ #include "stdinclude.h" @@ -296,8 +298,8 @@ tell(int p, int p1, char *msg, int why, int ch) } if (player_censored(p1, p) && parray[p].adminLevel == 0) { - if (why != TELL_KIBITZ || - why != TELL_WHISPER || + if (why != TELL_KIBITZ && + why != TELL_WHISPER && why != TELL_CHANNEL) { pprintf(p, "Player \"%s\" is censoring you.\n", parray[p1].name); diff --git a/FICS/utils.c b/FICS/utils.c index ce02a5d..4da2411 100644 --- a/FICS/utils.c +++ b/FICS/utils.c @@ -38,14 +38,25 @@ in truncate_file(). Markus Uhlin 25/03/09 truncate_file: fixed null ptr dereference. + Markus Uhlin 25/04/06 Fixed Clang Tidy warnings. + Markus Uhlin 25/07/21 Replaced non-reentrant functions + with their corresponding thread + safe version. + Markus Uhlin 25/07/28 truncate_file: restricted file + permissions upon creation. */ #include "stdinclude.h" #include "common.h" +#include <ctype.h> #include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> #include "config.h" +#include "ficsmain.h" #include "network.h" #include "playerdb.h" #include "rmalloc.h" @@ -70,6 +81,49 @@ struct t_dirs { PRIVATE char** t_buffer = NULL; PRIVATE int t_buffersize = 0; +PUBLIC bool +is_valid_filename(const char *name, const bool allow_hidden) +{ + if (name == NULL || strcmp(name, "") == 0) + return false; + if (!allow_hidden && name[0] == '.') + return false; + if (strstr(name, "..") || strchr(name, '/') || strchr(name, '\\') || + name[0] == '/') + return false; + for (const char *p = name; *p; ++p) { + if (isspace((unsigned char)*p) || iscntrl((unsigned char)*p)) + return false; + } + for (const char *p = name; *p; ++p) { + if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && + *p != '.') + return false; + } + return true; +} + +/* + * Function to validate login names for file path usage + */ +PUBLIC bool +is_valid_login_name(const char *login) +{ + if (login == NULL || login[0] == '\0' || + strstr(login, "..") || strchr(login, '/') || strchr(login, '\\')) + return false; + + for (const char *p = login; *p; ++p) { + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || + *p == '_')) + return false; + } + + return true; +} + PUBLIC int count_lines(FILE *fp) { @@ -275,10 +329,10 @@ pcommand(int p, char *comstr, ...) return retval; } -PUBLIC int +PUBLIC void pprintf(int p, const char *format, ...) { - char tmp[10 * MAX_LINE_SIZE]; + char tmp[10 * MAX_LINE_SIZE] = { '\0' }; int retval; va_list ap; @@ -286,8 +340,8 @@ pprintf(int p, const char *format, ...) retval = vsnprintf(tmp, sizeof tmp, format, ap); va_end(ap); + UNUSED_VAR(retval); net_send_string(parray[p].socket, tmp, 1); - return retval; } PRIVATE void @@ -393,7 +447,7 @@ pprintf_noformat(int p, char *format, ...) } PUBLIC int -psend_raw_file(int p, char *dir, char *file) +psend_raw_file(int p, const char *dir, const char *file) { FILE *fp; char fname[MAX_FILENAME_SIZE] = { '\0' }; @@ -408,9 +462,18 @@ psend_raw_file(int p, char *dir, char *file) if ((fp = fopen(fname, "r")) == NULL) return -1; - while ((num = fread(tmp, sizeof(char), MAX_LINE_SIZE - 1, fp)) > 0) { - tmp[num] = '\0'; - net_send_string(parray[p].socket, tmp, 1); + while (!feof(fp) && !ferror(fp)) { + if ((num = fread(tmp, sizeof(char), MAX_LINE_SIZE - 1, + fp)) > 0) { + tmp[num] = '\0'; + net_send_string(parray[p].socket, tmp, 1); + } + } + + if (ferror(fp)) { + warnx("%s: %s: the error indicator is set", __func__, fname); + fclose(fp); + return -1; } fclose(fp); @@ -418,7 +481,7 @@ psend_raw_file(int p, char *dir, char *file) } PUBLIC int -psend_file(int p, char *dir, char *file) +psend_file(int p, const char *dir, const char *file) { FILE *fp; char fname[MAX_FILENAME_SIZE] = { '\0' }; @@ -445,6 +508,12 @@ psend_file(int p, char *dir, char *file) } if (!feof(fp)) { + if (ferror(fp)) { + warnx("%s: %s: the error indicator is set", __func__, + fname); + fclose(fp); + return -1; + } parray[p].last_file = xstrdup(fname); parray[p].last_file_byte = ftell(fp); pprintf(p, "Type [next] to see next page.\n"); @@ -513,8 +582,17 @@ pmore_file(int p) } if (!feof(fp)) { - parray[p].last_file_byte = ftell(fp); - pprintf(p, "Type [next] to see next page.\n"); + if (ferror(fp)) { + warnx("%s: %s: the error indicator is set", __func__, + parray[p].last_file); + fclose(fp); + return -1; + } else if ((parray[p].last_file_byte = ftell(fp)) == -1) { + warn("%s: %s: ftell", __func__, parray[p].last_file); + fclose(fp); + return -1; + } else + pprintf(p, "Type [next] to see next page.\n"); } else { rfree(parray[p].last_file); parray[p].last_file = NULL; @@ -729,19 +807,31 @@ fix_time(char *old_time) } PUBLIC char * -strltime(time_t *clock) +strltime(time_t *p_clock) { - struct tm *stm = localtime(clock); + struct tm stm = {0}; + + errno = 0; - return strtime(stm); + if (localtime_r(p_clock, &stm) == NULL) { + warn("%s: localtime_r", __func__); + memset(&stm, 0, sizeof stm); + } + return strtime(&stm); } PUBLIC char * -strgtime(time_t *clock) +strgtime(time_t *p_clock) { - struct tm *stm = gmtime(clock); + struct tm stm = {0}; + + errno = 0; - return strtime(stm); + if (gmtime_r(p_clock, &stm) == NULL) { + warn("%s: gmtime_r", __func__); + memset(&stm, 0, sizeof stm); + } + return strtime(&stm); } /* @@ -813,8 +903,17 @@ truncate_file(char *file, int lines) fclose(fp); if (trunc) { - if ((fp = fopen(file, "w")) == NULL) { - warn("%s: fopen", __func__); + int fd; + + errno = 0; + fd = open(file, g_open_flags[1], g_open_modes); + + if (fd < 0) { + warn("%s: open", __func__); + return 1; + } else if ((fp = fdopen(fd, "w")) == NULL) { + warn("%s: fdopen", __func__); + close(fd); return 1; } @@ -964,10 +1063,11 @@ ratstrii(int rat, int reg) * Fill 't_buffer' with anything matching "want*" in file tree */ PRIVATE void -t_sft(char *want, struct t_tree *t) +t_sft(const char *want, struct t_tree *t) { if (t) { - int cmp = strncmp(want, t->name, strlen(want)); + const char *v_want = (want ? want : ""); + int cmp = strncmp(v_want, t->name, strlen(v_want)); if (cmp <= 0) // If 'want' <= this one, look left t_sft(want, t->left); @@ -1050,7 +1150,6 @@ PUBLIC int search_directory(char *dir, char *filter, char **buffer, int buffersize) { int cmp; - static char nullify = '\0'; static struct t_dirs *ramdirs = NULL; struct stat statbuf; struct t_dirs** i; @@ -1059,9 +1158,6 @@ search_directory(char *dir, char *filter, char **buffer, int buffersize) t_buffersize = buffersize; if (!stat(dir, &statbuf)) { - if (filter == NULL) // NULL becomes pointer to null string - filter = &nullify; - i = &ramdirs; while (*i) { // Find dir in dir tree diff --git a/FICS/utils.h b/FICS/utils.h index 5342be2..e376a03 100644 --- a/FICS/utils.h +++ b/FICS/utils.h @@ -31,6 +31,7 @@ #ifndef _UTILS_H #define _UTILS_H +#include <stdbool.h> #include <stdint.h> #include <stdio.h> @@ -58,6 +59,8 @@ #define CheckFlag(VAR, FLAG) (VAR & (FLAG)) __FICS_BEGIN_DECLS +extern bool is_valid_filename(const char *, const bool); +extern bool is_valid_login_name(const char *); extern char *dotQuad(unsigned int); extern char *eattailwhite(char *); extern char *eatwhite(char *); @@ -90,15 +93,15 @@ extern int mail_string_to_address(char *, char *, char *); extern int mail_string_to_user(int, char *, char *); extern int pcommand(int, char *, ...) PRINTFLIKE(2); extern int pmore_file(int); -extern int pprintf(int, const char *, ...) PRINTFLIKE(2); +extern void pprintf(int, const char *, ...) PRINTFLIKE(2); extern int pprintf_highlight(int, char *, ...) PRINTFLIKE(2); extern int pprintf_noformat(int, char *, ...) PRINTFLIKE(2); extern int pprintf_prompt(int, char *, ...) PRINTFLIKE(2); extern int printablestring(char *); extern int psend_command(int, char *, char *); -extern int psend_file(int, char *, char *); +extern int psend_file(int, const char *, const char *); extern int psend_logoutfile(int, char *, char *); -extern int psend_raw_file(int, char *, char *); +extern int psend_raw_file(int, const char *, const char *); extern int psprintf_highlight(int, char *, size_t, char *, ...) PRINTFLIKE(4); extern int safechar(int); @@ -107,8 +110,6 @@ extern int search_directory(char *, char *, char **, int); extern int truncate_file(char *, int); extern time_t untenths(uint64_t); extern unsigned int tenth_secs(void); -//extern void pprintf_dohightlight(int); -//extern void sprintf_dohightlight(int, char *); __FICS_END_DECLS #endif /* _UTILS_H */ diff --git a/FICS/vers.c b/FICS/vers.c index d556d4a..c6dd99c 100644 --- a/FICS/vers.c +++ b/FICS/vers.c @@ -1,5 +1,5 @@ #include "vers.h" const char SGS_VERS[] = ""; -const char VERS_NUM[] = "rpblc-1.4.4"; +const char VERS_NUM[] = "rpblc-1.4.5"; const char COMP_DATE[] = __DATE__; |