MsgBoard commands: Handle and assemble on clients

MsgBoard commands used to be evaluated on the issuing client. Malicious
clients would be able to insert arbitrary C4Script code to be executed
instead of the scenario-defined command; other clients would not be able
to tell the difference.

Instead, we now only send the command identifier, issuing player and
command parameter. This is still not perfect because clients can
insert any player they want, but it's better than before.

Part of #936.
stable-5.4
Nicolas Hake 2013-09-02 20:47:03 +02:00
parent f920e853e0
commit f2dba4a6b3
5 changed files with 70 additions and 34 deletions

View File

@ -49,6 +49,7 @@
#include <C4PlayerList.h>
#include <C4GameObjects.h>
#include <C4GameControl.h>
#include "gui/C4MessageInput.h"
#ifndef NOAULDEBUG
#include <C4AulDebug.h>
@ -317,6 +318,45 @@ void C4ControlMsgBoardReply::CompileFunc(StdCompiler *pComp)
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlMsgBoardCmd
void C4ControlMsgBoardCmd::Execute() const
{
C4Player *source_player = ::Players.Get(player);
// don't handle this if the game isn't actually running
if (!::Game.IsRunning) return;
// fetch command script
C4MessageBoardCommand *cmd = ::MessageInput.GetCommand(command.getData());
if (!cmd) return;
StdCopyStrBuf script(cmd->Script);
// interpolate parameters as required
script.Replace("%player%", FormatString("%d", player).getData());
if (parameter)
{
script.Replace("%d", FormatString("%d", std::atoi(parameter.getData())).getData());
StdCopyStrBuf escaped_param(parameter);
escaped_param.EscapeString();
script.Replace("%s", escaped_param.getData());
}
// Run script
C4Value rv(::ScriptEngine.DirectExec(nullptr, script.getData(), "message board command"));
#ifndef NOAULDEBUG
C4AulDebug* pDebug = C4AulDebug::GetDebugger();
if (pDebug)
pDebug->ControlScriptEvaluated(script.getData(), rv.GetDataString().getData());
#endif
}
void C4ControlMsgBoardCmd::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(player, "Player", NO_OWNER));
pComp->Value(mkNamingAdapt(command, "Command"));
pComp->Value(mkNamingAdapt(parameter, "Parameter"));
C4ControlPacket::CompileFunc(pComp);
}
// *** C4ControlPlayerSelect
C4ControlPlayerSelect::C4ControlPlayerSelect(int32_t iPlr, const C4ObjectList &Objs, bool fIsAlt)

View File

@ -171,6 +171,25 @@ public:
DECLARE_C4CONTROL_VIRTUALS
};
class C4ControlMsgBoardCmd : public C4ControlPacket // sync
{
public:
C4ControlMsgBoardCmd()
: player(NO_OWNER)
{}
C4ControlMsgBoardCmd(const char *command, const char *parameter, int32_t player)
: command(command), parameter(parameter), player(player)
{}
private:
StdCopyStrBuf command;
StdCopyStrBuf parameter;
int32_t player;
public:
DECLARE_C4CONTROL_VIRTUALS
};
class C4ControlPlayerSelect : public C4ControlPacket // sync
{
public:

View File

@ -779,40 +779,15 @@ bool C4MessageInput::ProcessCommand(const char *szCommand)
if (Game.IsRunning)
if ((pCmd = GetCommand(szCmdName)))
{
StdStrBuf Script, CmdScript;
// replace %player% by calling player number
if (SSearch(pCmd->Script, "%player%"))
{
int32_t iLocalPlr = NO_OWNER;
C4Player *pLocalPlr = ::Players.GetLocalByIndex(0);
if (pLocalPlr) iLocalPlr = pLocalPlr->Number;
StdStrBuf sLocalPlr; sLocalPlr.Format("%d", iLocalPlr);
CmdScript.Copy(pCmd->Script);
CmdScript.Replace("%player%", sLocalPlr.getData());
}
else
{
CmdScript.Ref(pCmd->Script);
}
// insert parameters
if (SSearch(CmdScript.getData(), "%d"))
{
// make sure it's a number by converting
Script.Format(CmdScript.getData(), (int) atoi(pCmdPar));
}
else if (SSearch(CmdScript.getData(), "%s"))
{
// escape strings
StdStrBuf Par;
Par.Copy(pCmdPar);
Par.EscapeString();
// compose script
Script.Format(CmdScript.getData(), Par.getData());
}
else
Script = CmdScript.getData();
// add script
::Control.DoInput(CID_Script, new C4ControlScript(Script.getData()), CDT_Decide);
// get player number of first local player; if multiple players
// share one computer, we can't distinguish between them anyway
int32_t player_num = NO_OWNER;
C4Player *player = ::Players.GetLocalByIndex(0);
if (player) player_num = player->Number;
// send command to network
::Control.DoInput(CID_MsgBoardCmd, new C4ControlMsgBoardCmd(szCmdName, pCmdPar, player_num), CDT_Decide);
// ok
return true;
}

View File

@ -117,6 +117,7 @@ const C4PktHandlingData PktHandlingData[] =
{ CID_Set, PC_Control, "Set", false, true, 0, PKT_UNPACK(C4ControlSet) },
{ CID_Script, PC_Control, "Script", false, true, 0, PKT_UNPACK(C4ControlScript) },
{ CID_MsgBoardReply,PC_Control, "Message Board Reply", false, true, 0, PKT_UNPACK(C4ControlMsgBoardReply)},
{ CID_MsgBoardCmd ,PC_Control, "Message Board Command", false, true, 0, PKT_UNPACK(C4ControlMsgBoardCmd)},
{ CID_PlrInfo, PC_Control, "Player Info", false, true, 0, PKT_UNPACK(C4ControlPlayerInfo) },
{ CID_JoinPlr, PC_Control, "Join Player", false, true, 0, PKT_UNPACK(C4ControlJoinPlayer) },
{ CID_RemovePlr, PC_Control, "Remove Player", false, true, 0, PKT_UNPACK(C4ControlRemovePlr) },

View File

@ -154,6 +154,7 @@ enum C4PacketType
CID_Set = CID_First | 0x07,
CID_Script = CID_First | 0x08,
CID_MsgBoardReply = CID_First | 0x09,
CID_MsgBoardCmd = CID_First | 0x0A,
CID_PlrInfo = CID_First | 0x10,
CID_JoinPlr = CID_First | 0x11,