710 lines
19 KiB
C++
710 lines
19 KiB
C++
#include "common.hh"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
#include <poll.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
#if TIBIA772
|
|
static const int TERMINALVERSION[] = {772, 772, 772};
|
|
#else
|
|
static const int TERMINALVERSION[] = {770, 770, 770};
|
|
#endif
|
|
|
|
static RSAKey *g_PrivateKey;
|
|
|
|
static int g_Listener = -1;
|
|
static TConnection *g_Connections;
|
|
static int g_MaxConnections;
|
|
|
|
static TStatusRecord *g_StatusRecords;
|
|
static int g_MaxStatusRecords;
|
|
|
|
// Connection Handling
|
|
//==============================================================================
|
|
static int ListenerBind(uint16 Port){
|
|
int Socket = socket(AF_INET, SOCK_STREAM, 0);
|
|
if(Socket == -1){
|
|
LOG_ERR("Failed to create listener socket: (%d) %s", errno, strerrordesc_np(errno));
|
|
return -1;
|
|
}
|
|
|
|
int ReuseAddr = 1;
|
|
if(setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR, &ReuseAddr, sizeof(ReuseAddr)) == -1){
|
|
LOG_ERR("Failed to set SO_REUSADDR: (%d) %s", errno, strerrordesc_np(errno));
|
|
close(Socket);
|
|
return -1;
|
|
}
|
|
|
|
int Flags = fcntl(Socket, F_GETFL);
|
|
if(Flags == -1){
|
|
LOG_ERR("Failed to get socket flags: (%d) %s", errno, strerrordesc_np(errno));
|
|
close(Socket);
|
|
return -1;
|
|
}
|
|
|
|
if(fcntl(Socket, F_SETFL, Flags | O_NONBLOCK) == -1){
|
|
LOG_ERR("Failed to set socket flags: (%d) %s", errno, strerrordesc_np(errno));
|
|
close(Socket);
|
|
return -1;
|
|
}
|
|
|
|
sockaddr_in Addr = {};
|
|
Addr.sin_family = AF_INET;
|
|
Addr.sin_port = htons(Port);
|
|
Addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
if(bind(Socket, (sockaddr*)&Addr, sizeof(Addr)) == -1){
|
|
LOG_ERR("Failed to bind socket to port %d: (%d) %s", Port, errno, strerrordesc_np(errno));
|
|
close(Socket);
|
|
return -1;
|
|
}
|
|
|
|
if(listen(Socket, 128) == -1){
|
|
LOG_ERR("Failed to listen to port %d: (%d) %s", Port, errno, strerrordesc_np(errno));
|
|
close(Socket);
|
|
return -1;
|
|
}
|
|
|
|
return Socket;
|
|
}
|
|
|
|
static int ListenerAccept(int Listener, uint32 *OutAddr, uint16 *OutPort){
|
|
while(true){
|
|
sockaddr_in SocketAddr = {};
|
|
socklen_t SocketAddrLen = sizeof(SocketAddr);
|
|
int Socket = accept(Listener, (sockaddr*)&SocketAddr, &SocketAddrLen);
|
|
if(Socket == -1){
|
|
if(errno != EAGAIN){
|
|
LOG_ERR("Failed to accept connection: (%d) %s", errno, strerrordesc_np(errno));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int Flags = fcntl(Socket, F_GETFL);
|
|
if(Flags == -1){
|
|
LOG_ERR("Failed to get socket flags: (%d) %s", errno, strerrordesc_np(errno));
|
|
close(Socket);
|
|
continue;
|
|
}
|
|
|
|
if(fcntl(Socket, F_SETFL, Flags | O_NONBLOCK) == -1){
|
|
LOG_ERR("Failed to set socket flags: (%d) %s", errno, strerrordesc_np(errno));
|
|
close(Socket);
|
|
continue;
|
|
}
|
|
|
|
if(OutAddr){
|
|
*OutAddr = ntohl(SocketAddr.sin_addr.s_addr);
|
|
}
|
|
|
|
if(OutPort){
|
|
*OutPort = ntohs(SocketAddr.sin_port);
|
|
}
|
|
|
|
return Socket;
|
|
}
|
|
}
|
|
|
|
static void CloseConnection(TConnection *Connection){
|
|
if(Connection->Socket != -1){
|
|
close(Connection->Socket);
|
|
Connection->Socket = -1;
|
|
}
|
|
}
|
|
|
|
static TConnection *AssignConnection(int Socket, uint32 Addr, uint16 Port){
|
|
int ConnectionIndex = -1;
|
|
for(int i = 0; i < g_Config.MaxConnections; i += 1){
|
|
if(g_Connections[i].State == CONNECTION_FREE){
|
|
ConnectionIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
TConnection *Connection = NULL;
|
|
if(ConnectionIndex != -1){
|
|
Connection = &g_Connections[ConnectionIndex];
|
|
Connection->State = CONNECTION_READING;
|
|
Connection->Socket = Socket;
|
|
Connection->IPAddress = (int)Addr;
|
|
Connection->StartTime = GetMonotonicUptime();
|
|
Connection->RandomSeed = (uint32)rand();
|
|
StringBufFormat(Connection->RemoteAddress,
|
|
"%d.%d.%d.%d:%d",
|
|
((Connection->IPAddress >> 24) & 0xFF),
|
|
((Connection->IPAddress >> 16) & 0xFF),
|
|
((Connection->IPAddress >> 8) & 0xFF),
|
|
((Connection->IPAddress >> 0) & 0xFF),
|
|
(int)Port);
|
|
LOG("Connection %s assigned to slot %d",
|
|
Connection->RemoteAddress, ConnectionIndex);
|
|
}
|
|
return Connection;
|
|
}
|
|
|
|
static void ReleaseConnection(TConnection *Connection){
|
|
if(Connection->State != CONNECTION_FREE){
|
|
LOG("Connection %s released", Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
memset(Connection, 0, sizeof(TConnection));
|
|
Connection->State = CONNECTION_FREE;
|
|
}
|
|
}
|
|
|
|
static void CheckConnectionInput(TConnection *Connection, int Events){
|
|
if(Connection->Socket == -1 || (Events & POLLIN) == 0){
|
|
return;
|
|
}
|
|
|
|
if(Connection->State != CONNECTION_READING){
|
|
LOG_ERR("Connection %s (State: %d) sending out-of-order data",
|
|
Connection->RemoteAddress, Connection->State);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
while(true){
|
|
int ReadSize = (Connection->RWSize > 0 ? Connection->RWSize : 2);
|
|
int BytesRead = (int)read(Connection->Socket,
|
|
(Connection->Buffer + Connection->RWPosition),
|
|
(ReadSize - Connection->RWPosition));
|
|
if(BytesRead == -1){
|
|
if(errno != EAGAIN){
|
|
// NOTE(fusion): Connection error.
|
|
CloseConnection(Connection);
|
|
}
|
|
break;
|
|
}else if(BytesRead == 0){
|
|
// NOTE(fusion): Graceful close.
|
|
CloseConnection(Connection);
|
|
break;
|
|
}
|
|
|
|
Connection->RWPosition += BytesRead;
|
|
if(Connection->RWPosition >= ReadSize){
|
|
if(Connection->RWSize != 0){
|
|
Connection->State = CONNECTION_PROCESSING;
|
|
break;
|
|
}else if(Connection->RWPosition == 2){
|
|
int PayloadSize = (int)BufferRead16LE(Connection->Buffer);
|
|
if(PayloadSize <= 0 || PayloadSize > (int)sizeof(Connection->Buffer)){
|
|
CloseConnection(Connection);
|
|
break;
|
|
}
|
|
|
|
Connection->RWSize = PayloadSize;
|
|
Connection->RWPosition = 0;
|
|
}else{
|
|
PANIC("Invalid input state (State: %d, RWSize: %d, RWPosition: %d)",
|
|
Connection->State, Connection->RWSize, Connection->RWPosition);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CheckConnectionRequest(TConnection *Connection){
|
|
if(Connection->Socket == -1){
|
|
return;
|
|
}
|
|
|
|
if(Connection->State != CONNECTION_PROCESSING){
|
|
return;
|
|
}
|
|
|
|
// PARANOID(fusion): A non-empty payload is guaranteed, due to how we parse
|
|
// input in `CheckConnectionInput` just above.
|
|
ASSERT(Connection->RWSize > 0);
|
|
|
|
int Command = Connection->Buffer[0];
|
|
if(Command == 1){
|
|
ProcessLoginRequest(Connection);
|
|
}else if(Command == 255){
|
|
ProcessStatusRequest(Connection);
|
|
}else{
|
|
LOG_ERR("Invalid command %d from %s (expected 1 or 255)",
|
|
Command, Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
}
|
|
}
|
|
|
|
static void CheckConnectionOutput(TConnection *Connection, int Events){
|
|
// NOTE(fusion): We're only polling `POLLOUT` when the connection is WRITING,
|
|
// but we want to allow requests to complete in a single cycle, so we always
|
|
// check for output if the connection is WRITING.
|
|
(void)Events;
|
|
|
|
if(Connection->Socket == -1){
|
|
return;
|
|
}
|
|
|
|
if(Connection->State != CONNECTION_WRITING){
|
|
return;
|
|
}
|
|
|
|
while(true){
|
|
int BytesWritten = (int)write(Connection->Socket,
|
|
(Connection->Buffer + Connection->RWPosition),
|
|
(Connection->RWSize - Connection->RWPosition));
|
|
if(BytesWritten == -1){
|
|
if(errno != EAGAIN){
|
|
CloseConnection(Connection);
|
|
}
|
|
break;
|
|
}
|
|
|
|
Connection->RWPosition += BytesWritten;
|
|
if(Connection->RWPosition >= Connection->RWSize){
|
|
CloseConnection(Connection);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CheckConnection(TConnection *Connection, int Events){
|
|
ASSERT((Events & POLLNVAL) == 0);
|
|
|
|
if((Events & (POLLERR | POLLHUP)) != 0){
|
|
CloseConnection(Connection);
|
|
}
|
|
|
|
if(g_Config.ConnectionTimeout > 0){
|
|
int ElapsedTime = GetMonotonicUptime() - Connection->StartTime;
|
|
if(ElapsedTime >= g_Config.ConnectionTimeout){
|
|
LOG_WARN("Connection %s TIMEDOUT (ElapsedTime: %ds, Timeout: %ds)",
|
|
Connection->RemoteAddress, ElapsedTime, g_Config.ConnectionTimeout);
|
|
CloseConnection(Connection);
|
|
}
|
|
}
|
|
|
|
if(Connection->Socket == -1){
|
|
ReleaseConnection(Connection);
|
|
}
|
|
}
|
|
|
|
static void AcceptConnections(int Events){
|
|
ASSERT(g_Listener != -1);
|
|
if((Events & POLLIN) == 0){
|
|
return;
|
|
}
|
|
|
|
while(true){
|
|
uint32 Addr;
|
|
uint16 Port;
|
|
int Socket = ListenerAccept(g_Listener, &Addr, &Port);
|
|
if(Socket == -1){
|
|
break;
|
|
}
|
|
|
|
if(AssignConnection(Socket, Addr, Port) == NULL){
|
|
LOG_ERR("Rejecting connection %08X:%d:"
|
|
" max number of connections reached (%d)",
|
|
Addr, Port, g_Config.MaxConnections);
|
|
close(Socket);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessConnections(void){
|
|
int NumFds = 0;
|
|
int MaxFds = g_MaxConnections + 1;
|
|
pollfd *Fds = (pollfd*)alloca(MaxFds * sizeof(pollfd));
|
|
int *ConnectionIndices = (int*)alloca(MaxFds * sizeof(int));
|
|
|
|
if(g_Listener != -1){
|
|
Fds[NumFds].fd = g_Listener;
|
|
Fds[NumFds].events = POLLIN;
|
|
Fds[NumFds].revents = 0;
|
|
ConnectionIndices[NumFds] = -1;
|
|
NumFds += 1;
|
|
}
|
|
|
|
for(int i = 0; i < g_Config.MaxConnections; i += 1){
|
|
if(g_Connections[i].State == CONNECTION_FREE){
|
|
continue;
|
|
}
|
|
|
|
Fds[NumFds].fd = g_Connections[i].Socket;
|
|
Fds[NumFds].events = POLLIN;
|
|
if(g_Connections[i].State == CONNECTION_WRITING){
|
|
Fds[NumFds].events |= POLLOUT;
|
|
}
|
|
Fds[NumFds].revents = 0;
|
|
ConnectionIndices[NumFds] = i;
|
|
NumFds += 1;
|
|
}
|
|
|
|
// NOTE(fusion): Block for 1 second at most, so we can properly timeout
|
|
// idle connections.
|
|
ASSERT(NumFds > 0);
|
|
int NumEvents = poll(Fds, NumFds, 1);
|
|
if(NumEvents == -1){
|
|
if(errno != ETIMEDOUT && errno != EINTR){
|
|
LOG_ERR("Failed to poll connections: (%d) %s",
|
|
errno, strerrordesc_np(errno));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// NOTE(fusion): Process connections.
|
|
for(int i = 0; i < NumFds; i += 1){
|
|
int Index = ConnectionIndices[i];
|
|
int Events = (int)Fds[i].revents;
|
|
if(Index >= 0 && Index < g_Config.MaxConnections){
|
|
TConnection *Connection = &g_Connections[Index];
|
|
CheckConnectionInput(Connection, Events);
|
|
CheckConnectionRequest(Connection);
|
|
CheckConnectionOutput(Connection, Events);
|
|
CheckConnection(Connection, Events);
|
|
}else if(Index == -1 && Fds[i].fd == g_Listener){
|
|
AcceptConnections(Events);
|
|
}else{
|
|
LOG_ERR("Unknown connection index %d", Index);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool InitConnections(void){
|
|
ASSERT(g_PrivateKey == NULL);
|
|
ASSERT(g_Listener == -1);
|
|
ASSERT(g_Connections == NULL);
|
|
ASSERT(g_StatusRecords == NULL);
|
|
|
|
g_PrivateKey = RSALoadPEM("tibia.pem");
|
|
if(g_PrivateKey == NULL){
|
|
LOG_ERR("Failed to load RSA key");
|
|
return false;
|
|
}
|
|
|
|
g_Listener = ListenerBind((uint16)g_Config.LoginPort);
|
|
if(g_Listener == -1){
|
|
LOG_ERR("Failed to bind listener to port %d", g_Config.LoginPort);
|
|
return false;
|
|
}
|
|
|
|
g_MaxConnections = g_Config.MaxConnections;
|
|
g_Connections = (TConnection*)calloc(
|
|
g_MaxConnections, sizeof(TConnection));
|
|
for(int i = 0; i < g_MaxConnections; i += 1){
|
|
g_Connections[i].State = CONNECTION_FREE;
|
|
}
|
|
|
|
g_MaxStatusRecords = g_Config.MaxStatusRecords;
|
|
g_StatusRecords = (TStatusRecord*)calloc(
|
|
g_MaxStatusRecords, sizeof(TStatusRecord));
|
|
|
|
return true;
|
|
}
|
|
|
|
void ExitConnections(void){
|
|
if(g_PrivateKey != NULL){
|
|
RSAFree(g_PrivateKey);
|
|
g_PrivateKey = NULL;
|
|
}
|
|
|
|
if(g_Listener != -1){
|
|
close(g_Listener);
|
|
g_Listener = -1;
|
|
}
|
|
|
|
if(g_Connections != NULL){
|
|
for(int i = 0; i < g_MaxConnections; i += 1){
|
|
ReleaseConnection(&g_Connections[i]);
|
|
}
|
|
|
|
free(g_Connections);
|
|
g_Connections = NULL;
|
|
}
|
|
|
|
if(g_StatusRecords != NULL){
|
|
free(g_StatusRecords);
|
|
g_StatusRecords = NULL;
|
|
}
|
|
}
|
|
|
|
// Login Request
|
|
//==============================================================================
|
|
static TWriteBuffer PrepareXTEAResponse(TConnection *Connection){
|
|
if(Connection->State != CONNECTION_PROCESSING){
|
|
LOG_ERR("Connection %s is not PROCESSING (State: %d)",
|
|
Connection->RemoteAddress, Connection->State);
|
|
CloseConnection(Connection);
|
|
return TWriteBuffer(NULL, 0);
|
|
}
|
|
|
|
TWriteBuffer WriteBuffer(Connection->Buffer, sizeof(Connection->Buffer));
|
|
WriteBuffer.Write16(0); // Encrypted Size
|
|
WriteBuffer.Write16(0); // Data Size
|
|
return WriteBuffer;
|
|
}
|
|
|
|
static void SendXTEAResponse(TConnection *Connection, TWriteBuffer *WriteBuffer){
|
|
if(Connection->State != CONNECTION_PROCESSING){
|
|
LOG_ERR("Connection %s is not PROCESSING (State: %d)",
|
|
Connection->RemoteAddress, Connection->State);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
ASSERT(WriteBuffer != NULL
|
|
&& WriteBuffer->Buffer == Connection->Buffer
|
|
&& WriteBuffer->Size == sizeof(Connection->Buffer)
|
|
&& WriteBuffer->Position > 4);
|
|
|
|
int DataSize = WriteBuffer->Position - 4;
|
|
int EncryptedSize = WriteBuffer->Position - 2;
|
|
while((EncryptedSize % 8) != 0){
|
|
WriteBuffer->Write8(rand_r(&Connection->RandomSeed));
|
|
EncryptedSize += 1;
|
|
}
|
|
|
|
if(WriteBuffer->Overflowed()){
|
|
LOG_ERR("Write buffer overflowed when writing response to %s",
|
|
Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
WriteBuffer->Rewrite16(0, EncryptedSize);
|
|
WriteBuffer->Rewrite16(2, DataSize);
|
|
XTEAEncrypt(Connection->XTEA,
|
|
WriteBuffer->Buffer + 2,
|
|
WriteBuffer->Position - 2);
|
|
Connection->State = CONNECTION_WRITING;
|
|
Connection->RWSize = WriteBuffer->Position;
|
|
Connection->RWPosition = 0;
|
|
}
|
|
|
|
static void SendLoginError(TConnection *Connection, const char *Message){
|
|
TWriteBuffer WriteBuffer = PrepareXTEAResponse(Connection);
|
|
WriteBuffer.Write8(10); // LOGIN_ERROR
|
|
WriteBuffer.WriteString(Message);
|
|
SendXTEAResponse(Connection, &WriteBuffer);
|
|
}
|
|
|
|
static void SendCharacterList(TConnection *Connection, int NumCharacters,
|
|
TCharacterLoginData *Characters, int PremiumDays){
|
|
TWriteBuffer WriteBuffer = PrepareXTEAResponse(Connection);
|
|
|
|
if(g_Config.Motd[0] != 0){
|
|
WriteBuffer.Write8(20); // MOTD
|
|
WriteBuffer.WriteString(g_Config.Motd);
|
|
}
|
|
|
|
WriteBuffer.Write8(100); // CHARACTER_LIST
|
|
if(NumCharacters > UINT8_MAX){
|
|
NumCharacters = UINT8_MAX;
|
|
}
|
|
WriteBuffer.Write8(NumCharacters);
|
|
for(int i = 0; i < NumCharacters; i += 1){
|
|
WriteBuffer.WriteString(Characters[i].Name);
|
|
WriteBuffer.WriteString(Characters[i].WorldName);
|
|
WriteBuffer.Write32BE((uint32)Characters[i].WorldAddress);
|
|
WriteBuffer.Write16((uint16)Characters[i].WorldPort);
|
|
}
|
|
WriteBuffer.Write16((uint16)PremiumDays);
|
|
|
|
SendXTEAResponse(Connection, &WriteBuffer);
|
|
}
|
|
|
|
void ProcessLoginRequest(TConnection *Connection){
|
|
if(Connection->RWSize != 145){
|
|
LOG_ERR("Invalid login request size from %s (expected 145, got %d)",
|
|
Connection->RemoteAddress, Connection->RWSize);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
TReadBuffer ReadBuffer(Connection->Buffer, Connection->RWSize);
|
|
ReadBuffer.Read8(); // always 1 for a login request
|
|
int TerminalType = ReadBuffer.Read16();
|
|
int TerminalVersion = ReadBuffer.Read16();
|
|
ReadBuffer.Read32(); // DATSIGNATURE
|
|
ReadBuffer.Read32(); // SPRSIGNATURE
|
|
ReadBuffer.Read32(); // PICSIGNATURE
|
|
|
|
uint8 AsymmetricData[128];
|
|
ReadBuffer.ReadBytes(AsymmetricData, sizeof(AsymmetricData));
|
|
if(ReadBuffer.Overflowed()){
|
|
LOG_ERR("Input buffer overflowed while reading login command from %s",
|
|
Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
// IMPORTANT(fusion): Without a checksum, there is no way of validating
|
|
// the asymmetric data. The best we can do is to verify that the first
|
|
// plaintext byte is ZERO, but that alone isn't enough.
|
|
if(!RSADecrypt(g_PrivateKey, AsymmetricData, sizeof(AsymmetricData)) || AsymmetricData[0] != 0){
|
|
LOG_ERR("Failed to decrypt asymmetric data from %s",
|
|
Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
ReadBuffer = TReadBuffer(AsymmetricData, sizeof(AsymmetricData));
|
|
ReadBuffer.Read8(); // always zero
|
|
Connection->XTEA[0] = ReadBuffer.Read32();
|
|
Connection->XTEA[1] = ReadBuffer.Read32();
|
|
Connection->XTEA[2] = ReadBuffer.Read32();
|
|
Connection->XTEA[3] = ReadBuffer.Read32();
|
|
|
|
char Password[30];
|
|
int AccountID = ReadBuffer.Read32();
|
|
ReadBuffer.ReadString(Password, sizeof(Password));
|
|
if(ReadBuffer.Overflowed()){
|
|
LOG_ERR("Malformed asymmetric data from %s", Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
if(AccountID <= 0){
|
|
SendLoginError(Connection, "You must enter an account number.");
|
|
return;
|
|
}
|
|
|
|
if(TerminalType < 0 || TerminalType >= NARRAY(TERMINALVERSION)
|
|
|| TERMINALVERSION[TerminalType] != TerminalVersion){
|
|
SendLoginError(Connection,
|
|
"Your terminal version is too old.\n"
|
|
"Please get a new version at\n"
|
|
"http://www.tibia.com.");
|
|
return;
|
|
}
|
|
|
|
char IPString[16];
|
|
StringBufFormat(IPString, "%d.%d.%d.%d",
|
|
((Connection->IPAddress >> 24) & 0xFF),
|
|
((Connection->IPAddress >> 16) & 0xFF),
|
|
((Connection->IPAddress >> 8) & 0xFF),
|
|
((Connection->IPAddress >> 0) & 0xFF));
|
|
|
|
int NumCharacters = 0;
|
|
int PremiumDays = 0;
|
|
TCharacterLoginData Characters[50];
|
|
int LoginCode = LoginAccount(AccountID, Password, IPString,
|
|
NARRAY(Characters), &NumCharacters, Characters, &PremiumDays);
|
|
switch(LoginCode){
|
|
case 0:{
|
|
SendCharacterList(Connection, NumCharacters, Characters, PremiumDays);
|
|
break;
|
|
}
|
|
|
|
case 1: // Invalid account number
|
|
case 2:{ // Invalid password
|
|
SendLoginError(Connection, "Accountnumber or password is not correct.");
|
|
break;
|
|
}
|
|
|
|
case 3:{
|
|
SendLoginError(Connection, "Account disabled for five minutes. Please wait.");
|
|
break;
|
|
}
|
|
|
|
case 4:{
|
|
SendLoginError(Connection, "IP address blocked for 30 minutes. Please wait.");
|
|
break;
|
|
}
|
|
|
|
case 5:{
|
|
SendLoginError(Connection, "Your account is banished.");
|
|
break;
|
|
}
|
|
|
|
case 6:{
|
|
SendLoginError(Connection, "Your IP address is banished.");
|
|
break;
|
|
}
|
|
|
|
default:{
|
|
if(LoginCode != -1){
|
|
LOG_ERR("Invalid login code %d", LoginCode);
|
|
}
|
|
SendLoginError(Connection, "Internal error, closing connection.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Status Request
|
|
//==============================================================================
|
|
static bool AllowStatusRequest(int IPAddress){
|
|
TStatusRecord *Record = NULL;
|
|
int LeastRecentlyUsedIndex = 0;
|
|
int LeastRecentlyUsedTime = g_StatusRecords[0].Timestamp;
|
|
int TimeNow = GetMonotonicUptime();
|
|
for(int i = 0; i < g_MaxStatusRecords; i += 1){
|
|
if(g_StatusRecords[i].Timestamp < LeastRecentlyUsedTime){
|
|
LeastRecentlyUsedIndex = i;
|
|
LeastRecentlyUsedTime = g_StatusRecords[i].Timestamp;
|
|
}
|
|
|
|
if(g_StatusRecords[i].IPAddress == IPAddress){
|
|
Record = &g_StatusRecords[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Result = false;
|
|
if(Record == NULL){
|
|
Record = &g_StatusRecords[LeastRecentlyUsedIndex];
|
|
Record->IPAddress = IPAddress;
|
|
Record->Timestamp = TimeNow;
|
|
Result = true;
|
|
}else if((TimeNow - Record->Timestamp) >= g_Config.MinStatusInterval){
|
|
Record->Timestamp = TimeNow;
|
|
Result = true;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static void SendStatusString(TConnection *Connection, const char *StatusString){
|
|
if(Connection->State != CONNECTION_PROCESSING){
|
|
LOG_ERR("Connection %s is not PROCESSING (State: %d)",
|
|
Connection->RemoteAddress, Connection->State);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
int Length = (int)strlen(StatusString);
|
|
if(Length > (int)sizeof(Connection->Buffer)){
|
|
Length = (int)sizeof(Connection->Buffer);
|
|
}
|
|
|
|
Connection->RWSize = Length;
|
|
Connection->RWPosition = 0;
|
|
memcpy(Connection->Buffer, StatusString, Length);
|
|
Connection->State = CONNECTION_WRITING;
|
|
}
|
|
|
|
void ProcessStatusRequest(TConnection *Connection){
|
|
if(!AllowStatusRequest(Connection->IPAddress)){
|
|
LOG_ERR("Too many status requests from %s", Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
return;
|
|
}
|
|
|
|
TReadBuffer ReadBuffer(Connection->Buffer, Connection->RWSize);
|
|
ReadBuffer.Read8(); // always 255 for a status request
|
|
int Format = (int)ReadBuffer.Read8();
|
|
if(Format == 255){ // XML
|
|
char Request[5] = {};
|
|
ReadBuffer.ReadBytes((uint8*)Request, 4);
|
|
if(StringEqCI(Request, "info")){
|
|
const char *StatusString = GetStatusString();
|
|
SendStatusString(Connection, StatusString);
|
|
}else{
|
|
LOG_WARN("Invalid status request \"%s\" from %s",
|
|
Request, Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
}
|
|
}else{
|
|
LOG_WARN("Invalid status format %d from %s",
|
|
Format, Connection->RemoteAddress);
|
|
CloseConnection(Connection);
|
|
}
|
|
}
|
|
|