From 0d89804dd07e811d0a8573d3a428bddc476c26bb Mon Sep 17 00:00:00 2001 From: remoitnane Date: Sat, 14 Aug 2010 01:39:32 -0700 Subject: Centralize chat validation and simplify at-command processing Signed-off-by: Chuck Miller --- src/map/atcommand.c | 16 +-- src/map/clif.c | 358 ++++++++++++++++++++++++++++++++++++++-------------- src/map/tmw.c | 3 - 3 files changed, 269 insertions(+), 108 deletions(-) (limited to 'src') diff --git a/src/map/atcommand.c b/src/map/atcommand.c index cfd46a7..8d69b21 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -710,8 +710,6 @@ AtCommandType is_atcommand (const int fd, struct map_session_data *sd, const char *message, int gmlvl) { - const char *str = message; - int s_flag = 0; AtCommandInfo info; AtCommandType type; @@ -721,22 +719,14 @@ is_atcommand (const int fd, struct map_session_data *sd, const char *message, return AtCommand_None; memset (&info, 0, sizeof (info)); - str += strlen (sd->status.name); - while (*str && (isspace (*str) || (s_flag == 0 && *str == ':'))) - { - if (*str == ':') - s_flag = 1; - str++; - } - if (!*str) - return AtCommand_None; - type = atcommand (gmlvl > 0 ? gmlvl : pc_isGM (sd), str, &info); + type = atcommand (gmlvl > 0 ? gmlvl : pc_isGM (sd), message, &info); if (type != AtCommand_None) { char command[100]; char output[200]; - const char *p = str; + const char *str = message; + const char *p = message; memset (command, '\0', sizeof (command)); memset (output, '\0', sizeof (output)); while (*p && !isspace (*p)) diff --git a/src/map/clif.c b/src/map/clif.c index 12b058b..00b3ea5 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -224,6 +224,9 @@ static int is_deaf (struct block_list *bl) static void clif_emotion_towards (struct block_list *bl, struct block_list *target, int type); +static char *clif_validate_chat (struct map_session_data *sd, int type, + char **message, size_t *message_len); + /*========================================== * clif_send��AREA*�w�莞�p *------------------------------------------ @@ -7114,46 +7117,48 @@ void clif_parse_GetCharNameRequest (int fd, struct map_session_data *sd) } /*========================================== + * Validate and process transmission of a + * global/public message. * + * (S 008c .w .?B) *------------------------------------------ */ void clif_parse_GlobalMessage (int fd, struct map_session_data *sd) -{ // S 008c .w .?B +{ + int msg_len = RFIFOW (fd, 2) - 4; /* Header (2) + length (2). */ + size_t message_len = 0; + char *buf = NULL; + char *message = NULL; /* The message text only. */ + nullpo_retv (sd); - if ((is_atcommand (fd, sd, RFIFOP (fd, 4), 0) != AtCommand_None) || (sd->sc_data && (sd->sc_data[SC_BERSERK].timer != -1 || - sd->sc_data[SC_NOCHAT].timer != -1))) + if (!(buf = clif_validate_chat (sd, 2, &message, &message_len))) + { + /* "Your message could not be sent." */ + clif_displaymessage (fd, msg_txt (505)); return; + } - if (strlen (RFIFOP (fd, 4)) >= battle_config.chat_maxline) - return; - - // Simply ignore messages with spoofed/incorrect source names. - if (strncmp (RFIFOP (fd, 4), sd->status.name, strlen (sd->status.name)) != - 0) - return; - - char *buf = (char *) malloc (RFIFOW (fd, 2) + 4); - int msg_len = RFIFOW (fd, 2) + 4; // len of message - 4 + 8 + if (is_atcommand (fd, sd, message, 0) != AtCommand_None + || (sd->sc_data && (sd->sc_data[SC_BERSERK].timer != -1 + || sd->sc_data[SC_NOCHAT].timer != -1))) + { + free (buf); + return; + } - // Prepare to send message to others - WBUFW (buf, 0) = 0x8d; - WBUFW (buf, 2) = msg_len; - WBUFL (buf, 4) = sd->bl.id; - memcpy (WBUFP (buf, 8), RFIFOP (fd, 4), RFIFOW (fd, 2) - 4); - - if (magic_message(sd, buf, msg_len) == 0) + if (!magic_message (sd, buf, msg_len)) { - if (tmw_CheckChatSpam (sd, RFIFOP (fd, 4))) { - free(buf); - return; - } + /* It's not a spell/magic message, so send the message to others. */ + WBUFW (buf, 0) = 0x8d; + WBUFW (buf, 2) = msg_len + 8; /* Header (2) + length (2) + ID (4). */ + WBUFL (buf, 4) = sd->bl.id; - clif_send (buf, WBUFW (buf, 2), &sd->bl, + clif_send (buf, msg_len + 8, &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); } - // send back message to the speaker + /* Send the message back to the speaker. */ memcpy (WFIFOP (fd, 0), RFIFOP (fd, 0), RFIFOW (fd, 2)); WFIFOW (fd, 0) = 0x8e; WFIFOSET (fd, WFIFOW (fd, 2)); @@ -7388,90 +7393,90 @@ void clif_parse_Restart (int fd, struct map_session_data *sd) } /*========================================== - * Transmission of a wisp (S 0096 .w .24B .?B) + * Validate and process transmission of a + * whisper/private message. + * + * (S 0096 .w .24B .?B) + * + * rewritten by [Yor], then partially by + * [remoitnane] *------------------------------------------ */ void clif_parse_Wis (int fd, struct map_session_data *sd) -{ // S 0096 .w .24B .?B // rewritten by [Yor] - struct map_session_data *dstsd; - int i; - int gmlen = strlen (RFIFOP (fd, 28)); - - if (RFIFOW (fd, 2) - 28 <= 0 - || (sd && sd->sc_data - && (sd->sc_data[SC_BERSERK].timer != -1 - || sd->sc_data[SC_NOCHAT].timer != -1))) - return; +{ + size_t message_len = 0; + char *buf = NULL; + char *message = NULL; /* The message text only. */ + struct map_session_data *dstsd = NULL; - char gmbuf[512]; - char *gm_command = - ((gmlen + 28) > - sizeof (gmbuf)) ? (char *) malloc (gmlen + 28) : gmbuf; + nullpo_retv (sd); - memset (gm_command, 0, gmlen); - sprintf (gm_command, "%s : %s", sd->status.name, RFIFOP (fd, 28)); - if ((is_atcommand (fd, sd, gm_command, 0) != AtCommand_None)) + if (!(buf = clif_validate_chat (sd, 1, &message, &message_len))) { - if (gm_command != gmbuf) - free (gm_command); + /* "Your message could not be sent." */ + clif_displaymessage (fd, msg_txt (505)); return; } - if (tmw_CheckChatSpam (sd, RFIFOP (fd, 28))) - return; - - if (strlen (RFIFOP (fd, 28)) >= battle_config.chat_maxline) + if (is_atcommand (fd, sd, message, 0) != AtCommand_None + || (sd->sc_data && (sd->sc_data[SC_BERSERK].timer != -1 + || sd->sc_data[SC_NOCHAT].timer != -1))) + { + free (buf); return; + } - // searching destination character - dstsd = map_nick2sd (RFIFOP (fd, 4)); - // player is not on this map-server - if (dstsd == NULL || - // At this point, don't send wisp/page if it's not exactly the same name, because (example) - // if there are 'Test' player on an other map-server and 'test' player on this map-server, - // and if we ask for 'Test', we must not contact 'test' player - // so, we send information to inter-server, which is the only one which decide (and copy correct name). - strcmp (dstsd->status.name, RFIFOP (fd, 4)) != 0) // not exactly same name - // send message to inter-server - intif_wis_message (sd, RFIFOP (fd, 4), RFIFOP (fd, 28), - RFIFOW (fd, 2) - 28); - // player is on this map-server + /* + * The player is not on this server. Only send the whisper if the name is + * exactly the same, because if there are multiple map-servers and a name + * conflict (for instance, "Test" versus "test"), the char-server must + * settle the discrepancy. + */ + if (!(dstsd = map_nick2sd (RFIFOP (fd, 4))) + || strcmp (dstsd->status.name, RFIFOP (fd, 4)) != 0) + intif_wis_message (sd, RFIFOP (fd, 4), message, RFIFOW (fd, 2) - 28); else { - // if you send to your self, don't send anything to others - if (dstsd->fd == fd) // but, normaly, it's impossible! - clif_wis_message (fd, wisp_server_name, - "You can not page yourself. Sorry.", - strlen ("You can not page yourself. Sorry.") + - 1); - // otherwise, send message and answer immediatly + /* Refuse messages addressed to self. */ + if (dstsd->fd == fd) + { + /* "You cannot page yourself." */ + char *mes = msg_txt (504); + clif_wis_message (fd, wisp_server_name, mes, strlen (mes) + 1); + } else { + /* The target is ignoring all whispers. */ if (dstsd->ignoreAll == 1) - clif_wis_end (fd, 2); // type: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + /* Ignored by target. */ + clif_wis_end (fd, 2); else { - // if player ignore the source character - for (i = 0; - i < (sizeof (dstsd->ignore) / sizeof (dstsd->ignore[0])); - i++) + int i; + size_t end = sizeof (dstsd->ignore) / sizeof (dstsd->ignore[0]); + + /* See if the source player is being ignored. */ + for (i = 0; i < end; ++i) if (strcmp (dstsd->ignore[i].name, sd->status.name) == 0) { - clif_wis_end (fd, 2); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + /* Ignored by target. */ + clif_wis_end (fd, 2); break; } - // if source player not found in ignore list - if (i == (sizeof (sd->ignore) / sizeof (sd->ignore[0]))) + + /* The player is not being ignored. */ + if (i == end) { - clif_wis_message (dstsd->fd, sd->status.name, - RFIFOP (fd, 28), RFIFOW (fd, 2) - 28); - clif_wis_end (fd, 0); // type: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + clif_wis_message (dstsd->fd, sd->status.name, message, + RFIFOW (fd, 2) - 28); + /* The whisper was sent successfully. */ + clif_wis_end (fd, 0); } } } } - return; + free (buf); } /*========================================== @@ -8463,20 +8468,37 @@ void clif_parse_PartyChangeOption (int fd, struct map_session_data *sd) } /*========================================== - * �p�[�e�B���b�Z�[�W���M�v�� + * Validate and process transmission of a + * party message. + * + * (S 0108 .w .?B) *------------------------------------------ */ void clif_parse_PartyMessage (int fd, struct map_session_data *sd) { + size_t message_len = 0; + char *buf = NULL; + char *message = NULL; /* The message text only. */ + nullpo_retv (sd); - if (is_atcommand (fd, sd, RFIFOP (fd, 4), 0) != AtCommand_None) + if (!(buf = clif_validate_chat (sd, 0, &message, &message_len))) + { + /* "Your message could not be sent." */ + clif_displaymessage (fd, msg_txt (505)); return; - if (sd->sc_data && (sd->sc_data[SC_BERSERK].timer != -1 || //�o�[�T�[�N���͉��b���s�� - sd->sc_data[SC_NOCHAT].timer != -1)) //�`���b�g�֎~ + } + + if (is_atcommand (fd, sd, message, 0) != AtCommand_None + || (sd->sc_data && (sd->sc_data[SC_BERSERK].timer != -1 + || sd->sc_data[SC_NOCHAT].timer != -1))) + { + free (buf); return; + } - party_send_message (sd, RFIFOP (fd, 4), RFIFOW (fd, 2) - 4); + party_send_message (sd, message, RFIFOW (fd, 2) - 4); + free (buf); } /*========================================== @@ -8669,20 +8691,37 @@ void clif_parse_GuildExplusion (int fd, struct map_session_data *sd) } /*========================================== - * �M���h���b + * Validate and process transmission of a + * guild message. + * + * (S 017e .w .?B) *------------------------------------------ */ void clif_parse_GuildMessage (int fd, struct map_session_data *sd) { + size_t message_len = 0; + char *buf = NULL; + char *message = NULL; /* The message text only. */ + nullpo_retv (sd); - if (is_atcommand (fd, sd, RFIFOP (fd, 4), 0) != AtCommand_None) + if (!(buf = clif_validate_chat (sd, 0, &message, &message_len))) + { + /* "Your message could not be sent." */ + clif_displaymessage (fd, msg_txt (505)); return; - if (sd->sc_data && (sd->sc_data[SC_BERSERK].timer != -1 || //�o�[�T�[�N���͉��b���s�� - sd->sc_data[SC_NOCHAT].timer != -1)) //�`���b�g�֎~ + } + + if (is_atcommand (fd, sd, message, 0) != AtCommand_None + || (sd->sc_data && (sd->sc_data[SC_BERSERK].timer != -1 + || sd->sc_data[SC_NOCHAT].timer != -1))) + { + free (buf); return; + } - guild_send_message (sd, RFIFOP (fd, 4), RFIFOW (fd, 2) - 4); + guild_send_message (sd, message, RFIFOW (fd, 2) - 4); + free (buf); } /*========================================== @@ -9803,6 +9842,141 @@ int clif_check_packet_flood(fd, cmd) return 0; } +#define WARN_MALFORMED_MSG(sd, msg) \ + printf ("clif_validate_chat(): %s (ID %d) sent a malformed" \ + " message: %s.\n", sd->status.name, sd->status.account_id, msg) +/** + * Validate message integrity (inspired by upstream source (eAthena)). + * + * @param sd active session data + * @param type message type: + * 0 for when the sender's name is not included (party/guild chat) + * 1 for when the target's name is included (whisper chat) + * 2 for when the sender's name is embedded ("sender : text", public chat) + * @param[out] message the message text (pointing within return value, or NULL) + * @param[out] message_len the length of the actual text, excluding NUL + * @return a dynamically allocated copy of the message, or NULL upon failure + */ +static char *clif_validate_chat (struct map_session_data *sd, int type, + char **message, size_t *message_len) +{ + int fd; + unsigned int buf_len; /* Actual message length. */ + unsigned int msg_len; /* Reported message length. */ + unsigned int min_len; /* Minimum message length. */ + size_t name_len; /* Sender's name length. */ + char *buf = NULL; /* Copy of actual message data. */ + char *p = NULL; /* Temporary pointer to message. */ + + *message = NULL; + *message_len = 0; + + nullpo_retr (NULL, sd); + /* + * Don't send chat in the period between the ban and the connection's + * closure. + */ + if (type < 0 || type > 2 || sd->auto_ban_info.in_progress) + return NULL; + + fd = sd->fd; + msg_len = RFIFOW (fd, 2) - 4; + name_len = strlen (sd->status.name); + /* + * At least one character is required in all instances. + * Notes for length checks: + * + * For all types, header (2) + length (2) is considered empty. + * For type 1, the message must be longer than the maximum name length (24) + * to be valid. + * For type 2, the message must be longer than the sender's name length + * plus the length of the separator (" : "). + */ + min_len = (type == 1) ? 24 : (type == 2) ? name_len + 3 : 0; + + /* The player just sent the header (2) and length (2) words. */ + if (!msg_len) + { + WARN_MALFORMED_MSG (sd, "no message sent"); + return NULL; + } + + /* The client sent (or claims to have sent) an empty message. */ + if (msg_len == min_len) + { + WARN_MALFORMED_MSG (sd, "empty message"); + return NULL; + } + + /* The protocol specifies that the target must be 24 bytes long. */ + if (type == 1 && msg_len < min_len) + { + /* Disallow malformed messages. */ + clif_setwaitclose (fd); + WARN_MALFORMED_MSG (sd, "illegal target name"); + return NULL; + } + + p = (char *) (type != 1) ? RFIFOP (fd, 4) : RFIFOP (fd, 28); + buf_len = (type == 1) ? msg_len - min_len: msg_len; + + /* + * The client attempted to exceed the maximum message length. + * + * The conf suggests up to chat_maxline characters, after which the message + * is truncated. But the previous behavior was to drop the message, so + * we'll do that, too. + */ + if (buf_len >= battle_config.chat_maxline) + { + WARN_MALFORMED_MSG (sd, "exceeded maximum message length"); + return NULL; + } + + /* We're leaving an extra eight bytes for public/global chat, 1 for NUL. */ + buf_len += (type == 2) ? 8 + 1 : 1; + + buf = (char *) aMalloc (buf_len); + memcpy ((type != 2) ? buf : buf + 8, p, + (type != 2) ? buf_len - 1 : buf_len - 8 - 1); + buf[buf_len - 1] = '\0'; + p = (type != 2) ? buf : buf + 8; + + /* Don't send chat that results in an automatic ban. */ + if (tmw_CheckChatSpam (sd, p)) + { + free (buf); + return NULL; + } + + if (type != 2) + { + *message = buf; + /* Don't count the NUL. */ + *message_len = buf_len - 1; + } + else + { + char *pos = NULL; + if (!(pos = strstr(p, " : ")) + || strncmp (p, sd->status.name, name_len) + || pos - p != name_len) + { + free (buf); + /* Disallow malformed/spoofed messages. */ + clif_setwaitclose (fd); + WARN_MALFORMED_MSG (sd, "spoofed name/invalid format"); + return NULL; + } + /* Step beyond the separator. */ + *message = pos + 3; + /* Don't count the sender's name, the extra eight bytes, or the NUL. */ + *message_len = buf_len - min_len - 8 - 1; + } + + return buf; +} + /*========================================== * �N���C�A���g�����̃p�P�b�g���� * socket.c��do_parsepacket�����Ăяo������ diff --git a/src/map/tmw.c b/src/map/tmw.c index 46e02bc..8fcfd0d 100644 --- a/src/map/tmw.c +++ b/src/map/tmw.c @@ -131,9 +131,6 @@ int tmw_CheckChatLameness (struct map_session_data *sd, char *message) { int count, lame; - // Ignore the name - message += strlen (sd->status.name); - for (count = lame = 0; *message; message++, count++) if (isupper (*message) || ispunct (*message)) lame++; -- cgit v1.2.3-70-g09d2