1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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.
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/>.
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"
35 using namespace NLMISC
;
36 using namespace NLNET
;
38 using namespace RSMGR
;
39 using namespace ENTITYLOC
;
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
66 TLoggedUsers _LoggedUsers
;
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
;
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>) ");
90 bool initModule(const TParsedCommandLine
&pcl
)
93 if (!CModuleBase::initModule(pcl
))
96 const TParsedCommandLine
*initRingDb
= pcl
.getParam("ring_db");
97 if (initRingDb
== NULL
)
99 nlwarning("LS : missing ring database connection information");
102 const TParsedCommandLine
*initNelDb
= pcl
.getParam("nel_db");
103 if (initNelDb
== NULL
)
105 nlwarning("LS : missing nel database connection information");
109 // connect to the databases
110 if (!_RingDb
.connect(*initRingDb
))
112 nlwarning("Failed to connect to database using %s", initRingDb
->toString().c_str());
115 if (!_NelDb
.connect(*initNelDb
))
117 nlwarning("Failed to connect to database using %s", initNelDb
->toString().c_str());
121 const TParsedCommandLine
*initWeb
= pcl
.getParam("web");
124 nlwarning("LS : missing web connection information");
128 const TParsedCommandLine
*webPort
= initWeb
->getParam("port");
131 nlwarning("LS : missing web.port connection information");
135 // open the web interface
137 NLMISC::fromString(webPort
->ParamValue
, port
);
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();
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);
175 ru
->setCurrentStatus(TCurrentStatus::cs_offline
);
179 // update the database
182 // clear the logged user entry
183 _LoggedUsers
.erase(first
);
185 // stop the check for this update
191 // bool onProcessModuleMessage(IModuleProxy *senderModuleProxy, const CMessage &message)
193 // if (CLoginServiceSkel::onDispatchMessage(senderModuleProxy, message))
196 // // manual dispatching
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(),
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");
246 ru
->setCurrentStatus(TCurrentStatus::cs_offline
);
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
);
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
);
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
)
292 virtual void onCharacterDisconnection(NLNET::IModuleProxy
*locatorHost
, uint32 charId
)
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(),
318 //1 check is user already online, if so, disconnect it
319 CRingUserPtr ru
= CRingUser::load(_RingDb
, userId
, __FILE__
, __LINE__
);
323 nldebug("on_login : invalid ring user %u", userId
);
324 loginResult(from
, userId
, "", 1, "Invalid user");
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");
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
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
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__);
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));
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)
391 result->getField(0, otherUserId);
393 CRingUserPtr otherRu = CRingUser::load(_RingDb, otherUserId, __FILE__, __LINE__);
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));
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
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(),
440 // load the ring user
441 CRingUserPtr ru
= CRingUser::load(_RingDb
, userId
, __FILE__
, __LINE__
);
445 nldebug("on_logout : invalid user %u", userId
);
446 logoutResult(from
, 1, "unknown user");
450 if (ru
->getCurrentStatus() == TCurrentStatus::cs_offline
)
452 // the user is offline, could not logout
453 logoutResult(from
, 2, "user already offline");
456 if (ru
->getCurrentStatus() == TCurrentStatus::cs_online
)
458 // the user is online (in game), ignore the disconnect but return ok
459 logoutResult(from
, 0, "");
463 // ok, the user is logged, we can put it offline
464 ru
->setCurrentStatus( TCurrentStatus::cs_offline
);
466 ru
->setCurrentDomainId(-1);
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
)
487 NLMISC::fromString(args
[0], _LoggedUserTimeout
);
491 log
.displayNL("LoggedUserTimeout = %u", _LoggedUserTimeout
);
497 // NLMISC_CLASS_COMMAND_DECL(closeWebInterface)
499 // if (args.size() != 0)
505 NLMISC_CLASS_COMMAND_DECL(openWebInterface
)
507 if (args
.size() != 1)
511 NLMISC::fromString(args
[0], port
);
512 log
.displayNL("Opening web interface on port %u", port
);
519 NLNET_REGISTER_MODULE_FACTORY(CLoginService
, "LoginService");