khanat-opennel-code/code/nelns/login_service/connection_client.cpp
2010-05-06 02:08:41 +02:00

453 lines
11 KiB
C++

// NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// 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 <http://www.gnu.org/licenses/>.
//
// Includes
//
#include <nel/misc/types_nl.h>
#include <math.h>
#include <stdio.h>
#include <ctype.h>
#include <map>
#include <vector>
#include <nel/misc/log.h>
#include <nel/misc/debug.h>
#include <nel/misc/displayer.h>
#include <nel/misc/config_file.h>
#include <nel/net/service.h>
#include <nel/net/login_cookie.h>
#include "login_service.h"
#include "mysql_helper.h"
//
// Namespaces
//
using namespace std;
using namespace NLMISC;
using namespace NLNET;
//
// Variables
//
static CCallbackServer *ClientsServer = 0;
//
// Functions
//
void sendToClient(CMessage &msgout, TSockId sockId)
{
nlassert(ClientsServer != 0);
ClientsServer->send(msgout, sockId);
}
void string_escape(string &str)
{
string::size_type pos = 0;
while((pos=str.find('\'')) != string::npos)
{
str.replace(pos, 1, " ");
}
}
static void cbClientVerifyLoginPassword(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
//
// S03: check the validity of the client login/password and send "VLP" message to client
//
// reason is empty if everything goes right or contains the reason of the failure
string reason;
sint32 uid = -1;
ucstring login;
string cpassword, application;
msgin.serial (login);
msgin.serial (cpassword);
msgin.serial (application);
breakable
{
CMysqlResult result;
MYSQL_ROW row;
sint32 nbrow;
//const CInetAddress &ia = netbase.hostAddress (from);
retry:
reason = sqlQuery("select * from user where Login='"+login.toUtf8()+"'", nbrow, row, result);
if(!reason.empty()) break;
if(nbrow == 0)
{
if(IService::getInstance ()->ConfigFile.getVar("AcceptUnknownUsers").asInt () == 1)
{
// we accept new users, add it
string query = "insert into user (Login, Password) values ('"+login.toUtf8()+"', '"+cpassword+"')";
reason = sqlQuery(query, nbrow, row, result);
if (!reason.empty()) break;
nlinfo("The user %s was inserted in the database for the application '%s'!", login.toUtf8().c_str(), application.c_str());
goto retry;
}
else
{
reason = toString("Login '%s' doesn't exist", login.toUtf8().c_str());
break;
}
}
if(nbrow != 1)
{
reason = toString("Too much login '%s' exists", login.toUtf8().c_str());
break;
}
// now the user is on the database
uid = atoi(row[0]);
if(cpassword != row[2])
{
reason = toString("Bad password");
break;
}
if(row[4] != string("Offline"))
{
// 2 players are trying to play with the same id, disconnect all
//reason = sqlQuery(string("update user set state='Offline', ShardId=-1 where UId=")+uid);
//if(!reason.empty()) break;
// send a message to the already connected player to disconnect
CMessage msgout("DC");
msgout.serial(uid);
CUnifiedNetwork::getInstance()->send("WS", msgout);
reason = "You are already connected.";
break;
}
CLoginCookie c;
c.set((uint32)(uintptr_t)from, rand(), uid);
reason = sqlQuery("update user set state='Authorized', Cookie='"+c.setToString()+"' where UId="+uid);
if(!reason.empty()) break;
reason = sqlQuery("select * from shard where Online>0 and ClientApplication='"+application+"'", nbrow, row, result);
if(!reason.empty()) break;
// Send success message
CMessage msgout ("VLP");
msgout.serial(reason);
msgout.serial(nbrow);
// send address and name of all online shards
while(row != 0)
{
// serial the name of the shard
ucstring shardname;
shardname.fromUtf8(row[3]);
uint8 nbplayers = atoi(row[2]);
uint32 sid = atoi(row[0]);
msgout.serial (shardname, nbplayers, sid);
row = mysql_fetch_row(result);
}
netbase.send (msgout, from);
netbase.authorizeOnly ("CS", from);
return;
}
// Manage error
CMessage msgout("VLP");
if(reason.empty()) reason = "Unknown error";
msgout.serial(reason);
netbase.send(msgout, from);
// FIX: On GNU/Linux, when we disconnect now, sometime the other side doesn't receive the message sent just before.
// So it is the other side to disconnect
// netbase.disconnect (from);
}
static void cbClientChooseShard(CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
{
//
// S06: receive "CS" message from client
//
string reason;
breakable
{
CMysqlResult result;
MYSQL_ROW row;
sint32 nbrow;
reason = sqlQuery("select UId, Cookie, Privilege, ExtendedPrivilege from user where State='Authorized'", nbrow, row, result);
if(!reason.empty()) break;
if(nbrow == 0)
{
reason = "You are not authorized to select a shard";
break;
}
bool ok = false;
while(row != 0)
{
CLoginCookie lc;
lc.setFromString(row[1]);
if(lc.getUserAddr() == (uint32)(uintptr_t)from)
{
ok = true;
break;
}
row = mysql_fetch_row(result);
}
if(!ok)
{
reason = "You are not authorized to select a shard";
break;
}
string uid = row[0];
string cookie = row[1];
string priv = row[2];
string expriv = row[3];
// it is ok, so we find the wanted shard
sint32 shardid;
msgin.serial(shardid);
reason = sqlQuery("select * from shard where ShardId="+toString(shardid), nbrow, row, result);
if(!reason.empty()) break;
if(nbrow == 0)
{
reason = "This shard is not available";
break;
}
sint32 s = findShard (shardid);
if (s == -1)
{
reason = "Cannot find the shard internal id";
break;
}
TServiceId sid = Shards[s].SId;
reason = sqlQuery("update user set State='Waiting', ShardId="+toString(shardid)+" where UId="+uid);
if(!reason.empty()) break;
reason = sqlQuery("select Login from user where UId="+uid, nbrow, row, result);
if(!reason.empty()) break;
if(nbrow == 0)
{
reason = "Cannot retrieve the username";
break;
}
ucstring name;
name.fromUtf8(row[0]);
CLoginCookie lc;
lc.setFromString(cookie);
CMessage msgout("CS");
msgout.serial(lc, name, priv, expriv);
CUnifiedNetwork::getInstance()->send(sid, msgout);
return;
}
// Manage error
CMessage msgout("SCS");
msgout.serial(reason);
netbase.send(msgout, from);
// FIX: On GNU/Linux, when we disconnect now, sometime the other side doesn't receive the message sent just before.
// So it's the other side to disconnect
// netbase.disconnect (from);
}
static void cbClientConnection (TSockId from, void *arg)
{
CCallbackNetBase *cnb = ClientsServer;
const CInetAddress &ia = cnb->hostAddress (from);
nldebug("new client connection: %s", ia.asString ().c_str ());
Output->displayNL ("CCC: Connection from %s", ia.asString ().c_str ());
cnb->authorizeOnly ("VLP", from);
}
static void cbClientDisconnection (TSockId from, void *arg)
{
CCallbackNetBase *cnb = ClientsServer;
const CInetAddress &ia = cnb->hostAddress (from);
nldebug("new client disconnection: %s", ia.asString ().c_str ());
string reason;
CMysqlResult result;
MYSQL_ROW row;
sint32 nbrow;
reason = sqlQuery("select UId, State, Cookie from user where State!='Offline'", nbrow, row, result);
if(!reason.empty()) return;
if(nbrow == 0)
{
return;
}
while(row != 0)
{
CLoginCookie lc;
string str = row[2];
if(!str.empty())
{
lc.setFromString(str);
if(lc.getUserAddr() == (uint32)(uintptr_t)from)
{
// got it, if he is not in waiting state, it s not normal, remove all
if(row[1] == string("Authorized"))
sqlQuery("update user set state='Offline', ShardId=-1, Cookie='' where UId="+string(row[0]));
return;
}
}
row = mysql_fetch_row(result);
}
}
const TCallbackItem ClientCallbackArray[] =
{
{ "VLP", cbClientVerifyLoginPassword },
{ "CS", cbClientChooseShard },
};
static void cbWSShardChooseShard (CMessage &msgin, const std::string &serviceName, TServiceId sid)
{
//
// S10: receive "SCS" message from WS
//
CMessage msgout("SCS");
CLoginCookie cookie;
string reason;
breakable
{
msgin.serial (reason);
msgin.serial (cookie);
if(!reason.empty())
{
nldebug("SCS from WS failed: %s", reason.c_str());
sqlQuery("update user set state='Offline', ShardId=-1, Cookie='' where Cookie='" + cookie.setToString() + "'");
break;
}
CMysqlResult result;
MYSQL_ROW row;
sint32 nbrow;
reason = sqlQuery("select UId, Cookie, Privilege, ExtendedPrivilege from user where Cookie='"+cookie.setToString()+"'", nbrow, row, result);
if(!reason.empty()) break;
if(nbrow != 1)
{
reason = "More than one row was found";
nldebug("SCS from WS failed with duplicate cookies, sending disconnect messages.");
// disconnect them all
while(row != 0)
{
CMessage msgout("DC");
uint32 uid = atoui(row[0]);
msgout.serial(uid);
CUnifiedNetwork::getInstance()->send("WS", msgout);
row = mysql_fetch_row(result);
}
break;
}
msgout.serial(reason);
string str = cookie.setToString ();
msgout.serial (str);
string addr;
msgin.serial (addr);
msgout.serial (addr);
ClientsServer->send (msgout, (TSockId)cookie.getUserAddr ());
return;
}
msgout.serial(reason);
ClientsServer->send (msgout, (TSockId)cookie.getUserAddr ());
}
static const TUnifiedCallbackItem WSCallbackArray[] =
{
{ "SCS", cbWSShardChooseShard },
};
//
//
//
void connectionClientInit ()
{
nlassert(ClientsServer == 0);
ClientsServer = new CCallbackServer();
nlassert(ClientsServer != 0);
uint16 port = (uint16) IService::getInstance ()->ConfigFile.getVar ("ClientsPort").asInt();
ClientsServer->init (port);
ClientsServer->addCallbackArray(ClientCallbackArray, sizeof(ClientCallbackArray)/sizeof(ClientCallbackArray[0]));
ClientsServer->setConnectionCallback(cbClientConnection, 0);
ClientsServer->setDisconnectionCallback(cbClientDisconnection, 0);
// catch the messages from Welcome Service to know if the user can connect or not
CUnifiedNetwork::getInstance ()->addCallbackArray (WSCallbackArray, sizeof(WSCallbackArray)/sizeof(WSCallbackArray[0]));
}
void connectionClientUpdate ()
{
nlassert(ClientsServer != 0);
try
{
ClientsServer->update();
}
catch (Exception &e)
{
nlwarning ("Error during update: '%s'", e.what ());
}
}
void connectionClientRelease ()
{
nlassert(ClientsServer != 0);
delete ClientsServer;
ClientsServer = 0;
}