////////////////////////////////////////////////////////////////////////
// OpenTibia - an opensource roleplaying game
////////////////////////////////////////////////////////////////////////
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
////////////////////////////////////////////////////////////////////////
#ifdef __REMOTE_CONTROL__
#include "otpch.h"
#include
#include "admin.h"
#include "rsa.h"
#include "tools.h"
#include "configmanager.h"
#include "game.h"
#include "connection.h"
#include "outputmessage.h"
#include "networkmessage.h"
#include "house.h"
#include "town.h"
#include "iologindata.h"
extern Game g_game;
extern ConfigManager g_config;
Admin* g_admin = NULL;
#ifdef __ENABLE_SERVER_DIAGNOSTIC__
uint32_t ProtocolAdmin::protocolAdminCount = 0;
#endif
void ProtocolAdmin::onRecvFirstMessage(NetworkMessage& msg)
{
if(!g_admin->enabled())
{
getConnection()->close();
return;
}
m_state = NO_CONNECTED;
if(!g_admin->allowIP(getIP()))
{
addLogLine(LOGTYPE_EVENT, "ip not allowed");
getConnection()->close();
return;
}
if(!g_admin->addConnection())
{
addLogLine(LOGTYPE_EVENT, "cannot add new connection");
getConnection()->close();
return;
}
addLogLine(LOGTYPE_EVENT, "sending HELLO");
if(OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false))
{
TRACK_MESSAGE(output);
output->AddByte(AP_MSG_HELLO);
output->AddU32(1); //version
output->AddString("OTADMIN");
output->AddU16(g_admin->getProtocolPolicy()); //security policy
output->AddU32(g_admin->getProtocolOptions()); //protocol options(encryption, ...)
OutputMessagePool::getInstance()->send(output);
}
m_lastCommand = time(NULL);
m_state = ENCRYPTION_NO_SET;
}
void ProtocolAdmin::parsePacket(NetworkMessage& msg)
{
if(g_game.getGameState() == GAME_STATE_SHUTDOWN)
{
getConnection()->close();
return;
}
uint8_t recvbyte = msg.GetByte();
OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false);
if(!output)
return;
TRACK_MESSAGE(output);
switch(m_state)
{
case ENCRYPTION_NO_SET:
{
if(g_admin->requireEncryption())
{
if((time(NULL) - m_startTime) > 30000)
{
getConnection()->close();
addLogLine(LOGTYPE_EVENT, "encryption timeout");
return;
}
if(recvbyte != AP_MSG_ENCRYPTION && recvbyte != AP_MSG_KEY_EXCHANGE)
{
output->AddByte(AP_MSG_ERROR);
output->AddString("encryption needed");
OutputMessagePool::getInstance()->send(output);
getConnection()->close();
addLogLine(LOGTYPE_EVENT, "wrong command while ENCRYPTION_NO_SET");
return;
}
}
else
m_state = NO_LOGGED_IN;
break;
}
case NO_LOGGED_IN:
{
if(g_admin->requireLogin())
{
if((time(NULL) - m_startTime) > 30000)
{
//login timeout
getConnection()->close();
addLogLine(LOGTYPE_EVENT, "login timeout");
return;
}
if(m_loginTries > 3)
{
output->AddByte(AP_MSG_ERROR);
output->AddString("too many login tries");
OutputMessagePool::getInstance()->send(output);
getConnection()->close();
addLogLine(LOGTYPE_EVENT, "too many login tries");
return;
}
if(recvbyte != AP_MSG_LOGIN)
{
output->AddByte(AP_MSG_ERROR);
output->AddString("you are not logged in");
OutputMessagePool::getInstance()->send(output);
getConnection()->close();
addLogLine(LOGTYPE_EVENT, "wrong command while NO_LOGGED_IN");
return;
}
}
else
m_state = LOGGED_IN;
break;
}
case LOGGED_IN:
break;
default:
{
getConnection()->close();
addLogLine(LOGTYPE_EVENT, "no valid connection state!!!");
return;
}
}
m_lastCommand = time(NULL);
switch(recvbyte)
{
case AP_MSG_LOGIN:
{
if(m_state == NO_LOGGED_IN && g_admin->requireLogin())
{
std::string password = msg.GetString();
if(g_admin->passwordMatch(password))
{
m_state = LOGGED_IN;
output->AddByte(AP_MSG_LOGIN_OK);
addLogLine(LOGTYPE_EVENT, "login ok");
}
else
{
m_loginTries++;
output->AddByte(AP_MSG_LOGIN_FAILED);
output->AddString("wrong password");
addLogLine(LOGTYPE_EVENT, "login failed.("+ password + ")");
}
}
else
{
output->AddByte(AP_MSG_LOGIN_FAILED);
output->AddString("cannot login");
addLogLine(LOGTYPE_EVENT, "wrong state at login");
}
break;
}
case AP_MSG_ENCRYPTION:
{
if(m_state == ENCRYPTION_NO_SET && g_admin->requireEncryption())
{
uint8_t keyType = msg.GetByte();
switch(keyType)
{
case ENCRYPTION_RSA1024XTEA:
{
RSA* rsa = g_admin->getRSAKey(ENCRYPTION_RSA1024XTEA);
if(!rsa)
{
output->AddByte(AP_MSG_ENCRYPTION_FAILED);
addLogLine(LOGTYPE_EVENT, "no valid server key type");
break;
}
if(RSA_decrypt(rsa, msg))
{
m_state = NO_LOGGED_IN;
uint32_t k[4]= {msg.GetU32(), msg.GetU32(), msg.GetU32(), msg.GetU32()};
//use for in/out the new key we have
enableXTEAEncryption();
setXTEAKey(k);
output->AddByte(AP_MSG_ENCRYPTION_OK);
addLogLine(LOGTYPE_EVENT, "encryption ok");
}
else
{
output->AddByte(AP_MSG_ENCRYPTION_FAILED);
output->AddString("wrong encrypted packet");
addLogLine(LOGTYPE_EVENT, "wrong encrypted packet");
}
break;
}
default:
{
output->AddByte(AP_MSG_ENCRYPTION_FAILED);
output->AddString("no valid key type");
addLogLine(LOGTYPE_EVENT, "no valid client key type");
break;
}
}
}
else
{
output->AddByte(AP_MSG_ENCRYPTION_FAILED);
output->AddString("cannot set encryption");
addLogLine(LOGTYPE_EVENT, "cannot set encryption");
}
break;
}
case AP_MSG_KEY_EXCHANGE:
{
if(m_state == ENCRYPTION_NO_SET && g_admin->requireEncryption())
{
uint8_t keyType = msg.GetByte();
switch(keyType)
{
case ENCRYPTION_RSA1024XTEA:
{
RSA* rsa = g_admin->getRSAKey(ENCRYPTION_RSA1024XTEA);
if(!rsa)
{
output->AddByte(AP_MSG_KEY_EXCHANGE_FAILED);
addLogLine(LOGTYPE_EVENT, "no valid server key type");
break;
}
output->AddByte(AP_MSG_KEY_EXCHANGE_OK);
output->AddByte(ENCRYPTION_RSA1024XTEA);
char RSAPublicKey[128];
rsa->getPublicKey(RSAPublicKey);
output->AddBytes(RSAPublicKey, 128);
break;
}
default:
{
output->AddByte(AP_MSG_KEY_EXCHANGE_FAILED);
addLogLine(LOGTYPE_EVENT, "no valid client key type");
break;
}
}
}
else
{
output->AddByte(AP_MSG_KEY_EXCHANGE_FAILED);
output->AddString("cannot get public key");
addLogLine(LOGTYPE_EVENT, "cannot get public key");
}
break;
}
case AP_MSG_COMMAND:
{
if(m_state != LOGGED_IN)
{
addLogLine(LOGTYPE_EVENT, "recvbyte == AP_MSG_COMMAND && m_state != LOGGED_IN !!!");
break;
}
uint8_t command = msg.GetByte();
switch(command)
{
case CMD_SAVE_SERVER:
case CMD_SHALLOW_SAVE_SERVER:
{
addLogLine(LOGTYPE_EVENT, "saving server");
Dispatcher::getInstance().addTask(createTask(boost::bind(
&Game::saveGameState, &g_game, (command == CMD_SHALLOW_SAVE_SERVER))));
output->AddByte(AP_MSG_COMMAND_OK);
break;
}
case CMD_CLOSE_SERVER:
{
addLogLine(LOGTYPE_EVENT, "closing server");
Dispatcher::getInstance().addTask(createTask(boost::bind(
&Game::setGameState, &g_game, GAME_STATE_CLOSED)));
output->AddByte(AP_MSG_COMMAND_OK);
break;
}
case CMD_OPEN_SERVER:
{
addLogLine(LOGTYPE_EVENT, "opening server");
Dispatcher::getInstance().addTask(createTask(boost::bind(
&Game::setGameState, &g_game, GAME_STATE_NORMAL)));
output->AddByte(AP_MSG_COMMAND_OK);
break;
}
case CMD_SHUTDOWN_SERVER:
{
addLogLine(LOGTYPE_EVENT, "shutting down server");
Dispatcher::getInstance().addTask(createTask(boost::bind(
&Game::setGameState, &g_game, GAME_STATE_SHUTDOWN)));
output->AddByte(AP_MSG_COMMAND_OK);
break;
}
case CMD_PAY_HOUSES:
{
Dispatcher::getInstance().addTask(createTask(boost::bind(
&ProtocolAdmin::adminCommandPayHouses, this)));
break;
}
case CMD_RELOAD_SCRIPTS:
{
const int8_t reload = msg.GetByte();
Dispatcher::getInstance().addTask(createTask(boost::bind(
&ProtocolAdmin::adminCommandReload, this, reload)));
break;
}
case CMD_KICK:
{
const std::string param = msg.GetString();
Dispatcher::getInstance().addTask(createTask(boost::bind(
&ProtocolAdmin::adminCommandKickPlayer, this, param)));
break;
}
case CMD_SETOWNER:
{
const std::string param = msg.GetString();
Dispatcher::getInstance().addTask(createTask(boost::bind(
&ProtocolAdmin::adminCommandSetOwner, this, param)));
break;
}
case CMD_SEND_MAIL:
{
const std::string xmlData = msg.GetString();
Dispatcher::getInstance().addTask(createTask(boost::bind(
&ProtocolAdmin::adminCommandSendMail, this, xmlData)));
break;
}
case CMD_BROADCAST:
{
const std::string param = msg.GetString();
addLogLine(LOGTYPE_EVENT, "broadcasting: " + param);
Dispatcher::getInstance().addTask(createTask(boost::bind(
&Game::broadcastMessage, &g_game, param, MSG_STATUS_WARNING)));
output->AddByte(AP_MSG_COMMAND_OK);
break;
}
default:
{
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("not known server command");
addLogLine(LOGTYPE_EVENT, "not known server command");
}
}
break;
}
case AP_MSG_PING:
output->AddByte(AP_MSG_PING_OK);
break;
case AP_MSG_KEEP_ALIVE:
break;
default:
{
output->AddByte(AP_MSG_ERROR);
output->AddString("not known command byte");
addLogLine(LOGTYPE_EVENT, "not known command byte");
break;
}
}
if(output->getMessageLength() > 0)
OutputMessagePool::getInstance()->send(output);
}
void ProtocolAdmin::deleteProtocolTask()
{
addLogLine(LOGTYPE_EVENT, "end connection");
g_admin->removeConnection();
Protocol::deleteProtocolTask();
}
void ProtocolAdmin::adminCommandPayHouses()
{
OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false);
if(!output)
return;
Houses::getInstance()->payHouses();
addLogLine(LOGTYPE_EVENT, "pay houses ok");
TRACK_MESSAGE(output);
output->AddByte(AP_MSG_COMMAND_OK);
OutputMessagePool::getInstance()->send(output);
}
void ProtocolAdmin::adminCommandReload(int8_t reload)
{
OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false);
if(!output)
return;
g_game.reloadInfo((ReloadInfo_t)reload);
addLogLine(LOGTYPE_EVENT, "reload ok");
TRACK_MESSAGE(output);
output->AddByte(AP_MSG_COMMAND_OK);
OutputMessagePool::getInstance()->send(output);
}
void ProtocolAdmin::adminCommandKickPlayer(const std::string& param)
{
OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false);
if(!output)
return;
TRACK_MESSAGE(output);
Player* player = NULL;
if(g_game.getPlayerByNameWildcard(param, player) == RET_NOERROR)
{
Scheduler::getInstance().addEvent(createSchedulerTask(SCHEDULER_MINTICKS, boost::bind(&Game::kickPlayer, &g_game, player->getID(), false)));
addLogLine(LOGTYPE_EVENT, "kicking player " + player->getName());
output->AddByte(AP_MSG_COMMAND_OK);
}
else
{
addLogLine(LOGTYPE_EVENT, "failed setting kick for player " + param);
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("player is not online");
}
OutputMessagePool::getInstance()->send(output);
}
void ProtocolAdmin::adminCommandSetOwner(const std::string& param)
{
OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false);
if(!output)
return;
StringVec params = explodeString(param, ",");
for(StringVec::iterator it = params.begin(); it != params.end(); ++it)
trimString(*it);
TRACK_MESSAGE(output);
int32_t houseId = atoi(params[0].c_str());
if(houseId > 0)
{
size_t size = params.size();
if(size > 1)
{
std::string name = params[1];
bool clean = true;
if(size > 2)
clean = booleanString(params[2]);
if(House* house = Houses::getInstance()->getHouse(houseId))
{
uint32_t guid;
if(IOLoginData::getInstance()->getGuidByName(guid, name))
{
if(house->setOwnerEx(guid, clean))
{
addLogLine(LOGTYPE_EVENT, "Set " + name + " as new owner of house with id " + house->getName());
output->AddByte(AP_MSG_COMMAND_OK);
}
else
{
addLogLine(LOGTYPE_EVENT, "Failed setting " + name + " as new owner of house with id " + house->getName());
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("failed while setting owner");
}
}
else
{
addLogLine(LOGTYPE_EVENT, "Could not find player with name: " + name);
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("such player does not exists");
}
}
else
{
addLogLine(LOGTYPE_EVENT, "Could not find house with id: " + houseId);
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("such house does not exists");
}
}
else
{
addLogLine(LOGTYPE_EVENT, "Not enough params given, param data: " + param);
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("not enough params");
}
}
else
{
addLogLine(LOGTYPE_EVENT, "Specified house id is not a valid one: " + params[0]);
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("invalid house id");
}
OutputMessagePool::getInstance()->send(output);
}
void ProtocolAdmin::adminCommandSendMail(const std::string& xmlData)
{
OutputMessage_ptr output = OutputMessagePool::getInstance()->getOutputMessage(this, false);
if(!output)
return;
std::string name;
uint32_t depotId;
TRACK_MESSAGE(output);
if(Item* mailItem = Admin::createMail(xmlData, name, depotId))
{
if(IOLoginData::getInstance()->playerMail(NULL, name, depotId, mailItem))
{
addLogLine(LOGTYPE_EVENT, "sent mailbox to " + name);
output->AddByte(AP_MSG_COMMAND_OK);
}
else
{
addLogLine(LOGTYPE_EVENT, "failed sending mailbox to " + name);
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("could not send the box");
}
}
else
{
addLogLine(LOGTYPE_EVENT, "failed parsing mailbox");
output->AddByte(AP_MSG_COMMAND_FAILED);
output->AddString("could not parse the box");
}
OutputMessagePool::getInstance()->send(output);
}
bool Admin::loadFromXml()
{
xmlDocPtr doc = xmlParseFile(getFilePath(FILE_TYPE_XML, "admin.xml").c_str());
if(!doc)
{
std::cout << "[Warning - Admin::loadFromXml] Cannot load admin file." << std::endl;
std::cout << getLastXMLError() << std::endl;
return false;
}
xmlNodePtr p, q, root = xmlDocGetRootElement(doc);
if(xmlStrcmp(root->name,(const xmlChar*)"otadmin"))
{
std::cout << "[Error - Admin::loadFromXml] Malformed admin file" << std::endl;
xmlFreeDoc(doc);
return false;
}
std::string strValue;
if(readXMLString(root, "enabled", strValue))
m_enabled = booleanString(strValue);
int32_t intValue;
p = root->children;
while(p)
{
if(xmlStrEqual(p->name, (const xmlChar*)"security"))
{
if(readXMLString(p, "onlylocalhost", strValue))
m_onlyLocalHost = booleanString(strValue);
if(readXMLInteger(p, "maxconnections", intValue) && intValue > 0)
m_maxConnections = intValue;
if(readXMLString(p, "loginrequired", strValue))
m_requireLogin = booleanString(strValue);
if(readXMLString(p, "loginpassword", strValue))
m_password = strValue;
else if(m_requireLogin)
std::cout << "[Warning - Admin::loadFromXml]: Login required, but no password specified - using default." << std::endl;
}
else if(xmlStrEqual(p->name, (const xmlChar*)"encryption"))
{
if(readXMLString(p, "required", strValue))
m_requireEncryption = booleanString(strValue);
q = p->children;
while(q)
{
if(xmlStrEqual(q->name, (const xmlChar*)"key"))
{
if(readXMLString(q, "type", strValue))
{
if(asLowerCaseString(strValue) == "rsa1024xtea")
{
if(readXMLString(q, "file", strValue))
{
m_key_RSA1024XTEA = new RSA();
if(!m_key_RSA1024XTEA->setKey(getFilePath(FILE_TYPE_XML, strValue)))
{
delete m_key_RSA1024XTEA;
m_key_RSA1024XTEA = NULL;
std::cout << "[Error - Admin::loadFromXml]: Could not load RSA key from file " << getFilePath(FILE_TYPE_XML, strValue) << std::endl;
}
}
else
std::cout << "[Error - Admin::loadFromXml]: Missing file for RSA1024XTEA key." << std::endl;
}
else
std::cout << "[Warning - Admin::loadFromXml]: " << strValue << " is not a valid key type." << std::endl;
}
}
q = q->next;
}
}
p = p->next;
}
xmlFreeDoc(doc);
return true;
}
bool Admin::addConnection()
{
if(m_currrentConnections >= m_maxConnections)
return false;
m_currrentConnections++;
return true;
}
void Admin::removeConnection()
{
if(m_currrentConnections > 0)
m_currrentConnections--;
}
uint16_t Admin::getProtocolPolicy()
{
uint16_t policy = 0;
if(requireLogin())
policy |= REQUIRE_LOGIN;
if(requireEncryption())
policy |= REQUIRE_ENCRYPTION;
return policy;
}
uint32_t Admin::getProtocolOptions()
{
uint32_t ret = 0;
if(requireEncryption() && m_key_RSA1024XTEA)
ret |= ENCRYPTION_RSA1024XTEA;
return ret;
}
RSA* Admin::getRSAKey(uint8_t type)
{
switch(type)
{
case ENCRYPTION_RSA1024XTEA:
return m_key_RSA1024XTEA;
default:
break;
}
return NULL;
}
Item* Admin::createMail(const std::string xmlData, std::string& name, uint32_t& depotId)
{
xmlDocPtr doc = xmlParseMemory(xmlData.c_str(), xmlData.length());
if(!doc)
return NULL;
xmlNodePtr root = xmlDocGetRootElement(doc);
if(xmlStrcmp(root->name,(const xmlChar*)"mail"))
return NULL;
int32_t intValue;
std::string strValue;
int32_t itemId = ITEM_PARCEL;
if(readXMLString(root, "to", strValue))
name = strValue;
if(readXMLString(root, "town", strValue))
{
Town* town = Towns::getInstance()->getTown(strValue);
if(!town)
return false;
depotId = town->getID();
}
else if(!IOLoginData::getInstance()->getDefaultTownByName(name, depotId)) //use the players default town
return false;
if(readXMLInteger(root, "id", intValue))
itemId = intValue;
Item* mailItem = Item::CreateItem(itemId);
mailItem->setParent(VirtualCylinder::virtualCylinder);
if(Container* mailContainer = mailItem->getContainer())
{
xmlNodePtr node = root->children;
while(node)
{
if(node->type != XML_ELEMENT_NODE)
{
node = node->next;
continue;
}
if(!Item::loadItem(node, mailContainer))
{
delete mailContainer;
return NULL;
}
node = node->next;
}
}
return mailItem;
}
bool Admin::allowIP(uint32_t ip)
{
if(!m_onlyLocalHost)
return !ConnectionManager::getInstance()->isDisabled(ip, 0xFE);
if(ip == 0x0100007F) //127.0.0.1
return true;
if(g_config.getBool(ConfigManager::ADMIN_LOGS_ENABLED))
LOG_MESSAGE(LOGTYPE_EVENT, "forbidden connection try", "ADMIN " + convertIPAddress(ip));
return false;
}
bool Admin::passwordMatch(const std::string& password)
{
//prevent empty password login
if(!m_password.length())
return false;
return password == m_password;
}
void ProtocolAdmin::addLogLine(LogType_t type, std::string message)
{
if(g_config.getBool(ConfigManager::ADMIN_LOGS_ENABLED))
LOG_MESSAGE(type, message, "ADMIN " + convertIPAddress(getIP()))
}
#endif