// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdnet.h"
#include "nel/misc/system_info.h"
#include "nel/net/callback_client.h"
#include "nel/net/login_cookie.h"
#include "nel/net/login_client.h"
#include "nel/net/udp_sock.h"
using namespace std;
using namespace NLMISC;
namespace NLNET {
CLoginClient::TShardList CLoginClient::ShardList;
CCallbackClient *CLoginClient::_LSCallbackClient;
//
// CALLBACK FROM THE FS (Front-end Service)
//
// Callback for answer of the request shard
static bool ShardValidate;
static string ShardValidateReason;
static void cbShardValidate (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
//
// S14: receive "SV" message from FES
//
msgin.serial (ShardValidateReason);
ShardValidate = true;
}
static TCallbackItem FESCallbackArray[] =
{
{ "SV", cbShardValidate },
};
//
// CALLBACK FROM THE LS (Login Service)
//
// Callback for answer of the login password.
static bool VerifyLoginPassword;
static string VerifyLoginPasswordReason;
static void cbVerifyLoginPassword (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
//
// S04: receive the "VLP" message from LS
//
msgin.serial (VerifyLoginPasswordReason);
if(VerifyLoginPasswordReason.empty())
{
uint32 nbshard;
msgin.serial (nbshard);
CLoginClient::ShardList.clear ();
VerifyLoginPasswordReason.clear();
// get the shard list
for (uint i = 0; i < nbshard; i++)
{
CLoginClient::CShardEntry se;
msgin.serial (se.Name, se.NbPlayers, se.Id);
CLoginClient::ShardList.push_back (se);
}
}
VerifyLoginPassword = true;
}
// Callback for answer of the request shard
static bool ShardChooseShard;
static string ShardChooseShardReason;
static string ShardChooseShardAddr;
static string ShardChooseShardCookie;
static void cbShardChooseShard (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
//
// S11: receive "SCS" message from LS
//
msgin.serial (ShardChooseShardReason);
if (ShardChooseShardReason.empty())
{
msgin.serial (ShardChooseShardCookie);
msgin.serial (ShardChooseShardAddr);
}
ShardChooseShard = true;
}
static TCallbackItem LSCallbackArray[] =
{
{ "VLP", cbVerifyLoginPassword },
{ "SCS", cbShardChooseShard },
};
string CLoginClient::authenticate(const string &loginServiceAddr, const ucstring &login, const string &cpassword, const string &application)
{
string result = authenticateBegin(loginServiceAddr, login, cpassword, application);
if (!result.empty()) return result;
while (CLoginClient::authenticateUpdate(result)) nlSleep(10);
return result;
}
string CLoginClient::authenticateBegin(const string &loginServiceAddr, const ucstring &login, const string &cpassword, const string &application)
{
VerifyLoginPasswordReason = "";
VerifyLoginPassword = false;
// S01: connect to the LS
try
{
if(_LSCallbackClient == 0)
{
_LSCallbackClient = new CCallbackClient();
_LSCallbackClient->addCallbackArray(LSCallbackArray, sizeof(LSCallbackArray) / sizeof(LSCallbackArray[0]));
}
string addr = loginServiceAddr;
if(addr.find(":") == string::npos)
addr += ":49997";
if(_LSCallbackClient->connected())
_LSCallbackClient->disconnect();
_LSCallbackClient->connect (CInetAddress(addr));
}
catch (ESocket &e)
{
delete _LSCallbackClient;
_LSCallbackClient = 0;
nlwarning("Connection refused to LS (addr:%s): %s", loginServiceAddr.c_str(), e.what());
return toString("Connection refused to LS (addr:%s): %s", loginServiceAddr.c_str(), e.what());
}
// S02: create and send the "VLP" message
CMessage msgout("VLP");
msgout.serial(const_cast(login));
msgout.serial(const_cast(cpassword));
msgout.serial(const_cast(application));
_LSCallbackClient->send(msgout);
return "";
}
// returns true if it needs to be called again
// error not empty if something went wrong
bool CLoginClient::authenticateUpdate(string &error)
{
if (!_LSCallbackClient)
{
error = "CLoginClient::authenticateBegin() must be called first";
nlwarning("CLoginClient::authenticateUpdate(): %s", error.c_str());
return false;
}
if (!_LSCallbackClient->connected())
{
error = "Disconnected from LS";
nlwarning("CLoginClient::authenticateUpdate(): %s", error.c_str());
delete _LSCallbackClient;
_LSCallbackClient = 0;
return false;
}
_LSCallbackClient->update();
if (VerifyLoginPassword)
{
error = VerifyLoginPasswordReason;
if (!error.empty())
{
nlwarning("CLoginClient::authenticateUpdate(): %s", error.c_str());
_LSCallbackClient->disconnect ();
delete _LSCallbackClient;
_LSCallbackClient = 0;
}
return false;
}
return true; // no news, try again
}
string CLoginClient::connectToShard(CLoginCookie &lc, const std::string &addr, CCallbackClient &cnx)
{
nlassert (!cnx.connected());
try
{
//
// S12: connect to the FES and send "SV" message to the FES
//
cnx.connect (CInetAddress(addr));
cnx.addCallbackArray (FESCallbackArray, sizeof(FESCallbackArray)/sizeof(FESCallbackArray[0]));
// send the cookie
CMessage msgout2 ("SV");
msgout2.serial (lc);
cnx.send (msgout2);
// wait the answer of the connection
ShardValidate = false;
while (cnx.connected() && !ShardValidate)
{
cnx.update ();
nlSleep(10);
}
// have we received the answer?
if (!ShardValidate) return "FES disconnect me";
}
catch (ESocket &e)
{
return string("FES refused the connection (") + e.what () + ")";
}
return ShardValidateReason;
}
string CLoginClient::connectToShard (const std::string &addr, CUdpSock &cnx)
{
nlassert (!cnx.connected());
try
{
//
// S12: connect to the FES. Note: In UDP mode, it's the user that have to send the cookie to the front end
//
// If a personal firewall such as ZoneAlarm is installed and permission not granted yet,
// the connect blocks until the user makes a choice.
// If the user denies the connection, the exception ESocket is thrown.
// Other firewalls such as Kerio make the send() fail instead.
//
cnx.connect (CInetAddress(addr));
}
catch (ESocket &e)
{
return string("FES refused the connection (") + e.what () + ")";
}
return ShardValidateReason;
}
string CLoginClient::connectToShard (const std::string &addr, CUdpSimSock &cnx)
{
nlassert (!cnx.connected());
try
{
//
// S12: connect to the FES. Note: In UDP mode, it's the user that have to send the cookie to the front end
//
// See firewall comment in connectToShard(string,CUdpSock)
//
cnx.connect (CInetAddress(addr));
}
catch (ESocket &e)
{
return string("FES refused the connection (") + e.what () + ")";
}
return ShardValidateReason;
}
string CLoginClient::confirmConnection(sint32 shardId)
{
nlassert(_LSCallbackClient != 0 && _LSCallbackClient->connected());
//
// S05: create and send the "CS" message with the shardid choice to the LS
//
if (!ShardList.size())
{
_LSCallbackClient->disconnect();
return "No shard available";
}
CLoginClient::CShardEntry *s = getShard(shardId);
if (!s)
{
_LSCallbackClient->disconnect();
return "Invalid shard selected";
}
// send CS
CMessage msgout ("CS");
msgout.serial (s->Id);
_LSCallbackClient->send (msgout);
// wait the answer
ShardChooseShard = false;
while (_LSCallbackClient->connected() && !ShardChooseShard)
{
_LSCallbackClient->update ();
nlSleep(10);
}
// have we received the answer?
if (!ShardChooseShard)
{
delete _LSCallbackClient;
_LSCallbackClient = 0;
return "CLoginClientMtp::confirmConnection(): LS disconnects me";
}
else
{
_LSCallbackClient->disconnect ();
delete _LSCallbackClient;
_LSCallbackClient = 0;
}
if (!ShardChooseShardReason.empty())
{
return ShardChooseShardReason;
}
// ok, we can try to connect to the good front end
nlinfo("addr: '%s' cookie: %s", ShardChooseShardAddr.c_str(), ShardChooseShardCookie.c_str());
return "";
}
string CLoginClient::wantToConnectToShard(sint32 shardId, string &ip, string &cookie)
{
string res = confirmConnection(shardId);
if (!res.empty()) return res;
ip = ShardChooseShardAddr;
cookie = ShardChooseShardCookie;
return "";
}
string CLoginClient::selectShardBegin(sint32 shardId)
{
nlassert(_LSCallbackClient != 0 && _LSCallbackClient->connected());
ShardChooseShardReason = "";
ShardChooseShard = false;
if (!ShardList.size())
{
_LSCallbackClient->disconnect();
delete _LSCallbackClient;
_LSCallbackClient = 0;
return "No shard available";
}
CLoginClient::CShardEntry *s = getShard(shardId);
if (!s)
{
_LSCallbackClient->disconnect();
delete _LSCallbackClient;
_LSCallbackClient = 0;
return "Invalid shard selected";
}
// S05: create and send the "CS" message with the shardid choice to the LS
CMessage msgout ("CS");
msgout.serial (s->Id);
_LSCallbackClient->send (msgout);
return "";
}
bool CLoginClient::selectShardUpdate(string &error, string &ip, string &cookie)
{
if (!_LSCallbackClient)
{
error = "CLoginClient::selectShardBegin() must be called first";
nlwarning("CLoginClient::selectShardUpdate(): %s", error.c_str());
return false;
}
if (!_LSCallbackClient->connected())
{
error = "Disconnected from LS";
nlwarning("CLoginClient::selectShardUpdate(): %s", error.c_str());
delete _LSCallbackClient;
_LSCallbackClient = 0;
return false;
}
_LSCallbackClient->update();
if (ShardChooseShard)
{
error = ShardChooseShardReason;
ip = ShardChooseShardAddr;
cookie = ShardChooseShardCookie;
if (!error.empty()) nlwarning("CLoginClient::selectShardUpdate(): %s", error.c_str());
else nlinfo("addr: '%s' cookie: %s", ShardChooseShardAddr.c_str(), ShardChooseShardCookie.c_str());
_LSCallbackClient->disconnect ();
delete _LSCallbackClient;
_LSCallbackClient = 0;
return false;
}
return true;
}
CLoginClient::CShardEntry *CLoginClient::getShard (sint32 shardId)
{
for(TShardList::iterator it=ShardList.begin();it!=ShardList.end();it++)
{
if((*it).Id == shardId)
return &(*it);
}
return 0;
}
} // NLNET