1
0
cuberite-2a/src/Protocol/ProtocolRecognizer.cpp
Tiger Wang 49ef21d650 MultiVersionProtocol: fix two crashes
First one: add missing exception handler in ProcessProtocolIn

Second: remove faulty logic dealing with incomplete packets.

`a_Data = a_Data.substr(m_Buffer.GetUsedSpace() - m_Buffer.GetReadableSpace());`

was incorrect; it attempted to apply a length derived from m_Buffer to an unrelated a_Data. Its purpose was to give cProtocol the data the client sent, minus initial handshake bytes. However, we can use the knowledge that during initial handshake, there is no encryption and every byte can be written unchanged into m_Buffer, to just call cProtocol with a data length of zero. This will cause it to parse from m_Buffer - wherein we have already written everything the client sent - with no a_Data manipulation needed.

Additionally, removed UnsupportedButPingableProtocolException (use of exception as control flow) and encode this state as m_Protocol == nullptr, id est "no protocol for this unsupported version", which is then handled by cMultiVersionProtocol itself.
2021-01-19 09:54:58 +00:00

402 lines
12 KiB
C++

// ProtocolRecognizer.cpp
// Implements the cProtocolRecognizer class representing the meta-protocol that recognizes possibly multiple
// protocol versions and redirects everything to them
#include "Globals.h"
#include "ProtocolRecognizer.h"
#include "Protocol_1_8.h"
#include "Protocol_1_9.h"
#include "Protocol_1_10.h"
#include "Protocol_1_11.h"
#include "Protocol_1_12.h"
#include "Protocol_1_13.h"
#include "Protocol_1_14.h"
#include "../ClientHandle.h"
#include "../Root.h"
#include "../Server.h"
#include "../World.h"
#include "../JsonUtils.h"
#include "../Bindings/PluginManager.h"
struct TriedToJoinWithUnsupportedProtocolException : public std::runtime_error
{
explicit TriedToJoinWithUnsupportedProtocolException(const std::string & a_Message) :
std::runtime_error(a_Message)
{
}
};
cMultiVersionProtocol::cMultiVersionProtocol() :
HandleIncomingData(std::bind(&cMultiVersionProtocol::HandleIncomingDataInRecognitionStage, this, std::placeholders::_1, std::placeholders::_2)),
m_Buffer(32 KiB)
{
}
AString cMultiVersionProtocol::GetVersionTextFromInt(cProtocol::Version a_ProtocolVersion)
{
switch (a_ProtocolVersion)
{
case cProtocol::Version::v1_8_0: return "1.8";
case cProtocol::Version::v1_9_0: return "1.9";
case cProtocol::Version::v1_9_1: return "1.9.1";
case cProtocol::Version::v1_9_2: return "1.9.2";
case cProtocol::Version::v1_9_4: return "1.9.4";
case cProtocol::Version::v1_10_0: return "1.10";
case cProtocol::Version::v1_11_0: return "1.11";
case cProtocol::Version::v1_11_1: return "1.11.1";
case cProtocol::Version::v1_12: return "1.12";
case cProtocol::Version::v1_12_1: return "1.12.1";
case cProtocol::Version::v1_12_2: return "1.12.2";
case cProtocol::Version::v1_13: return "1.13";
case cProtocol::Version::v1_13_1: return "1.13.1";
case cProtocol::Version::v1_13_2: return "1.13.2";
case cProtocol::Version::v1_14: return "1.14";
}
ASSERT(!"Unknown protocol version");
return Printf("Unknown protocol (%d)", a_ProtocolVersion);
}
void cMultiVersionProtocol::HandleIncomingDataInRecognitionStage(cClientHandle & a_Client, std::string_view a_Data)
{
// NOTE: If a new protocol is added or an old one is removed, adjust MCS_CLIENT_VERSIONS and MCS_PROTOCOL_VERSIONS macros in the header file
/* Write all incoming data unmodified into m_Buffer.
Writing everything is always okay to do:
1. We can be sure protocol encryption hasn't started yet since m_Protocol hasn't been called, hence no decryption needs to take place
2. The extra data are processed at the end of this function */
if (!m_Buffer.Write(a_Data.data(), a_Data.size()))
{
a_Client.PacketBufferFull();
return;
}
// TODO: recover from git history
// Unlengthed protocol, ...
// Lengthed protocol, try if it has the entire initial handshake packet:
if (
UInt32 PacketLen;
// If not enough bytes for the packet length, keep waiting
!m_Buffer.ReadVarInt(PacketLen) ||
// If not enough bytes for the packet, keep waiting
// (More of a sanity check to make sure no one tries anything funny, since ReadXXX can wait for data themselves)
!m_Buffer.CanReadBytes(PacketLen)
)
{
m_Buffer.ResetRead();
return;
}
/* Figure out the client's version.
1. m_Protocol != nullptr: the protocol is supported and we have a handler
2. m_Protocol == nullptr: the protocol is unsupported, handling is a special case done by ourselves
3. Exception: the data sent were garbage, the client handle deals with it by disconnecting */
m_Protocol = TryRecognizeLengthedProtocol(a_Client, a_Data);
if (m_Protocol == nullptr)
{
// Got a server list ping for an unrecognised version,
// switch into responding to unknown protocols mode:
HandleIncomingData = [this](cClientHandle & a_Clyent, const std::string_view a_In)
{
HandleIncomingDataInOldPingResponseStage(a_Clyent, a_In);
};
}
else
{
// The protocol recogniser succesfully identified, switch mode:
HandleIncomingData = [this](cClientHandle &, const std::string_view a_In)
{
m_Protocol->DataReceived(m_Buffer, a_In.data(), a_In.size());
};
}
// Explicitly process any remaining data (already written to m_Buffer) with the new handler:
HandleIncomingData(a_Client, {});
}
void cMultiVersionProtocol::HandleIncomingDataInOldPingResponseStage(cClientHandle & a_Client, const std::string_view a_Data)
{
if (!m_Buffer.Write(a_Data.data(), a_Data.size()))
{
a_Client.PacketBufferFull();
return;
}
cByteBuffer OutPacketBuffer(6 KiB);
// Handle server list ping packets
for (;;)
{
UInt32 PacketLen;
UInt32 PacketID;
if (
!m_Buffer.ReadVarInt32(PacketLen) ||
!m_Buffer.CanReadBytes(PacketLen) ||
!m_Buffer.ReadVarInt32(PacketID)
)
{
// Not enough data
m_Buffer.ResetRead();
break;
}
if ((PacketID == 0x00) && (PacketLen == 1)) // Request packet
{
HandlePacketStatusRequest(a_Client, OutPacketBuffer);
SendPacket(a_Client, OutPacketBuffer);
}
else if ((PacketID == 0x01) && (PacketLen == 9)) // Ping packet
{
HandlePacketStatusPing(a_Client, OutPacketBuffer);
SendPacket(a_Client, OutPacketBuffer);
}
else
{
a_Client.PacketUnknown(PacketID);
return;
}
m_Buffer.CommitRead();
}
}
void cMultiVersionProtocol::SendDisconnect(cClientHandle & a_Client, const AString & a_Reason)
{
if (m_Protocol != nullptr)
{
m_Protocol->SendDisconnect(a_Reason);
return;
}
const AString Message = Printf("{\"text\":\"%s\"}", EscapeString(a_Reason).c_str());
const auto PacketID = GetPacketID(cProtocol::ePacketType::pktDisconnectDuringLogin);
cByteBuffer Out(
cByteBuffer::GetVarIntSize(PacketID) +
cByteBuffer::GetVarIntSize(static_cast<UInt32>(Message.size())) + Message.size()
);
VERIFY(Out.WriteVarInt32(PacketID));
VERIFY(Out.WriteVarUTF8String(Message));
SendPacket(a_Client, Out);
}
std::unique_ptr<cProtocol> cMultiVersionProtocol::TryRecognizeLengthedProtocol(cClientHandle & a_Client, const std::string_view a_Data)
{
UInt32 PacketType;
UInt32 ProtocolVersion;
AString ServerAddress;
UInt16 ServerPort;
UInt32 NextStateValue;
if (!m_Buffer.ReadVarInt(PacketType) || (PacketType != 0x00))
{
// Not an initial handshake packet, we don't know how to talk to them:
LOGD("Client \"%s\" uses an unsupported protocol (lengthed, initial packet %u)",
a_Client.GetIPString().c_str(), PacketType
);
throw TriedToJoinWithUnsupportedProtocolException("Your client isn't supported.\nTry connecting with Minecraft " MCS_CLIENT_VERSIONS);
}
if (
!m_Buffer.ReadVarInt(ProtocolVersion) ||
!m_Buffer.ReadVarUTF8String(ServerAddress) ||
!m_Buffer.ReadBEUInt16(ServerPort) ||
!m_Buffer.ReadVarInt(NextStateValue)
)
{
// TryRecognizeProtocol guarantees that we will have as much
// data to read as the client claims in the protocol length field:
throw TriedToJoinWithUnsupportedProtocolException("Incorrect amount of data received - hacked client?");
}
const auto NextState = [NextStateValue]
{
switch (NextStateValue)
{
case 1: return cProtocol::State::Status;
case 2: return cProtocol::State::Login;
case 3: return cProtocol::State::Game;
default: throw TriedToJoinWithUnsupportedProtocolException("Your client isn't supported.\nTry connecting with Minecraft " MCS_CLIENT_VERSIONS);
}
}();
// TODO: this should be a protocol property, not ClientHandle:
a_Client.SetProtocolVersion(ProtocolVersion);
// All good, eat up the data:
m_Buffer.CommitRead();
switch (ProtocolVersion)
{
case static_cast<UInt32>(cProtocol::Version::v1_8_0): return std::make_unique<cProtocol_1_8_0> (&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_9_0): return std::make_unique<cProtocol_1_9_0> (&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_9_1): return std::make_unique<cProtocol_1_9_1> (&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_9_2): return std::make_unique<cProtocol_1_9_2> (&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_9_4): return std::make_unique<cProtocol_1_9_4> (&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_10_0): return std::make_unique<cProtocol_1_10_0>(&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_11_0): return std::make_unique<cProtocol_1_11_0>(&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_11_1): return std::make_unique<cProtocol_1_11_1>(&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_12): return std::make_unique<cProtocol_1_12> (&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_12_1): return std::make_unique<cProtocol_1_12_1>(&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_12_2): return std::make_unique<cProtocol_1_12_2>(&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_13): return std::make_unique<cProtocol_1_13> (&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_13_1): return std::make_unique<cProtocol_1_13_1>(&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_13_2): return std::make_unique<cProtocol_1_13_2>(&a_Client, ServerAddress, NextState);
case static_cast<UInt32>(cProtocol::Version::v1_14): return std::make_unique<cProtocol_1_14> (&a_Client, ServerAddress, NextState);
default:
{
LOGD("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))",
a_Client.GetIPString().c_str(), ProtocolVersion, ProtocolVersion
);
if (NextState != cProtocol::State::Status)
{
throw TriedToJoinWithUnsupportedProtocolException(
Printf("Unsupported protocol version %u.\nTry connecting with Minecraft " MCS_CLIENT_VERSIONS, ProtocolVersion)
);
}
// No cProtocol can handle the client:
return nullptr;
}
}
}
void cMultiVersionProtocol::SendPacket(cClientHandle & a_Client, cByteBuffer & a_OutPacketBuffer)
{
// Writes out the packet normally.
UInt32 PacketLen = static_cast<UInt32>(a_OutPacketBuffer.GetUsedSpace());
cByteBuffer OutPacketLenBuffer(cByteBuffer::GetVarIntSize(PacketLen));
// Compression doesn't apply to this state, send raw data:
VERIFY(OutPacketLenBuffer.WriteVarInt32(PacketLen));
ContiguousByteBuffer LengthData;
OutPacketLenBuffer.ReadAll(LengthData);
a_Client.SendData(LengthData);
// Send the packet's payload:
ContiguousByteBuffer PacketData;
a_OutPacketBuffer.ReadAll(PacketData);
a_OutPacketBuffer.CommitRead();
a_Client.SendData(PacketData);
}
UInt32 cMultiVersionProtocol::GetPacketID(cProtocol::ePacketType a_PacketType)
{
switch (a_PacketType)
{
case cProtocol::ePacketType::pktDisconnectDuringLogin: return 0x00;
case cProtocol::ePacketType::pktStatusResponse: return 0x00;
case cProtocol::ePacketType::pktPingResponse: return 0x01;
default:
{
ASSERT(!"GetPacketID() called for an unhandled packet");
return 0;
}
}
}
void cMultiVersionProtocol::HandlePacketStatusRequest(cClientHandle & a_Client, cByteBuffer & a_Out)
{
cServer * Server = cRoot::Get()->GetServer();
AString ServerDescription = Server->GetDescription();
auto NumPlayers = static_cast<signed>(Server->GetNumPlayers());
auto MaxPlayers = static_cast<signed>(Server->GetMaxPlayers());
AString Favicon = Server->GetFaviconData();
cRoot::Get()->GetPluginManager()->CallHookServerPing(a_Client, ServerDescription, NumPlayers, MaxPlayers, Favicon);
// Version:
Json::Value Version;
Version["name"] = "Cuberite " MCS_CLIENT_VERSIONS;
Version["protocol"] = MCS_LATEST_PROTOCOL_VERSION;
// Players:
Json::Value Players;
Players["online"] = NumPlayers;
Players["max"] = MaxPlayers;
// TODO: Add "sample"
// Description:
Json::Value Description;
Description["text"] = ServerDescription.c_str();
// Create the response:
Json::Value ResponseValue;
ResponseValue["version"] = Version;
ResponseValue["players"] = Players;
ResponseValue["description"] = Description;
if (!Favicon.empty())
{
ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str());
}
AString Response = JsonUtils::WriteFastString(ResponseValue);
VERIFY(a_Out.WriteVarInt32(GetPacketID(cProtocol::ePacketType::pktStatusResponse)));
VERIFY(a_Out.WriteVarUTF8String(Response));
}
void cMultiVersionProtocol::HandlePacketStatusPing(cClientHandle & a_Client, cByteBuffer & a_Out)
{
Int64 Timestamp;
if (!m_Buffer.ReadBEInt64(Timestamp))
{
return;
}
VERIFY(a_Out.WriteVarInt32(GetPacketID(cProtocol::ePacketType::pktPingResponse)));
VERIFY(a_Out.WriteBEInt64(Timestamp));
}