Core/Chat: fix client crash by wrong quest shift-link structure.

Protection only work with ChatStrictLinkChecking.Severity = 3
(original patch by VladimirMangos)
This commit is contained in:
Azazel
2011-04-12 17:23:35 +06:00
parent 6a4151d531
commit 9079b27c56
2 changed files with 187 additions and 143 deletions
+172 -129
View File
@@ -35,6 +35,13 @@
#include "SpellMgr.h" #include "SpellMgr.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#ifdef TRINITY_DEBUG
#define LOG(...) sLog->outDebug(LOG_FILTER_CHATSYS, __VA_ARGS__);
#else
#define LOG(...)
#endif
// Supported shift-links (client generated and server side) // Supported shift-links (client generated and server side)
// |color|Hachievement:achievement_id:player_guid:0:0:0:0:0:0:0:0|h[name]|h|r // |color|Hachievement:achievement_id:player_guid:0:0:0:0:0:0:0:0|h[name]|h|r
// - client, item icon shift click, not used in server currently // - client, item icon shift click, not used in server currently
@@ -57,7 +64,7 @@
// |color|Htaxinode:id|h[name]|h|r // |color|Htaxinode:id|h[name]|h|r
// |color|Htele:id|h[name]|h|r // |color|Htele:id|h[name]|h|r
// |color|Htitle:id|h[name]|h|r // |color|Htitle:id|h[name]|h|r
// |color|Htrade:spell_id,cur_value,max_value,unk3int,unk3str|h[name]|h|r - client, spellbook profession icon shift-click // |color|Htrade:spell_id:cur_value:max_value:unk3int:unk3str|h[name]|h|r - client, spellbook profession icon shift-click
bool ChatHandler::load_command_table = true; bool ChatHandler::load_command_table = true;
@@ -832,6 +839,20 @@ int ChatHandler::ParseCommands(const char* text)
return 1; return 1;
} }
inline uint32 ChatHandler::_ReadUInt32(std::istringstream& reader) const
{
uint32 res = 0;
char c = reader.peek();
while (c >='0' && c <= '9')
{
reader.ignore(1);
res *= 10;
res += c-'0';
c = reader.peek();
}
return res;
}
bool ChatHandler::isValidChatMessage(const char* message) bool ChatHandler::isValidChatMessage(const char* message)
{ {
/* /*
@@ -918,18 +939,14 @@ valid examples:
} }
else if (reader.get() != '|') else if (reader.get() != '|')
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): sequence aborted unexpectedly", reader.str().c_str());
sLog->outBasic("ChatHandler::isValidChatMessage sequence aborted unexpectedly");
#endif
return false; return false;
} }
// pipe has always to be followed by at least one char // pipe has always to be followed by at least one char
if (reader.peek() == '\0') if (reader.peek() == '\0')
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): pipe followed by '\\0'", reader.str().c_str());
sLog->outBasic("ChatHandler::isValidChatMessage pipe followed by \\0");
#endif
return false; return false;
} }
@@ -952,18 +969,14 @@ valid examples:
} }
else else
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): invalid sequence, expected '%c' but got '%c'", reader.str().c_str(), *validSequenceIterator, commandChar);
sLog->outBasic("ChatHandler::isValidChatMessage invalid sequence, expected %c but got %c", *validSequenceIterator, commandChar);
#endif
return false; return false;
} }
} }
else if (validSequence != validSequenceIterator) else if (validSequence != validSequenceIterator)
{ {
// no escaped pipes in sequences // no escaped pipes in sequences
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): got escaped pipe in sequence", reader.str().c_str());
sLog->outBasic("ChatHandler::isValidChatMessage got escaped pipe in sequence");
#endif
return false; return false;
} }
@@ -972,60 +985,59 @@ valid examples:
case 'c': case 'c':
color = 0; color = 0;
// validate color, expect 8 hex chars // validate color, expect 8 hex chars
for (int i=0; i<8; i++) for (uint8 i = 0; i < 8; ++i)
{ {
char c; char c;
reader >> c; reader >> c;
if (!c) if (!c)
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): got \\0 while reading color in |c command", reader.str().c_str());
sLog->outBasic("ChatHandler::isValidChatMessage got \\0 while reading color in |c command");
#endif
return false; return false;
} }
color <<= 4; color <<= 4;
// check for hex char // check for hex char
if (c >= '0' && c <= '9') if (c >= '0' && c <= '9')
color |= c - '0';
else if (c >= 'a' && c <= 'f')
color |= 10 + c - 'a';
else
{ {
color |= c-'0'; LOG("ChatHandler::isValidChatMessage('%s'): got non hex char '%c' while reading color", reader.str().c_str(), c);
continue;
}
if (c >= 'a' && c <= 'f')
{
color |= 10+c-'a';
continue;
}
#ifdef TRINITY_DEBUG
sLog->outBasic("ChatHandler::isValidChatMessage got non hex char '%c' while reading color", c);
#endif
return false; return false;
} }
}
break; break;
case 'H': case 'H':
// read chars up to colon = link type // read chars up to colon = link type
reader.getline(buffer, 256, ':'); reader.getline(buffer, 256, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
if (strcmp(buffer, "item") == 0) if (strcmp(buffer, "item") == 0)
{ {
// read item entry // read item entry
reader.getline(buffer, 256, ':'); reader.getline(buffer, 256, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
linkedItem= ObjectMgr::GetItemPrototype(atoi(buffer)); uint32 itemEntry = atoi(buffer);
linkedItem = ObjectMgr::GetItemPrototype(itemEntry);
if (!linkedItem) if (!linkedItem)
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): got invalid itemEntry %u in |item command", reader.str().c_str(), itemEntry);
sLog->outBasic("ChatHandler::isValidChatMessage got invalid itemID %u in |item command", atoi(buffer));
#endif
return false; return false;
} }
if (color != ItemQualityColors[linkedItem->Quality]) if (color != ItemQualityColors[linkedItem->Quality])
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): linked item has color %u, but user claims %u", reader.str().c_str(), ItemQualityColors[linkedItem->Quality], color);
sLog->outBasic("ChatHandler::isValidChatMessage linked item has color %u, but user claims %u", ItemQualityColors[linkedItem->Quality],
color);
#endif
return false; return false;
} }
@@ -1045,30 +1057,40 @@ valid examples:
{ {
if (c >='0' && c <= '9') if (c >='0' && c <= '9')
{ {
propertyId*=10; propertyId *= 10;
propertyId += c-'0'; propertyId += c - '0';
} else if (c == '-') }
else if (c == '-')
negativeNumber = true; negativeNumber = true;
else else
{
LOG("ChatHandler::isValidChatMessage('%s'): invalid character '%c' found while reading item properties", reader.str().c_str(), c);
return false; return false;
} }
} }
if (negativeNumber) if (negativeNumber)
propertyId *= -1; propertyId = -propertyId;
if (propertyId > 0) if (propertyId > 0)
{ {
itemProperty = sItemRandomPropertiesStore.LookupEntry(propertyId); itemProperty = sItemRandomPropertiesStore.LookupEntry(propertyId);
if (!itemProperty) if (!itemProperty)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid item property id %u in |item command", reader.str().c_str(), propertyId);
return false; return false;
} }
}
else if (propertyId < 0) else if (propertyId < 0)
{ {
itemSuffix = sItemRandomSuffixStore.LookupEntry(-propertyId); itemSuffix = sItemRandomSuffixStore.LookupEntry(-propertyId);
if (!itemSuffix) if (!itemSuffix)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid item suffix id %u in |item command", reader.str().c_str(), -propertyId);
return false; return false;
} }
}
}
// ignore other integers // ignore other integers
while ((c >= '0' && c <= '9') || c == ':') while ((c >= '0' && c <= '9') || c == ':')
@@ -1079,33 +1101,28 @@ valid examples:
} }
else if (strcmp(buffer, "quest") == 0) else if (strcmp(buffer, "quest") == 0)
{ {
// no color check for questlinks, each client will adapt it anyway // Quest Id
uint32 questid= 0; uint32 questId = _ReadUInt32(reader);
// read questid linkedQuest = sObjectMgr->GetQuestTemplate(questId);
char c = reader.peek();
while (c >='0' && c <= '9')
{
reader.ignore(1);
questid *= 10;
questid += c-'0';
c = reader.peek();
}
linkedQuest = sObjectMgr->GetQuestTemplate(questid);
if (!linkedQuest) if (!linkedQuest)
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): quest template %u not found", reader.str().c_str(), questId);
sLog->outBasic("ChatHandler::isValidChatMessage Questtemplate %u not found", questid);
#endif
return false; return false;
} }
c = reader.peek(); // Delimiter
// level char c = reader.peek();
while (c !='|' && c != '\0') if (c != ':')
{ {
LOG("ChatHandler::isValidChatMessage('%s'): invalid quest link structure (':' expected, '%c' found)", reader.str().c_str(), c);
return false;
}
reader.ignore(1); reader.ignore(1);
c = reader.peek(); // Quest level
uint32 questLevel = _ReadUInt32(reader);
if (questLevel >= STRONG_MAX_LEVEL)
{
LOG("ChatHandler::isValidChatMessage('%s'): quest level %u is too big", reader.str().c_str(), questLevel);
return false;
} }
} }
else if (strcmp(buffer, "trade") == 0) else if (strcmp(buffer, "trade") == 0)
@@ -1113,15 +1130,25 @@ valid examples:
if (color != CHAT_LINK_COLOR_TRADE) if (color != CHAT_LINK_COLOR_TRADE)
return false; return false;
// read spell entry // Spell Id
reader.getline(buffer, 256, ':'); reader.getline(buffer, 256, ':');
linkedSpell = sSpellStore.LookupEntry(atoi(buffer)); if (reader.eof())
if (!linkedSpell) {
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false; return false;
}
uint32 spellId = atoi(buffer);
linkedSpell = sSpellStore.LookupEntry(spellId);
if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", reader.str().c_str(), spellId);
return false;
}
char c = reader.peek();
// base64 encoded stuff // base64 encoded stuff
while (c !='|' && c != '\0') char c = reader.peek();
while (c != '|' && c != '\0')
{ {
reader.ignore(1); reader.ignore(1);
c = reader.peek(); c = reader.peek();
@@ -1135,17 +1162,30 @@ valid examples:
// read talent entry // read talent entry
reader.getline(buffer, 256, ':'); reader.getline(buffer, 256, ':');
TalentEntry const *talentInfo = sTalentStore.LookupEntry(atoi(buffer)); if (reader.eof())
if (!talentInfo) {
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false; return false;
}
uint32 talentId = atoi(buffer);
TalentEntry const *talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid talent id %u in |talent command", reader.str().c_str(), talentId);
return false;
}
linkedSpell = sSpellStore.LookupEntry(talentInfo->RankID[0]); linkedSpell = sSpellStore.LookupEntry(talentInfo->RankID[0]);
if (!linkedSpell) if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", reader.str().c_str(), talentInfo->RankID[0]);
return false; return false;
}
char c = reader.peek();
// skillpoints? whatever, drop it // skillpoints? whatever, drop it
while (c !='|' && c != '\0') char c = reader.peek();
while (c != '|' && c != '\0')
{ {
reader.ignore(1); reader.ignore(1);
c = reader.peek(); c = reader.peek();
@@ -1156,52 +1196,52 @@ valid examples:
if (color != CHAT_LINK_COLOR_SPELL) if (color != CHAT_LINK_COLOR_SPELL)
return false; return false;
uint32 spellid = 0; // Spell Id
// read spell entry uint32 spellId = _ReadUInt32(reader);
char c = reader.peek(); linkedSpell = sSpellStore.LookupEntry(spellId);
while (c >='0' && c <= '9')
{
reader.ignore(1);
spellid *= 10;
spellid += c-'0';
c = reader.peek();
}
linkedSpell = sSpellStore.LookupEntry(spellid);
if (!linkedSpell) if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |spell command", reader.str().c_str(), spellId);
return false; return false;
} }
}
else if (strcmp(buffer, "enchant") == 0) else if (strcmp(buffer, "enchant") == 0)
{ {
if (color != CHAT_LINK_COLOR_ENCHANT) if (color != CHAT_LINK_COLOR_ENCHANT)
return false; return false;
uint32 spellid = 0; // Spell Id
// read spell entry uint32 spellId = _ReadUInt32(reader);
char c = reader.peek(); linkedSpell = sSpellStore.LookupEntry(spellId);
while (c >='0' && c <= '9')
{
reader.ignore(1);
spellid *= 10;
spellid += c-'0';
c = reader.peek();
}
linkedSpell = sSpellStore.LookupEntry(spellid);
if (!linkedSpell) if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |enchant command", reader.str().c_str(), spellId);
return false; return false;
} }
}
else if (strcmp(buffer, "achievement") == 0) else if (strcmp(buffer, "achievement") == 0)
{ {
if (color != CHAT_LINK_COLOR_ACHIEVEMENT) if (color != CHAT_LINK_COLOR_ACHIEVEMENT)
return false; return false;
// Achievemnt Id
reader.getline(buffer, 256, ':'); reader.getline(buffer, 256, ':');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
uint32 achievementId = atoi(buffer); uint32 achievementId = atoi(buffer);
linkedAchievement = sAchievementStore.LookupEntry(achievementId); linkedAchievement = sAchievementStore.LookupEntry(achievementId);
if (!linkedAchievement) if (!linkedAchievement)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid achivement id %u in |achievement command", reader.str().c_str(), achievementId);
return false; return false;
}
char c = reader.peek();
// skip progress // skip progress
char c = reader.peek();
while (c !='|' && c != '\0') while (c !='|' && c != '\0')
{ {
reader.ignore(1); reader.ignore(1);
@@ -1215,29 +1255,30 @@ valid examples:
// first id is slot, drop it // first id is slot, drop it
reader.getline(buffer, 256, ':'); reader.getline(buffer, 256, ':');
uint32 glyphId = 0; if (reader.eof())
char c = reader.peek();
while (c >= '0' && c <= '9')
{ {
glyphId *= 10; LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
glyphId += c-'0'; return false;
reader.ignore(1);
c = reader.peek();
} }
uint32 glyphId = _ReadUInt32(reader);
GlyphPropertiesEntry const* glyph = sGlyphPropertiesStore.LookupEntry(glyphId); GlyphPropertiesEntry const* glyph = sGlyphPropertiesStore.LookupEntry(glyphId);
if (!glyph) if (!glyph)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid glyph id %u in |glyph command", reader.str().c_str(), glyphId);
return false; return false;
}
linkedSpell = sSpellStore.LookupEntry(glyph->SpellId); linkedSpell = sSpellStore.LookupEntry(glyph->SpellId);
if (!linkedSpell) if (!linkedSpell)
{
LOG("ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |glyph command", reader.str().c_str(), glyph->SpellId);
return false; return false;
} }
}
else else
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): user sent unsupported link type '%s'", reader.str().c_str(), buffer);
sLog->outBasic("ChatHandler::isValidChatMessage user sent unsupported link type '%s'", buffer);
#endif
return false; return false;
} }
break; break;
@@ -1248,12 +1289,15 @@ valid examples:
// links start with '[' // links start with '['
if (reader.get() != '[') if (reader.get() != '[')
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): link caption doesn't start with '['", reader.str().c_str());
sLog->outBasic("ChatHandler::isValidChatMessage link caption doesn't start with '['");
#endif
return false; return false;
} }
reader.getline(buffer, 256, ']'); reader.getline(buffer, 256, ']');
if (reader.eof())
{
LOG("ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", reader.str().c_str());
return false;
}
// verify the link name // verify the link name
if (linkedSpell) if (linkedSpell)
@@ -1265,19 +1309,21 @@ valid examples:
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(linkedSpell->Id); SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(linkedSpell->Id);
if (bounds.first == bounds.second) if (bounds.first == bounds.second)
{ {
LOG("ChatHandler::isValidChatMessage('%s'): skill line not found for spell %u", reader.str().c_str(), linkedSpell->Id);
return false; return false;
} }
SkillLineAbilityEntry const *skillInfo = bounds.first->second; SkillLineAbilityEntry const *skillInfo = bounds.first->second;
if (!skillInfo) if (!skillInfo)
{ {
LOG("ChatHandler::isValidChatMessage('%s'): skill line ability not found for spell %u", reader.str().c_str(), linkedSpell->Id);
return false; return false;
} }
SkillLineEntry const *skillLine = sSkillLineStore.LookupEntry(skillInfo->skillId); SkillLineEntry const *skillLine = sSkillLineStore.LookupEntry(skillInfo->skillId);
if (!skillLine) if (!skillLine)
{ {
LOG("ChatHandler::isValidChatMessage('%s'): skill line not found for skill %u", reader.str().c_str(), skillInfo->skillId);
return false; return false;
} }
@@ -1288,8 +1334,8 @@ valid examples:
{ {
// found the prefix, remove it to perform spellname validation below // found the prefix, remove it to perform spellname validation below
// -2 = strlen(": ") // -2 = strlen(": ")
uint32 spellNameLength = strlen(buffer)-skillLineNameLength-2; uint32 spellNameLength = strlen(buffer) - skillLineNameLength - 2;
memmove(buffer, buffer+skillLineNameLength+2, spellNameLength+1); memmove(buffer, buffer + skillLineNameLength + 2, spellNameLength + 1);
} }
} }
} }
@@ -1310,12 +1356,9 @@ valid examples:
if (linkedQuest->GetTitle() != buffer) if (linkedQuest->GetTitle() != buffer)
{ {
QuestLocale const *ql = sObjectMgr->GetQuestLocale(linkedQuest->GetQuestId()); QuestLocale const *ql = sObjectMgr->GetQuestLocale(linkedQuest->GetQuestId());
if (!ql) if (!ql)
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): default questname didn't match and no locales present for this quest (id: %u)", reader.str().c_str(), linkedQuest->GetQuestId());
sLog->outBasic("ChatHandler::isValidChatMessage default questname didn't match and there is no locale");
#endif
return false; return false;
} }
@@ -1330,16 +1373,15 @@ valid examples:
} }
if (!foundName) if (!foundName)
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): linked quest (id: %u) name wasn't found in any localization", reader.str().c_str(), linkedQuest->GetQuestId());
sLog->outBasic("ChatHandler::isValidChatMessage no quest locale title matched");
#endif
return false; return false;
} }
} }
} }
else if (linkedItem) else if (linkedItem)
{ {
char* const* suffix = itemSuffix?itemSuffix->nameSuffix:(itemProperty?itemProperty->nameSuffix:NULL); char* const* suffix = itemSuffix ? itemSuffix->nameSuffix :
(itemProperty ? itemProperty->nameSuffix : NULL);
std::string expectedName = std::string(linkedItem->Name1); std::string expectedName = std::string(linkedItem->Name1);
if (suffix) if (suffix)
@@ -1373,9 +1415,7 @@ valid examples:
} }
if (!foundName) if (!foundName)
{ {
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): linked item (id: %u) name wasn't found in any localization", reader.str().c_str(), linkedItem->ItemId);
sLog->outBasic("ChatHandler::isValidChatMessage linked item name wasn't found in any localization");
#endif
return false; return false;
} }
} }
@@ -1392,8 +1432,11 @@ valid examples:
} }
} }
if (!foundName) if (!foundName)
{
LOG("ChatHandler::isValidChatMessage('%s'): linked achievement (id: %u) name wasn't found in any localization", reader.str().c_str(), linkedAchievement->ID);
return false; return false;
} }
}
// that place should never be reached - if nothing linked has been set in |H // that place should never be reached - if nothing linked has been set in |H
// it will return false before // it will return false before
else else
@@ -1405,19 +1448,19 @@ valid examples:
// no further payload // no further payload
break; break;
default: default:
#ifdef TRINITY_DEBUG LOG("ChatHandler::isValidChatMessage('%s'): got invalid command |%c", reader.str().c_str(), commandChar);
sLog->outBasic("ChatHandler::isValidChatMessage got invalid command |%c", commandChar);
#endif
return false; return false;
} }
} }
// check if every opened sequence was also closed properly // check if every opened sequence was also closed properly
#ifdef TRINITY_DEBUG
if (validSequence != validSequenceIterator) if (validSequence != validSequenceIterator)
sLog->outBasic("ChatHandler::isValidChatMessage EOF in active sequence"); {
#endif LOG("ChatHandler::isValidChatMessage('%s'): EOF in active sequence", reader.str().c_str());
return validSequence == validSequenceIterator; return false;
}
return true;
} }
bool ChatHandler::ShowHelpForSubCommands(ChatCommand *table, char const* cmd, char const* subcmd) bool ChatHandler::ShowHelpForSubCommands(ChatCommand *table, char const* cmd, char const* subcmd)
+1
View File
@@ -374,6 +374,7 @@ class ChatHandler
void HandleCharacterDeletedListHelper(DeletedInfoList const& foundList); void HandleCharacterDeletedListHelper(DeletedInfoList const& foundList);
void HandleCharacterDeletedRestoreHelper(DeletedInfo const& delInfo); void HandleCharacterDeletedRestoreHelper(DeletedInfo const& delInfo);
uint32 _ReadUInt32(std::istringstream& reader) const;
private: private:
WorldSession * m_session; // != NULL for chat command call and NULL for CLI command WorldSession * m_session; // != NULL for chat command call and NULL for CLI command