// NeLNS - 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 . // // Includes // #include #include #include #include #include #include #include #include #include #include #include #include #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; }