1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
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/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"
28 using namespace NLMISC
;
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();
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
;
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
);
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
;
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();
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
));
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
183 msgin
.serial (userid
);
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
);
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
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())
237 cookie
.set (rand(), rand(), rand());
240 CMessage
msgout2 ("SV");
241 msgout2
.serial (reason
);
242 netbase
.send (msgout2
, from
);
246 nlwarning ("LS: User (%s) is not in the pending user list (cookie:%s)", netbase
.hostAddress(from
).asString().c_str(), cookie
.toString().c_str());
248 netbase
.disconnect (from
);
252 // add the user association
253 uint32 userid
= cookie
.getUserId();
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();
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");
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 //////////////////////////////////////////////////////////////////////////////////////////////////
344 void CLoginServer::init (const string
&listenAddress
)
346 // connect to the welcome service
350 cfcbDefaultUserPriv(IService::getInstance()->ConfigFile
.getVar("DefaultUserPriv"));
351 IService::getInstance()->ConfigFile
.setCallback("DefaultUserPriv", cfcbDefaultUserPriv
);
352 } catch(const Exception
&) { }
355 cfcbAcceptInvalidCookie (IService::getInstance()->ConfigFile
.getVar("AcceptInvalidCookie"));
356 IService::getInstance()->ConfigFile
.setCallback("AcceptInvalidCookie", cfcbAcceptInvalidCookie
);
357 } catch(const Exception
&) { }
360 cfcbTimeBeforeEraseCookie (IService::getInstance()->ConfigFile
.getVar("TimeBeforeEraseCookie"));
361 IService::getInstance()->ConfigFile
.setCallback("TimeBeforeEraseCookie", cfcbTimeBeforeEraseCookie
);
362 } catch(const Exception
&) { }
364 // setup the listen address
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();
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
;
401 // listen socket is UDP
402 void CLoginServer::init (CUdpSock
&server
, TDisconnectClientCallback dc
)
404 init (server
.localAddr ().asIPString());
406 DisconnectClientCallback
= dc
;
411 void CLoginServer::init (const std::string
&listenAddr
, TDisconnectClientCallback dc
)
415 DisconnectClientCallback
= dc
;
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
)
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
;
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
445 CMessage
msgout ("CC");
446 uint32 userid
= lc
.getUserId();
447 msgout
.serial (userid
);
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
);
465 // we accept invalid cookie and it is one, fake
466 if (AcceptInvalidCookie
)
468 userName
= "InvalidUserName";
469 userPriv
= DefaultUserPriv
;
470 instanceId
= 0xffffffff;
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
)
486 CMessage
msgout ("CC");
487 msgout
.serial (userId
);
490 CUnifiedNetwork::getInstance()->send("WS", msgout
);
492 // remove the user association
494 UserIdSockAssociations
.erase (userId
);
497 /// Call this method to retrieve the listen address
498 const std::string
&CLoginServer::getListenAddress()
503 bool CLoginServer::acceptsInvalidCookie()
505 return AcceptInvalidCookie
;
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;
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");
532 log
.displayNL ("No user list in udp mode");
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");
557 NLMISC_CATEGORISED_DYNVARIABLE(nel
, string
, LSListenAddress
, "the listen address sended to the client to connect on this front_end")
559 nlunreferenced(human
);
563 *pointer
= ListenAddr
;
567 if ((*pointer
).find (":") == string::npos
)
569 nlwarning ("LS: You must set the address + port (ie: \"itsalive.nevrax.org:38000\")");
572 else if ((*pointer
).empty())
574 ListenAddr
= Server
->listenAddress ().asIPString();
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");