Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / net / login_server.cpp
blobff7d992c48d99dfbee67cacd531ff549159c4d7f
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
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/>.
17 #include "stdnet.h"
19 #include "nel/net/callback_client.h"
20 #include "nel/net/service.h"
22 #include "nel/net/login_cookie.h"
23 #include "nel/net/login_server.h"
25 #include "nel/net/udp_sock.h"
27 using namespace std;
28 using namespace NLMISC;
30 namespace NLNET {
32 struct CPendingUser
34 CPendingUser (const CLoginCookie &cookie, const string &un, const string &up, const string &ux, uint32 instanceId, uint32 charSlot)
35 : Cookie (cookie), UserName(un), UserPriv(up), UserExtended(ux), InstanceId(instanceId), CharSlot(charSlot)
37 Time = CTime::getSecondsSince1970();
40 CLoginCookie Cookie;
41 string UserName;
42 string UserPriv; // privilege for executing commands from the clients
43 string UserExtended; // extended data (for free use)
44 uint32 InstanceId; // the world instance in witch the user is awaited
45 uint32 CharSlot; // the expected character slot, any other will be denied
46 uint32 Time; // when the cookie is inserted in pending list
49 static list<CPendingUser> PendingUsers;
51 static CCallbackServer *Server = NULL;
52 static string ListenAddr;
54 static bool AcceptInvalidCookie = false;
56 static string DefaultUserPriv;
58 static TDisconnectClientCallback DisconnectClientCallback = NULL;
60 // true=tcp false=udp
61 static bool ModeTcp = 0;
63 // default value is 15 minutes
64 static uint TimeBeforeEraseCookie = 15*60;
66 /// contains the correspondance between userid and the sockid
67 map<uint32, TSockId> UserIdSockAssociations;
69 TNewClientCallback NewClientCallback = NULL;
71 TNewCookieCallback NewCookieCallback = NULL;
73 //////////////////////////////////////////////////////////////////////////////////////////////////
74 //////////////////////////////////////////////////////////////////////////////////////////////////
75 ///////////// CONNECTION TO THE WELCOME SERVICE //////////////////////////////////////////////////
76 //////////////////////////////////////////////////////////////////////////////////////////////////
77 //////////////////////////////////////////////////////////////////////////////////////////////////
79 void notifyWSRemovedPendingCookie(CLoginCookie& cookie)
81 CMessage msgout ("RPC"); // remove pending cookie
82 msgout.serial(cookie);
83 CUnifiedNetwork::getInstance()->send ("WS", msgout);
86 void CLoginServer::refreshPendingList ()
88 // delete too old cookie
90 list<CPendingUser>::iterator it = PendingUsers.begin();
91 uint32 Time = CTime::getSecondsSince1970();
92 while (it != PendingUsers.end ())
94 if ((*it).Time < Time - TimeBeforeEraseCookie)
96 nlinfo("LS: Removing cookie '%s' because too old", (*it).Cookie.toString().c_str());
97 notifyWSRemovedPendingCookie((*it).Cookie);
98 it = PendingUsers.erase (it);
100 else
102 it++;
108 void cbWSChooseShard (CMessage &msgin, const std::string &/* serviceName */, TServiceId /* sid */)
110 // the WS call me that a new client want to come in my shard
111 string reason, userName, userPriv, userExtended;
112 uint32 instanceId, charSlot;
113 CLoginCookie cookie;
115 // refreshPendingList ();
118 // S08: receive "CS" message from WS and send "SCS" message to WS
121 msgin.serial (cookie);
122 msgin.serial (userName, userPriv, userExtended);
123 msgin.serial (instanceId);
124 msgin.serial (charSlot);
126 vector<list<CPendingUser>::iterator> pendingToRemove;
127 list<CPendingUser>::iterator it;
128 for (it = PendingUsers.begin(); it != PendingUsers.end (); it++)
130 const CPendingUser &pu = *it;
131 if (pu.Cookie == cookie)
133 // the cookie already exists, erase it and return false
134 nlwarning ("LS: Cookie %s is already in the pending user list", cookie.toString().c_str());
135 // notifyWSRemovedPendingCookie((*it).Cookie);
136 PendingUsers.erase (it);
137 reason = "cookie already exists";
138 // iterator is invalid, set it to end
139 it = PendingUsers.end();
140 break;
142 else if (pu.Cookie.getUserId() == cookie.getUserId())
144 // we already have a cookie for this user, remove the old one
145 pendingToRemove.push_back(it);
148 // remove any useless cookie
149 while (!pendingToRemove.empty())
151 PendingUsers.erase(pendingToRemove.back());
152 pendingToRemove.pop_back();
155 if (it == PendingUsers.end ())
157 // add it to the awaiting client
158 nlinfo ("LS: New cookie %s (name '%s' priv '%s' extended '%s' instance %u slot %u) inserted in the pending user list (awaiting new client)", cookie.toString().c_str(), userName.c_str(), userPriv.c_str(), userExtended.c_str(), instanceId, charSlot);
159 PendingUsers.push_back (CPendingUser (cookie, userName, userPriv, userExtended, instanceId, charSlot));
160 reason.clear();
162 // callback if needed
163 if (NewCookieCallback != NULL)
165 NewCookieCallback(cookie);
169 CMessage msgout ("SCS");
170 msgout.serial (reason);
171 msgout.serial (cookie);
172 msgout.serial (ListenAddr);
173 uint32 nbPending = (uint32)PendingUsers.size();
174 msgout.serial (nbPending);
175 CUnifiedNetwork::getInstance()->send ("WS", msgout);
178 void cbWSDisconnectClient (CMessage &msgin, const std::string &serviceName, TServiceId /* sid */)
180 // the WS tells me that i have to disconnect a client
182 uint32 userid;
183 msgin.serial (userid);
185 if (ModeTcp)
187 map<uint32, TSockId>::iterator it = UserIdSockAssociations.find (userid);
188 if (it == UserIdSockAssociations.end ())
190 nlwarning ("LS: Can't disconnect the user %d, he is not found", userid);
192 else
194 nlinfo ("LS: Disconnect the user %d", userid);
195 Server->disconnect ((*it).second);
199 if (DisconnectClientCallback != NULL)
201 DisconnectClientCallback (userid, serviceName);
205 static TUnifiedCallbackItem WSCallbackArray[] =
207 { "CS", cbWSChooseShard },
208 { "DC", cbWSDisconnectClient },
211 //////////////////////////////////////////////////////////////////////////////////////////////////
212 //////////////////////////////////////////////////////////////////////////////////////////////////
213 ///////////// CONNECTION TO THE CLIENT ///////////////////////////////////////////////////////////
214 //////////////////////////////////////////////////////////////////////////////////////////////////
215 //////////////////////////////////////////////////////////////////////////////////////////////////
217 void cbShardValidation (CMessage &msgin, TSockId from, CCallbackNetBase &netbase)
220 // S13: receive "SV" message from the client
223 // the client send me a cookie
224 CLoginCookie cookie;
225 string reason;
226 msgin.serial (cookie);
228 string userName, userPriv, userExtended;
229 uint32 instanceId, charSlot;
230 // verify that the user was pending
231 reason = CLoginServer::isValidCookie (cookie, userName, userPriv, userExtended, instanceId, charSlot);
233 // if the cookie is not valid and we accept them, clear the error
234 if(AcceptInvalidCookie && !reason.empty())
236 reason.clear();
237 cookie.set (rand(), rand(), rand());
240 CMessage msgout2 ("SV");
241 msgout2.serial (reason);
242 netbase.send (msgout2, from);
244 if (!reason.empty())
246 nlwarning ("LS: User (%s) is not in the pending user list (cookie:%s)", netbase.hostAddress(from).asString().c_str(), cookie.toString().c_str());
247 // disconnect him
248 netbase.disconnect (from);
250 else
252 // add the user association
253 uint32 userid = cookie.getUserId();
255 if (ModeTcp)
256 UserIdSockAssociations.insert (make_pair(userid, from));
258 // identification OK, let's call the user callback
259 if (NewClientCallback != NULL)
260 NewClientCallback (from, cookie);
262 // ok, now, he can call all callback
263 Server->authorizeOnly (NULL, from);
267 void ClientConnection (TSockId from, void * /* arg */)
269 nldebug("LS: new client connection: %s", from->asString ().c_str ());
271 // the client could only call "SV" message
272 Server->authorizeOnly ("SV", from);
276 static const TCallbackItem ClientCallbackArray[] =
278 { "SV", cbShardValidation },
281 void CLoginServer::setListenAddress(const string &la)
283 // if the var is empty or not found, take it from the listenAddress()
284 if (la.empty() && ModeTcp && Server != NULL)
286 ListenAddr = Server->listenAddress ().asIPString();
288 else
290 ListenAddr = la;
293 // check that listen address is valid
294 if (ListenAddr.empty())
296 nlerror("FATAL : listen address in invalid, it should be either set via ListenAddress variable or with -D argument");
297 nlstop;
300 nlinfo("LS: Listen Address that will be sent to the client is now '%s'", ListenAddr.c_str());
303 uint32 CLoginServer::getNbPendingUsers()
305 return (uint32)PendingUsers.size();
308 void cfcbListenAddress (CConfigFile::CVar &var)
310 CLoginServer::setListenAddress (var.asString());
313 void cfcbDefaultUserPriv(CConfigFile::CVar &var)
315 // set the new ListenAddr
316 DefaultUserPriv = var.asString();
318 nlinfo("LS: The default user priv is '%s'", DefaultUserPriv.c_str());
321 void cfcbAcceptInvalidCookie(CConfigFile::CVar &var)
323 // set the new ListenAddr
324 AcceptInvalidCookie = var.asInt() == 1;
326 nlinfo("LS: This service %saccept invalid cookie", AcceptInvalidCookie?"":"doesn't ");
329 void cfcbTimeBeforeEraseCookie(CConfigFile::CVar &var)
331 // set the new ListenAddr
332 TimeBeforeEraseCookie = var.asInt();
334 nlinfo("LS: This service will remove cookie after %d seconds", TimeBeforeEraseCookie);
337 //////////////////////////////////////////////////////////////////////////////////////////////////
338 //////////////////////////////////////////////////////////////////////////////////////////////////
339 ///////////// CONNECTION TO THE WELCOME SERVICE //////////////////////////////////////////////////
340 //////////////////////////////////////////////////////////////////////////////////////////////////
341 //////////////////////////////////////////////////////////////////////////////////////////////////
343 // common init
344 void CLoginServer::init (const string &listenAddress)
346 // connect to the welcome service
347 connectToWS ();
349 try {
350 cfcbDefaultUserPriv(IService::getInstance()->ConfigFile.getVar("DefaultUserPriv"));
351 IService::getInstance()->ConfigFile.setCallback("DefaultUserPriv", cfcbDefaultUserPriv);
352 } catch(const Exception &) { }
354 try {
355 cfcbAcceptInvalidCookie (IService::getInstance()->ConfigFile.getVar("AcceptInvalidCookie"));
356 IService::getInstance()->ConfigFile.setCallback("AcceptInvalidCookie", cfcbAcceptInvalidCookie);
357 } catch(const Exception &) { }
359 try {
360 cfcbTimeBeforeEraseCookie (IService::getInstance()->ConfigFile.getVar("TimeBeforeEraseCookie"));
361 IService::getInstance()->ConfigFile.setCallback("TimeBeforeEraseCookie", cfcbTimeBeforeEraseCookie);
362 } catch(const Exception &) { }
364 // setup the listen address
366 string la;
368 if (IService::getInstance()->haveArg('D'))
370 // use the command line param if set
371 la = IService::getInstance()->getArg('D');
373 else if (IService::getInstance()->ConfigFile.exists ("ListenAddress"))
375 // use the config file param if set
376 la = IService::getInstance()->ConfigFile.getVar ("ListenAddress").asString();
378 else
380 la = listenAddress;
382 setListenAddress (la);
383 IService::getInstance()->ConfigFile.setCallback("ListenAddress", cfcbListenAddress);
386 // listen socket is TCP
387 void CLoginServer::init (CCallbackServer &server, TNewClientCallback ncl)
389 init (server.listenAddress ().asIPString());
391 // add callback to the server
392 server.addCallbackArray (ClientCallbackArray, sizeof (ClientCallbackArray) / sizeof (ClientCallbackArray[0]));
393 server.setConnectionCallback (ClientConnection, NULL);
395 NewClientCallback = ncl;
396 Server = &server;
398 ModeTcp = true;
401 // listen socket is UDP
402 void CLoginServer::init (CUdpSock &server, TDisconnectClientCallback dc)
404 init (server.localAddr ().asIPString());
406 DisconnectClientCallback = dc;
408 ModeTcp = false;
411 void CLoginServer::init (const std::string &listenAddr, TDisconnectClientCallback dc)
413 init (listenAddr);
415 DisconnectClientCallback = dc;
417 ModeTcp = false;
420 void CLoginServer::addNewCookieCallback(TNewCookieCallback newCookieCb)
422 NewCookieCallback = newCookieCb;
426 string CLoginServer::isValidCookie (const CLoginCookie &lc, string &userName, string &userPriv, string &userExtended, uint32 &instanceId, uint32 &charSlot)
428 userName.clear();
429 userPriv.clear();
431 if (!AcceptInvalidCookie && !lc.isValid())
432 return "The cookie is invalid";
434 // verify that the user was pending
435 list<CPendingUser>::iterator it;
436 for (it = PendingUsers.begin(); it != PendingUsers.end (); it++)
438 CPendingUser &pu = *it;
439 if (pu.Cookie == lc)
441 nlinfo ("LS: Cookie '%s' is valid and pending (user %s), send the client connection to the WS", lc.toString ().c_str (), pu.UserName.c_str());
443 // warn the WS that the client effectively connected
444 uint8 con = 1;
445 CMessage msgout ("CC");
446 uint32 userid = lc.getUserId();
447 msgout.serial (userid);
448 msgout.serial (con);
450 CUnifiedNetwork::getInstance()->send("WS", msgout);
452 userName = pu.UserName;
453 userPriv = pu.UserPriv;
454 userExtended = pu.UserExtended;
455 instanceId = pu.InstanceId;
456 charSlot = pu.CharSlot;
458 // ok, it was validate, remove it
459 PendingUsers.erase (it);
461 return "";
465 // we accept invalid cookie and it is one, fake
466 if (AcceptInvalidCookie)
468 userName = "InvalidUserName";
469 userPriv = DefaultUserPriv;
470 instanceId = 0xffffffff;
471 return "";
474 // problem
475 return "I didn't receive the cookie from WS";
478 void CLoginServer::connectToWS ()
480 CUnifiedNetwork::getInstance()->addCallbackArray(WSCallbackArray, sizeof(WSCallbackArray)/sizeof(WSCallbackArray[0]));
483 void CLoginServer::clientDisconnected (uint32 userId)
485 uint8 con = 0;
486 CMessage msgout ("CC");
487 msgout.serial (userId);
488 msgout.serial (con);
490 CUnifiedNetwork::getInstance()->send("WS", msgout);
492 // remove the user association
493 if (ModeTcp)
494 UserIdSockAssociations.erase (userId);
497 /// Call this method to retrieve the listen address
498 const std::string &CLoginServer::getListenAddress()
500 return ListenAddr;
503 bool CLoginServer::acceptsInvalidCookie()
505 return AcceptInvalidCookie;
510 // Commands
513 NLMISC_CATEGORISED_COMMAND(nel, lsUsers, "displays the list of all connected users", "")
515 nlunreferenced(rawCommandString);
516 nlunreferenced(quiet);
517 nlunreferenced(human);
519 if(args.size() != 0) return false;
521 if (ModeTcp)
523 log.displayNL ("Display the %d connected users :", UserIdSockAssociations.size());
524 for (map<uint32, TSockId>::iterator it = UserIdSockAssociations.begin(); it != UserIdSockAssociations.end (); it++)
526 log.displayNL ("> %u %s", (*it).first, (*it).second->asString().c_str());
528 log.displayNL ("End of the list");
530 else
532 log.displayNL ("No user list in udp mode");
535 return true;
538 NLMISC_CATEGORISED_COMMAND(nel, lsPending, "displays the list of all pending users", "")
540 nlunreferenced(rawCommandString);
541 nlunreferenced(quiet);
542 nlunreferenced(human);
544 if(args.size() != 0) return false;
546 log.displayNL ("Display the %d pending users :", PendingUsers.size());
547 for (list<CPendingUser>::iterator it = PendingUsers.begin(); it != PendingUsers.end (); it++)
549 log.displayNL ("> %s %s", (*it).Cookie.toString().c_str(), (*it).UserName.c_str());
551 log.displayNL ("End of the list");
553 return true;
557 NLMISC_CATEGORISED_DYNVARIABLE(nel, string, LSListenAddress, "the listen address sended to the client to connect on this front_end")
559 nlunreferenced(human);
561 if (get)
563 *pointer = ListenAddr;
565 else
567 if ((*pointer).find (":") == string::npos)
569 nlwarning ("LS: You must set the address + port (ie: \"itsalive.nevrax.org:38000\")");
570 return;
572 else if ((*pointer).empty())
574 ListenAddr = Server->listenAddress ().asIPString();
576 else
578 ListenAddr = *pointer;
580 nlinfo ("LS: Listen Address that will be send to client is '%s'", ListenAddr.c_str());
584 NLMISC_CATEGORISED_VARIABLE(nel, string, DefaultUserPriv, "Default User priv for people who don't use the login system");
586 } // NLNET