333 lines
7.9 KiB
C++
333 lines
7.9 KiB
C++
|
|
||
|
// RCONClient.cpp
|
||
|
|
||
|
// Implements the main app entrypoint
|
||
|
|
||
|
#include "Globals.h"
|
||
|
#include "OSSupport/Socket.h"
|
||
|
#include "ByteBuffer.h"
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// If set to true, verbose messages are output to stderr. Use the "-v" or "--verbose" param to turn on
|
||
|
bool g_IsVerbose = false;
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/// This class can read and write RCON packets to / from a connected socket
|
||
|
class cRCONPacketizer
|
||
|
{
|
||
|
public:
|
||
|
enum
|
||
|
{
|
||
|
ptCommand = 2,
|
||
|
ptLogin = 3,
|
||
|
} ;
|
||
|
|
||
|
cRCONPacketizer(cSocket & a_Socket);
|
||
|
|
||
|
/// Sends the packet to the socket and waits until the response is received.
|
||
|
/// Returns true if response successfully received, false if the client disconnected or protocol error.
|
||
|
/// Dumps the reply payload to stdout.
|
||
|
bool SendPacket(int a_PacketType, const AString & a_PacketPayload);
|
||
|
|
||
|
protected:
|
||
|
/// The socket to use for reading incoming data and writing outgoing data:
|
||
|
cSocket & m_Socket;
|
||
|
|
||
|
/// The RequestID of the packet that is being sent. Incremented when the reply is received
|
||
|
int m_RequestID;
|
||
|
|
||
|
/// Receives the full response and dumps its payload to stdout.
|
||
|
/// Returns true if successful, false if the client disconnected or protocol error.
|
||
|
bool ReceiveResponse(void);
|
||
|
|
||
|
/// Parses the received response packet and dumps its payload to stdout.
|
||
|
/// Returns true if successful, false on protocol error
|
||
|
/// Assumes that the packet length has already been read from the packet
|
||
|
/// If the packet is successfully parsed, increments m_RequestID
|
||
|
bool ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength);
|
||
|
} ;
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
cRCONPacketizer::cRCONPacketizer(cSocket & a_Socket) :
|
||
|
m_Socket(a_Socket),
|
||
|
m_RequestID(0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
bool cRCONPacketizer::SendPacket(int a_PacketType, const AString & a_PacketPayload)
|
||
|
{
|
||
|
// Send the packet:
|
||
|
cByteBuffer bb(a_PacketPayload.size() + 30);
|
||
|
bb.WriteLEInt(m_RequestID);
|
||
|
bb.WriteLEInt(a_PacketType);
|
||
|
bb.WriteBuf(a_PacketPayload.data(), a_PacketPayload.size());
|
||
|
bb.WriteBEShort(0); // Padding
|
||
|
AString Packet;
|
||
|
bb.ReadAll(Packet);
|
||
|
size_t Length = Packet.size();
|
||
|
if (!m_Socket.Send((const char *)&Length, 4))
|
||
|
{
|
||
|
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
|
||
|
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
if (!m_Socket.Send(Packet.data(), Packet.size()))
|
||
|
{
|
||
|
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
|
||
|
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return ReceiveResponse();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
bool cRCONPacketizer::ReceiveResponse(void)
|
||
|
{
|
||
|
// Receive the response:
|
||
|
cByteBuffer Buffer(64 KiB);
|
||
|
while (true)
|
||
|
{
|
||
|
char buf[1024];
|
||
|
int NumReceived = m_Socket.Receive(buf, sizeof(buf), 0);
|
||
|
if (NumReceived == 0)
|
||
|
{
|
||
|
fprintf(stderr, "The remote end closed the connection. Aborting.");
|
||
|
return false;
|
||
|
}
|
||
|
if (NumReceived < 0)
|
||
|
{
|
||
|
fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.",
|
||
|
NumReceived, cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
|
||
|
);
|
||
|
return false;
|
||
|
}
|
||
|
Buffer.Write(buf, NumReceived);
|
||
|
Buffer.ResetRead();
|
||
|
|
||
|
// Check if the buffer contains the full packet:
|
||
|
if (!Buffer.CanReadBytes(14))
|
||
|
{
|
||
|
// 14 is the minimum packet size for RCON
|
||
|
continue;
|
||
|
}
|
||
|
int PacketSize;
|
||
|
VERIFY(Buffer.ReadLEInt(PacketSize));
|
||
|
if (!Buffer.CanReadBytes(PacketSize))
|
||
|
{
|
||
|
// The packet is not complete yet
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Parse the packet
|
||
|
return ParsePacket(Buffer, PacketSize);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
|
||
|
{
|
||
|
// Check that the request ID is equal
|
||
|
bool IsValid = true;
|
||
|
int RequestID = 0;
|
||
|
VERIFY(a_Buffer.ReadLEInt(RequestID));
|
||
|
if (RequestID != m_RequestID)
|
||
|
{
|
||
|
if ((RequestID == -1) && (m_RequestID == 0))
|
||
|
{
|
||
|
fprintf(stderr, "Login failed. Aborting.");
|
||
|
IsValid = false;
|
||
|
// Continue, so that the payload is printed before the program aborts.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.", RequestID, m_RequestID);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check the packet type:
|
||
|
int PacketType = 0;
|
||
|
VERIFY(a_Buffer.ReadLEInt(PacketType));
|
||
|
if (PacketType != ptCommand)
|
||
|
{
|
||
|
fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.", PacketType);
|
||
|
IsValid = false;
|
||
|
// Continue, so that the payload is printed before the program aborts.
|
||
|
}
|
||
|
|
||
|
AString Payload;
|
||
|
VERIFY(a_Buffer.ReadString(Payload, a_PacketLength - 10));
|
||
|
|
||
|
// Dump the payload to stdout, in a binary mode
|
||
|
fwrite(Payload.data(), Payload.size(), 1, stdout);
|
||
|
|
||
|
if (IsValid)
|
||
|
{
|
||
|
m_RequestID++;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// main:
|
||
|
|
||
|
int RealMain(int argc, char * argv[])
|
||
|
{
|
||
|
new cMCLogger; // Create a new logger
|
||
|
|
||
|
// Parse the cmdline params for server IP, port, password and the commands to send:
|
||
|
AString ServerAddress, Password;
|
||
|
int ServerPort = -1;
|
||
|
AStringVector Commands;
|
||
|
for (int i = 1; i < argc; i++)
|
||
|
{
|
||
|
if (((NoCaseCompare(argv[i], "-s") == 0) || (NoCaseCompare(argv[i], "--server") == 0)) && (i < argc - 1))
|
||
|
{
|
||
|
ServerAddress = argv[i + 1];
|
||
|
i++;
|
||
|
continue;
|
||
|
}
|
||
|
if (((NoCaseCompare(argv[i], "-p") == 0) || (NoCaseCompare(argv[i], "--port") == 0)) && (i < argc - 1))
|
||
|
{
|
||
|
ServerPort = atoi(argv[i + 1]);
|
||
|
i++;
|
||
|
continue;
|
||
|
}
|
||
|
if (((NoCaseCompare(argv[i], "-w") == 0) || (NoCaseCompare(argv[i], "--password") == 0)) && (i < argc - 1))
|
||
|
{
|
||
|
Password = argv[i + 1];
|
||
|
i++;
|
||
|
continue;
|
||
|
}
|
||
|
if (((NoCaseCompare(argv[i], "-c") == 0) || (NoCaseCompare(argv[i], "--cmd") == 0) || (NoCaseCompare(argv[i], "--command") == 0)) && (i < argc - 1))
|
||
|
{
|
||
|
Commands.push_back(argv[i + 1]);
|
||
|
i++;
|
||
|
continue;
|
||
|
}
|
||
|
if (((NoCaseCompare(argv[i], "-f") == 0) || (NoCaseCompare(argv[i], "--file") == 0)) && (i < argc - 1))
|
||
|
{
|
||
|
i++;
|
||
|
cFile f(argv[i], cFile::fmRead);
|
||
|
if (!f.IsOpen())
|
||
|
{
|
||
|
fprintf(stderr, "Cannot read commands from file \"%s\", aborting.", argv[i]);
|
||
|
return 2;
|
||
|
}
|
||
|
AString cmd;
|
||
|
f.ReadRestOfFile(cmd);
|
||
|
Commands.push_back(cmd);
|
||
|
continue;
|
||
|
}
|
||
|
if ((NoCaseCompare(argv[i], "-v") == 0) || (NoCaseCompare(argv[i], "--verbose") == 0))
|
||
|
{
|
||
|
fprintf(stderr, "Verbose output enabled\n");
|
||
|
g_IsVerbose = true;
|
||
|
continue;
|
||
|
}
|
||
|
fprintf(stderr, "Unknown parameter: \"%s\". Aborting.", argv[i]);
|
||
|
return 1;
|
||
|
} // for i - argv[]
|
||
|
|
||
|
if (ServerAddress.empty() || (ServerPort < 0))
|
||
|
{
|
||
|
fprintf(stderr, "Server address or port not set. Use the --server and --port parameters to set them. Aborting.");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// Connect:
|
||
|
if (cSocket::WSAStartup() != 0)
|
||
|
{
|
||
|
fprintf(stderr, "Cannot initialize network stack. Aborting\n");
|
||
|
return 6;
|
||
|
}
|
||
|
if (g_IsVerbose)
|
||
|
{
|
||
|
fprintf(stderr, "Connecting to \"%s:%d\"...\n", ServerAddress.c_str(), ServerPort);
|
||
|
}
|
||
|
cSocket s = cSocket::CreateSocket(cSocket::IPv4);
|
||
|
if (!s.ConnectIPv4(ServerAddress, (unsigned short)ServerPort))
|
||
|
{
|
||
|
fprintf(stderr, "Cannot connect to \"%s:%d\": %s\n", ServerAddress.c_str(), ServerPort, cSocket::GetLastErrorString().c_str());
|
||
|
return 3;
|
||
|
}
|
||
|
cRCONPacketizer Packetizer(s);
|
||
|
|
||
|
// Authenticate using the provided password:
|
||
|
if (!Password.empty())
|
||
|
{
|
||
|
if (g_IsVerbose)
|
||
|
{
|
||
|
fprintf(stderr, "Sending the login packet...\n");
|
||
|
}
|
||
|
if (!Packetizer.SendPacket(cRCONPacketizer::ptLogin, Password))
|
||
|
{
|
||
|
// Error message has already been printed, bail out
|
||
|
return 4;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (g_IsVerbose)
|
||
|
{
|
||
|
fprintf(stderr, "No password provided, not sending a login packet.\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (AStringVector::const_iterator itr = Commands.begin(), end = Commands.end(); itr != end; ++itr)
|
||
|
{
|
||
|
if (g_IsVerbose)
|
||
|
{
|
||
|
fprintf(stderr, "Sending command \"%s\"...\n", itr->c_str());
|
||
|
}
|
||
|
if (!Packetizer.SendPacket(cRCONPacketizer::ptCommand, *itr))
|
||
|
{
|
||
|
return 5;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
int main(int argc, char * argv[])
|
||
|
{
|
||
|
// This redirection function is only so that debugging the program is easier in MSVC - when RealMain exits, it's still possible to place a breakpoint
|
||
|
int res = RealMain(argc, argv);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|