Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / server / src / shard_unifier_service / login_service.cpp
blob70d58147ca0a014c4bf4e7e13bb84ce3441c73d9
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "stdpch.h"
19 #include "nel/net/module.h"
20 #include "nel/net/module_builder_parts.h"
21 #include "nel/net/login_cookie.h"
23 #include "game_share/welcome_service_itf.h"
25 #include "game_share/utils.h"
26 #include "server_share/mysql_wrapper.h"
28 #include "server_share/login_service_itf.h"
29 #include "database_mapping.h"
30 #include "nel_database_mapping.h"
31 #include "entity_locator.h"
34 using namespace std;
35 using namespace NLMISC;
36 using namespace NLNET;
37 using namespace MSW;
38 using namespace RSMGR;
39 using namespace ENTITYLOC;
41 namespace LS
44 class CLoginService :
45 public CEmptyModuleCommBehav<CEmptyModuleServiceBehav<CEmptySocketBehav<CModuleBase> > >,
46 public WS::CLoginServiceSkel,
47 // public LS::CLoginServiceSkel,
48 public CLoginServiceWebItf,
49 public ICharacterEventCb
52 /// Mysql ring database connection
53 MSW::CConnection _RingDb;
54 /// Mysql nel database connection
55 MSW::CConnection _NelDb;
57 typedef std::set<NLNET::TModuleProxyPtr> TLSCLients;
58 /// Login service client (mostly frontend)
59 TLSCLients _LSClients;
61 typedef uint32 TUserId;
62 typedef map<TUserId, time_t> TLoggedUsers;
63 /// list of user that are only logged (not online) and checked for timeout
64 /// If the timeout occur, the users are put back to offline (and their cookie
65 /// erased)
66 TLoggedUsers _LoggedUsers;
68 enum
70 /// Default logged user timout (in second)
71 DEFAULT_LOGGED_USER_TIMEOUT = 15*60, // 15 mn (as in FE timeout for awaited users)
74 uint32 _LoggedUserTimeout;
76 public:
78 CLoginService()
80 _LoggedUserTimeout = DEFAULT_LOGGED_USER_TIMEOUT;
81 CLoginServiceSkel::init(this);
84 static const std::string &getInitStringHelp()
86 static std::string help(CModuleBase::getInitStringHelp()+"db(host=<hostname> [port=<port>] user=<user> password=<password> base=<baseName>) web(port=<listenPort>) ");
87 return help;
90 bool initModule(const TParsedCommandLine &pcl)
92 // recall base class
93 if (!CModuleBase::initModule(pcl))
94 return false;
96 const TParsedCommandLine *initRingDb = pcl.getParam("ring_db");
97 if (initRingDb == NULL)
99 nlwarning("LS : missing ring database connection information");
100 return false;
102 const TParsedCommandLine *initNelDb = pcl.getParam("nel_db");
103 if (initNelDb == NULL)
105 nlwarning("LS : missing nel database connection information");
106 return false;
109 // connect to the databases
110 if (!_RingDb.connect(*initRingDb))
112 nlwarning("Failed to connect to database using %s", initRingDb->toString().c_str());
113 return false;
115 if (!_NelDb.connect(*initNelDb))
117 nlwarning("Failed to connect to database using %s", initNelDb->toString().c_str());
118 return false;
121 const TParsedCommandLine *initWeb = pcl.getParam("web");
122 if (initWeb == NULL)
124 nlwarning("LS : missing web connection information");
125 return false;
128 const TParsedCommandLine *webPort = initWeb->getParam("port");
129 if (webPort == NULL)
131 nlwarning("LS : missing web.port connection information");
132 return false;
135 // open the web interface
136 uint16 port = 0;
137 NLMISC::fromString(webPort->ParamValue, port);
138 openItf(port);
140 return true;
143 void onModuleUpdate()
145 H_AUTO(CLoginService_onModuleUpdate);
147 // check that we are registered in the entity locator
148 if (getSpeaker() == NULL && IEntityLocator::getInstance() != NULL)
149 registerListener(IEntityLocator::getInstance());
153 CLoginServiceWebItf::update();
155 catch (...)
157 nlwarning( "Recovered from exception in CLoginServiceWebItf::update()" );
160 // check for logged user to put back to offline
161 uint32 now = NLMISC::CTime::getSecondsSince1970();
163 TLoggedUsers::iterator first(_LoggedUsers.begin()), last(_LoggedUsers.end());
164 for (; first != last; ++first)
166 if (first->second+_LoggedUserTimeout < now)
168 uint32 userId = first->first;
169 nldebug("LS : update : user %u is inactive since %u second, setting it to offline", userId, _LoggedUserTimeout);
170 // set this user offline !
171 CRingUserPtr ru = CRingUser::load(_RingDb, userId, __FILE__, __LINE__);
172 BOMB_IF(ru == NULL, "LS : updateModule : failed to load ring user "<<userId<<" that need to be set offline.", _LoggedUsers.erase(first); break);
174 // set the status
175 ru->setCurrentStatus(TCurrentStatus::cs_offline);
176 // clear the cookie
177 ru->setCookie("");
179 // update the database
180 ru->update(_RingDb);
182 // clear the logged user entry
183 _LoggedUsers.erase(first);
185 // stop the check for this update
186 break;
191 // bool onProcessModuleMessage(IModuleProxy *senderModuleProxy, const CMessage &message)
192 // {
193 // if (CLoginServiceSkel::onDispatchMessage(senderModuleProxy, message))
194 // return true;
196 // // manual dispatching
198 // return false;
199 // }
201 virtual void onModuleUp(IModuleProxy *proxy)
203 if (proxy->getModuleClassName() == "WelcomeService")
205 nlinfo("LS : adding WS '%s'", proxy->getModuleName().c_str());
206 // this is one of our clients, store it
207 _LSClients.insert(proxy);
210 virtual void onModuleDown(IModuleProxy *proxy)
212 TLSCLients::iterator it(_LSClients.find(proxy));
213 if (it != _LSClients.end())
215 nlinfo("LS : removing WS '%s'", proxy->getModuleName().c_str());
216 // we just lost a client
217 _LSClients.erase(it);
221 //////////////////////////////////////////////////
222 ///// login service from WS module interface callbacks
223 //////////////////////////////////////////////////
224 virtual void pendingUserLost(NLNET::IModuleProxy *sender, const NLNET::CLoginCookie &cookie)
226 nldebug("LS:pendingUserLost : WS '%s' report that user %u with cookie %s did not connect in the allowed time",
227 sender->getModuleName().c_str(),
228 cookie.getUserId(),
229 cookie.toString().c_str());
231 uint32 userId = cookie.getUserId();
233 CRingUserPtr ru = CRingUser::load(_RingDb, userId, __FILE__, __LINE__);
234 BOMB_IF(ru == NULL, "LS:pendingUserLost : failed to load user "<<userId<<" from the database", return);
236 // check user, it should be 'logged', and set it to offline.
237 BOMB_IF(ru->getCurrentStatus() != TCurrentStatus::cs_logged, "LS:pendingUserLost : the user "<<userId<<" should be logged, but he is "<<ru->getCurrentStatus().toString(), return);
239 if (ru->getCookie() != cookie.setToString())
241 // ignore this message because the user have relogged and obtained another cookie
242 nldebug("LS:pendingUserLost : message ignored because user has obtained another cookie");
243 return;
246 ru->setCurrentStatus(TCurrentStatus::cs_offline);
247 ru->setCookie("");
249 ru->update(_RingDb);
251 // remove it from the list of logged user
252 _LoggedUsers.erase(userId);
255 //////////////////////////////////////////////////
256 ///// entity locator callbacks
257 //////////////////////////////////////////////////
259 virtual void onUserConnection(NLNET::IModuleProxy *locatorHost, uint32 userId)
261 nldebug("LS: entity locator report user %u connection", userId);
262 // set the user in 'online state' and remove it from the 'user to check for time out' list
263 CRingUserPtr ru = CRingUser::load(_RingDb, userId, __FILE__, __LINE__);
264 BOMB_IF(ru == NULL, "LS : onUserConnection : failed to load ring user "<<userId, return);
266 ru->setCurrentStatus(TCurrentStatus::cs_online);
267 ru->update(_RingDb);
269 // remove it of the logged checked user list
270 _LoggedUsers.erase(userId);
273 virtual void onUserDisconnection(NLNET::IModuleProxy *locatorHost, uint32 userId)
275 nldebug("LS: entity locator report user %u disconnection", userId);
276 // set the user in ':logged state' and put it in the 'user to check for time out' list
277 CRingUserPtr ru = CRingUser::load(_RingDb, userId, __FILE__, __LINE__);
278 BOMB_IF(ru == NULL, "LS : onUserConnection : failed to load ring user "<<userId, return);
280 ru->setCurrentStatus(TCurrentStatus::cs_logged);
281 ru->update(_RingDb);
283 // erase existing record if any
284 _LoggedUsers.erase(userId);
286 // insert the user in the logged user list
287 _LoggedUsers.insert(make_pair(userId, NLMISC::CTime::getSecondsSince1970()));
290 virtual void onCharacterConnection(NLNET::IModuleProxy *locatorHost, uint32 charId, uint32 lastDisconnectionDate)
291 { /* nothing */ }
292 virtual void onCharacterDisconnection(NLNET::IModuleProxy *locatorHost, uint32 charId)
293 { /* nothing */ }
296 //////////////////////////////////////////////////
297 ///// Web interface callbacks
298 //////////////////////////////////////////////////
300 /// Connection callback : a new interface client connect
301 virtual void on_CLoginServiceWeb_Connection(NLNET::TSockId from)
305 virtual void on_CLoginServiceWeb_Disconnection(NLNET::TSockId from)
307 // nothing to do right now
310 /// A user has passed the auth, web sets the user online and asks for a cookie
311 virtual void on_login(NLNET::TSockId from, uint32 userId, const std::string &ipAddress, uint32 domainId)
313 nldebug("CLoginService : receive login request from %s with user %u, address %s",
314 from->getTcpSock()->remoteAddr().asString().c_str(),
315 userId,
316 ipAddress.c_str());
318 //1 check is user already online, if so, disconnect it
319 CRingUserPtr ru = CRingUser::load(_RingDb, userId, __FILE__, __LINE__);
320 if (ru == NULL)
322 // invalid user !
323 nldebug("on_login : invalid ring user %u", userId);
324 loginResult(from, userId, "", 1, "Invalid user");
325 return;
328 if (ru->getCurrentStatus() == TCurrentStatus::cs_online)
330 // this user seams online, we need to disconnect it from the shard
332 // send a disconnect message to all LS client
333 TLSCLients::iterator first(_LSClients.begin()), last(_LSClients.end());
334 for (; first != last; ++first)
336 WS::CWelcomeServiceProxy ws(*first);
338 ws.disconnectUser(this, userId);
341 // check in the entity locator that the player is really online
342 if (IEntityLocator::getInstance() && IEntityLocator::getInstance()->isUserOnline(userId))
344 nldebug("LS : on_login : user %u already connected, disconnecting him and reject login", userId);
345 loginResult(from, userId, "", 2, "User already online, please relog");
346 return;
349 // in fact, perhaps not really online, allow him to connect.
350 // a worst, the user is online but we have asked to the WS to
351 // disconnect him.
353 // In fact, most of the time, this case is when the SU is stopped
354 // with online user, so the database is not sync with the
355 // real user status.
358 /* commented by Ulukyn
359 // Now prevent from logging-in at the same time with a free GM's player account and a GM CS account
360 CNelUserPtr nelUser = CNelUser::load(_NelDb, userId, __FILE__, __LINE__);
361 BOMB_IF(nelUser == NULL, "on_login : invalid nel user %u" << userId, loginResult(from, userId, "", 5, "Invalid user"); return);
362 if (nelUser->getGMId() != 0)
364 uint32 otherUserId = nelUser->getGMId();
365 CRingUserPtr otherRu = CRingUser::load(_RingDb, otherUserId, __FILE__, __LINE__);
366 if (otherRu == NULL)
368 nlwarning("on_login : Can't find ring user %u from account %u with GMId", otherUserId, userId);
370 //else if (otherRu->getCurrentStatus() != TCurrentStatus::cs_offline) // cs_logged and cs_online
371 else if (otherRu->getCurrentStatus() == TCurrentStatus::cs_online) // less strict check, only avoid csr/player account logged on the same time
373 // DON'T check in the entity locator that the player is really online
374 //if (IEntityLocator::getInstance() && IEntityLocator::getInstance()->isUserOnline(otherUserId))
375 if (IEntityLocator::getInstance() && IEntityLocator::getInstance()->isUserOnline(otherUserId))
377 nldebug("LS : on_login : user %u already connected, rejecting login of %u with GMId", otherUserId, userId);
378 loginResult(from, userId, "", 3, toString("User %u (%u's GMId) already online", otherUserId, userId));
379 return;
383 CSString query;
384 query << "SELECT UId FROM user WHERE GMId = "<<userId<<"";
385 BOMB_IF(!_NelDb.query(query), "on_login : Failed to request in database", loginResult(from, userId, "", 6, "Failed request"); return);
386 CUniquePtr<CStoreResult> result(_NelDb.storeResult());
387 for (uint32 i=0; i!=result->getNumRows(); ++i)
389 result->fetchRow();
390 uint32 otherUserId;
391 result->getField(0, otherUserId);
393 CRingUserPtr otherRu = CRingUser::load(_RingDb, otherUserId, __FILE__, __LINE__);
394 if (otherRu == NULL)
396 nlwarning("on_login : Can't find ring user %u which GMID is account %u", otherUserId, userId);
398 //else if (otherRu->getCurrentStatus() != TCurrentStatus::cs_offline) // cs_logged and cs_online
399 else if (otherRu->getCurrentStatus() == TCurrentStatus::cs_online) // less strict check, only avoid csr/player account logged on the same time
401 // DON'T check in the entity locator that the player is really online
402 //if (IEntityLocator::getInstance() && IEntityLocator::getInstance()->isUserOnline(otherUserId))
403 if (IEntityLocator::getInstance() && IEntityLocator::getInstance()->isUserOnline(otherUserId))
405 nldebug("LS : on_login : user %u already connected, rejecting login of %u which is the GMId of it", otherUserId, userId);
406 loginResult(from, userId, "", 4, toString("GM user %u (having GMId=%u) already online", otherUserId, userId));
407 return;
413 NLNET::CInetAddress addr(ipAddress);
414 //2 generate a cookie and set the player status and cookie in database
415 NLNET::CLoginCookie cookie(addr.internalIPAddress(), userId);
416 ru->setCookie(cookie.setToString());
417 ru->setCurrentStatus(TCurrentStatus::cs_logged);
418 ru->setCurrentActivity(TCurrentActivity::ca_none);
419 ru->setCurrentDomainId(domainId);
421 // save the user record
422 ru->update(_RingDb);
424 // erase existing record if any
425 _LoggedUsers.erase(userId);
426 // insert the user in the user to wait table
427 _LoggedUsers.insert(make_pair(userId, NLMISC::CTime::getSecondsSince1970()));
430 //3 call the return method to the web
431 loginResult(from, userId, ru->getCookie(), 0, "");
434 virtual void on_logout(NLNET::TSockId from, uint32 userId)
436 nldebug("CLoginService : receive logout request from %s with user %u",
437 from->getTcpSock()->remoteAddr().asString().c_str(),
438 userId);
440 // load the ring user
441 CRingUserPtr ru = CRingUser::load(_RingDb, userId, __FILE__, __LINE__);
442 if (ru == NULL)
444 // invalid user !
445 nldebug("on_logout : invalid user %u", userId);
446 logoutResult(from, 1, "unknown user");
447 return;
450 if (ru->getCurrentStatus() == TCurrentStatus::cs_offline)
452 // the user is offline, could not logout
453 logoutResult(from, 2, "user already offline");
454 return;
456 if (ru->getCurrentStatus() == TCurrentStatus::cs_online)
458 // the user is online (in game), ignore the disconnect but return ok
459 logoutResult(from, 0, "");
460 return;
463 // ok, the user is logged, we can put it offline
464 ru->setCurrentStatus( TCurrentStatus::cs_offline);
465 ru->setCookie("");
466 ru->setCurrentDomainId(-1);
467 ru->update(_RingDb);
469 logoutResult(from, 0, "");
474 NLMISC_COMMAND_HANDLER_TABLE_EXTEND_BEGIN(CLoginService, CModuleBase)
475 NLMISC_COMMAND_HANDLER_ADD(CLoginService, openWebInterface, "Open the web interface", "<listenPort>");
476 // NLMISC_COMMAND_HANDLER_ADD(CLoginService, closeWebInterface, "Close the web interface", "no param");
477 NLMISC_COMMAND_HANDLER_ADD(CLoginService, LoggedUserTimeout, "get or set the logged user timeout in second", "[<newValue in second>]");
478 NLMISC_COMMAND_HANDLER_TABLE_END
480 NLMISC_CLASS_COMMAND_DECL(LoggedUserTimeout)
482 if (args.size() > 1)
483 return false;
485 if (!args.empty())
487 NLMISC::fromString(args[0], _LoggedUserTimeout);
490 // output the value
491 log.displayNL("LoggedUserTimeout = %u", _LoggedUserTimeout);
493 return true;
497 // NLMISC_CLASS_COMMAND_DECL(closeWebInterface)
498 // {
499 // if (args.size() != 0)
500 // return false;
503 // }
505 NLMISC_CLASS_COMMAND_DECL(openWebInterface)
507 if (args.size() != 1)
508 return false;
510 uint16 port;
511 NLMISC::fromString(args[0], port);
512 log.displayNL("Opening web interface on port %u", port);
513 openItf(port);
515 return true;
519 NLNET_REGISTER_MODULE_FACTORY(CLoginService, "LoginService");
521 } // namespace LS