2010-05-06 00:08:41 +00:00
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// 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 <http://www.gnu.org/licenses/>.
# include "stdnet.h"
# include "nel/net/callback_client.h"
# include "nel/net/service.h"
# include "nel/net/login_cookie.h"
# include "nel/net/login_server.h"
# include "nel/net/udp_sock.h"
using namespace std ;
using namespace NLMISC ;
namespace NLNET {
struct CPendingUser
{
CPendingUser ( const CLoginCookie & cookie , const string & un , const string & up , const string & ux , uint32 instanceId , uint32 charSlot )
: Cookie ( cookie ) , UserName ( un ) , UserPriv ( up ) , UserExtended ( ux ) , InstanceId ( instanceId ) , CharSlot ( charSlot )
{
Time = CTime : : getSecondsSince1970 ( ) ;
}
CLoginCookie Cookie ;
string UserName ;
string UserPriv ; // privilege for executing commands from the clients
string UserExtended ; // extended data (for free use)
uint32 InstanceId ; // the world instance in witch the user is awaited
uint32 CharSlot ; // the expected character slot, any other will be denied
uint32 Time ; // when the cookie is inserted in pending list
} ;
static list < CPendingUser > PendingUsers ;
static CCallbackServer * Server = NULL ;
static string ListenAddr ;
static bool AcceptInvalidCookie = false ;
static string DefaultUserPriv = " " ;
static TDisconnectClientCallback DisconnectClientCallback = NULL ;
// true=tcp false=udp
static bool ModeTcp = 0 ;
// default value is 15 minutes
static uint TimeBeforeEraseCookie = 15 * 60 ;
/// contains the correspondance between userid and the sockid
map < uint32 , TSockId > UserIdSockAssociations ;
TNewClientCallback NewClientCallback = NULL ;
TNewCookieCallback NewCookieCallback = NULL ;
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
///////////// CONNECTION TO THE WELCOME SERVICE //////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
void notifyWSRemovedPendingCookie ( CLoginCookie & cookie )
{
CMessage msgout ( " RPC " ) ; // remove pending cookie
msgout . serial ( cookie ) ;
CUnifiedNetwork : : getInstance ( ) - > send ( " WS " , msgout ) ;
}
void CLoginServer : : refreshPendingList ( )
{
// delete too old cookie
list < CPendingUser > : : iterator it = PendingUsers . begin ( ) ;
uint32 Time = CTime : : getSecondsSince1970 ( ) ;
while ( it ! = PendingUsers . end ( ) )
{
if ( ( * it ) . Time < Time - TimeBeforeEraseCookie )
{
nlinfo ( " LS: Removing cookie '%s' because too old " , ( * it ) . Cookie . toString ( ) . c_str ( ) ) ;
notifyWSRemovedPendingCookie ( ( * it ) . Cookie ) ;
it = PendingUsers . erase ( it ) ;
}
else
{
it + + ;
}
}
}
void cbWSChooseShard ( CMessage & msgin , const std : : string & /* serviceName */ , TServiceId /* sid */ )
{
// the WS call me that a new client want to come in my shard
string reason , userName , userPriv , userExtended ;
uint32 instanceId , charSlot ;
CLoginCookie cookie ;
// refreshPendingList ();
//
// S08: receive "CS" message from WS and send "SCS" message to WS
//
msgin . serial ( cookie ) ;
msgin . serial ( userName , userPriv , userExtended ) ;
msgin . serial ( instanceId ) ;
msgin . serial ( charSlot ) ;
vector < list < CPendingUser > : : iterator > pendingToRemove ;
list < CPendingUser > : : iterator it ;
for ( it = PendingUsers . begin ( ) ; it ! = PendingUsers . end ( ) ; it + + )
{
const CPendingUser & pu = * it ;
if ( pu . Cookie = = cookie )
{
// the cookie already exists, erase it and return false
nlwarning ( " LS: Cookie %s is already in the pending user list " , cookie . toString ( ) . c_str ( ) ) ;
// notifyWSRemovedPendingCookie((*it).Cookie);
PendingUsers . erase ( it ) ;
reason = " cookie already exists " ;
// iterator is invalid, set it to end
it = PendingUsers . end ( ) ;
break ;
}
else if ( pu . Cookie . getUserId ( ) = = cookie . getUserId ( ) )
{
// we already have a cookie for this user, remove the old one
pendingToRemove . push_back ( it ) ;
}
}
// remove any useless cookie
while ( ! pendingToRemove . empty ( ) )
{
PendingUsers . erase ( pendingToRemove . back ( ) ) ;
pendingToRemove . pop_back ( ) ;
}
if ( it = = PendingUsers . end ( ) )
{
// add it to the awaiting client
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 ) ;
PendingUsers . push_back ( CPendingUser ( cookie , userName , userPriv , userExtended , instanceId , charSlot ) ) ;
reason = " " ;
// callback if needed
if ( NewCookieCallback ! = NULL )
{
NewCookieCallback ( cookie ) ;
}
}
CMessage msgout ( " SCS " ) ;
msgout . serial ( reason ) ;
msgout . serial ( cookie ) ;
msgout . serial ( ListenAddr ) ;
2010-05-13 20:23:34 +00:00
uint32 nbPending = ( uint32 ) PendingUsers . size ( ) ;
2010-05-06 00:08:41 +00:00
msgout . serial ( nbPending ) ;
CUnifiedNetwork : : getInstance ( ) - > send ( " WS " , msgout ) ;
}
void cbWSDisconnectClient ( CMessage & msgin , const std : : string & serviceName , TServiceId /* sid */ )
{
// the WS tells me that i have to disconnect a client
uint32 userid ;
msgin . serial ( userid ) ;
if ( ModeTcp )
{
map < uint32 , TSockId > : : iterator it = UserIdSockAssociations . find ( userid ) ;
if ( it = = UserIdSockAssociations . end ( ) )
{
nlwarning ( " LS: Can't disconnect the user %d, he is not found " , userid ) ;
}
else
{
nlinfo ( " LS: Disconnect the user %d " , userid ) ;
Server - > disconnect ( ( * it ) . second ) ;
}
}
if ( DisconnectClientCallback ! = NULL )
{
DisconnectClientCallback ( userid , serviceName ) ;
}
}
static TUnifiedCallbackItem WSCallbackArray [ ] =
{
{ " CS " , cbWSChooseShard } ,
{ " DC " , cbWSDisconnectClient } ,
} ;
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
///////////// CONNECTION TO THE CLIENT ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
void cbShardValidation ( CMessage & msgin , TSockId from , CCallbackNetBase & netbase )
{
//
// S13: receive "SV" message from the client
//
// the client send me a cookie
CLoginCookie cookie ;
string reason ;
msgin . serial ( cookie ) ;
string userName , userPriv , userExtended ;
uint32 instanceId , charSlot ;
// verify that the user was pending
reason = CLoginServer : : isValidCookie ( cookie , userName , userPriv , userExtended , instanceId , charSlot ) ;
// if the cookie is not valid and we accept them, clear the error
if ( AcceptInvalidCookie & & ! reason . empty ( ) )
{
reason = " " ;
cookie . set ( rand ( ) , rand ( ) , rand ( ) ) ;
}
CMessage msgout2 ( " SV " ) ;
msgout2 . serial ( reason ) ;
netbase . send ( msgout2 , from ) ;
if ( ! reason . empty ( ) )
{
nlwarning ( " LS: User (%s) is not in the pending user list (cookie:%s) " , netbase . hostAddress ( from ) . asString ( ) . c_str ( ) , cookie . toString ( ) . c_str ( ) ) ;
// disconnect him
netbase . disconnect ( from ) ;
}
else
{
// add the user association
uint32 userid = cookie . getUserId ( ) ;
if ( ModeTcp )
UserIdSockAssociations . insert ( make_pair ( userid , from ) ) ;
// identification OK, let's call the user callback
if ( NewClientCallback ! = NULL )
NewClientCallback ( from , cookie ) ;
// ok, now, he can call all callback
Server - > authorizeOnly ( NULL , from ) ;
}
}
void ClientConnection ( TSockId from , void * /* arg */ )
{
nldebug ( " LS: new client connection: %s " , from - > asString ( ) . c_str ( ) ) ;
// the client could only call "SV" message
Server - > authorizeOnly ( " SV " , from ) ;
}
static const TCallbackItem ClientCallbackArray [ ] =
{
{ " SV " , cbShardValidation } ,
} ;
void CLoginServer : : setListenAddress ( const string & la )
{
// if the var is empty or not found, take it from the listenAddress()
if ( la . empty ( ) & & ModeTcp & & Server ! = NULL )
{
ListenAddr = Server - > listenAddress ( ) . asIPString ( ) ;
}
else
{
ListenAddr = la ;
}
// check that listen address is valid
if ( ListenAddr . empty ( ) )
{
nlerror ( " FATAL : listen address in invalid, it should be either set via ListenAddress variable or with -D argument " ) ;
nlstop ;
}
nlinfo ( " LS: Listen Address that will be sent to the client is now '%s' " , ListenAddr . c_str ( ) ) ;
}
uint32 CLoginServer : : getNbPendingUsers ( )
{
2010-05-13 20:23:34 +00:00
return ( uint32 ) PendingUsers . size ( ) ;
2010-05-06 00:08:41 +00:00
}
void cfcbListenAddress ( CConfigFile : : CVar & var )
{
CLoginServer : : setListenAddress ( var . asString ( ) ) ;
}
void cfcbDefaultUserPriv ( CConfigFile : : CVar & var )
{
// set the new ListenAddr
DefaultUserPriv = var . asString ( ) ;
nlinfo ( " LS: The default user priv is '%s' " , DefaultUserPriv . c_str ( ) ) ;
}
void cfcbAcceptInvalidCookie ( CConfigFile : : CVar & var )
{
// set the new ListenAddr
AcceptInvalidCookie = var . asInt ( ) = = 1 ;
nlinfo ( " LS: This service %saccept invalid cookie " , AcceptInvalidCookie ? " " : " doesn't " ) ;
}
void cfcbTimeBeforeEraseCookie ( CConfigFile : : CVar & var )
{
// set the new ListenAddr
TimeBeforeEraseCookie = var . asInt ( ) ;
nlinfo ( " LS: This service will remove cookie after %d seconds " , TimeBeforeEraseCookie ) ;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
///////////// CONNECTION TO THE WELCOME SERVICE //////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
// common init
void CLoginServer : : init ( const string & listenAddress )
{
// connect to the welcome service
connectToWS ( ) ;
try {
cfcbDefaultUserPriv ( IService : : getInstance ( ) - > ConfigFile . getVar ( " DefaultUserPriv " ) ) ;
IService : : getInstance ( ) - > ConfigFile . setCallback ( " DefaultUserPriv " , cfcbDefaultUserPriv ) ;
} catch ( Exception & ) { }
try {
cfcbAcceptInvalidCookie ( IService : : getInstance ( ) - > ConfigFile . getVar ( " AcceptInvalidCookie " ) ) ;
IService : : getInstance ( ) - > ConfigFile . setCallback ( " AcceptInvalidCookie " , cfcbAcceptInvalidCookie ) ;
} catch ( Exception & ) { }
try {
cfcbTimeBeforeEraseCookie ( IService : : getInstance ( ) - > ConfigFile . getVar ( " TimeBeforeEraseCookie " ) ) ;
IService : : getInstance ( ) - > ConfigFile . setCallback ( " TimeBeforeEraseCookie " , cfcbTimeBeforeEraseCookie ) ;
} catch ( Exception & ) { }
// setup the listen address
string la ;
if ( IService : : getInstance ( ) - > haveArg ( ' D ' ) )
{
// use the command line param if set
la = IService : : getInstance ( ) - > getArg ( ' D ' ) ;
}
else if ( IService : : getInstance ( ) - > ConfigFile . exists ( " ListenAddress " ) )
{
// use the config file param if set
la = IService : : getInstance ( ) - > ConfigFile . getVar ( " ListenAddress " ) . asString ( ) ;
}
else
{
la = listenAddress ;
}
setListenAddress ( la ) ;
IService : : getInstance ( ) - > ConfigFile . setCallback ( " ListenAddress " , cfcbListenAddress ) ;
}
// listen socket is TCP
void CLoginServer : : init ( CCallbackServer & server , TNewClientCallback ncl )
{
init ( server . listenAddress ( ) . asIPString ( ) ) ;
// add callback to the server
server . addCallbackArray ( ClientCallbackArray , sizeof ( ClientCallbackArray ) / sizeof ( ClientCallbackArray [ 0 ] ) ) ;
server . setConnectionCallback ( ClientConnection , NULL ) ;
NewClientCallback = ncl ;
Server = & server ;
ModeTcp = true ;
}
// listen socket is UDP
void CLoginServer : : init ( CUdpSock & server , TDisconnectClientCallback dc )
{
init ( server . localAddr ( ) . asIPString ( ) ) ;
DisconnectClientCallback = dc ;
ModeTcp = false ;
}
void CLoginServer : : init ( const std : : string & listenAddr , TDisconnectClientCallback dc )
{
init ( listenAddr ) ;
DisconnectClientCallback = dc ;
ModeTcp = false ;
}
void CLoginServer : : addNewCookieCallback ( TNewCookieCallback newCookieCb )
{
NewCookieCallback = newCookieCb ;
}
string CLoginServer : : isValidCookie ( const CLoginCookie & lc , string & userName , string & userPriv , string & userExtended , uint32 & instanceId , uint32 & charSlot )
{
userName = userPriv = " " ;
if ( ! AcceptInvalidCookie & & ! lc . isValid ( ) )
return " The cookie is invalid " ;
// verify that the user was pending
list < CPendingUser > : : iterator it ;
for ( it = PendingUsers . begin ( ) ; it ! = PendingUsers . end ( ) ; it + + )
{
CPendingUser & pu = * it ;
if ( pu . Cookie = = lc )
{
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 ( ) ) ;
// warn the WS that the client effectively connected
uint8 con = 1 ;
CMessage msgout ( " CC " ) ;
uint32 userid = lc . getUserId ( ) ;
msgout . serial ( userid ) ;
msgout . serial ( con ) ;
CUnifiedNetwork : : getInstance ( ) - > send ( " WS " , msgout ) ;
userName = pu . UserName ;
userPriv = pu . UserPriv ;
userExtended = pu . UserExtended ;
instanceId = pu . InstanceId ;
charSlot = pu . CharSlot ;
// ok, it was validate, remove it
PendingUsers . erase ( it ) ;
return " " ;
}
}
// we accept invalid cookie and it is one, fake
if ( AcceptInvalidCookie )
{
userName = " InvalidUserName " ;
userPriv = DefaultUserPriv ;
instanceId = 0xffffffff ;
return " " ;
}
// problem
return " I didn't receive the cookie from WS " ;
}
void CLoginServer : : connectToWS ( )
{
CUnifiedNetwork : : getInstance ( ) - > addCallbackArray ( WSCallbackArray , sizeof ( WSCallbackArray ) / sizeof ( WSCallbackArray [ 0 ] ) ) ;
}
void CLoginServer : : clientDisconnected ( uint32 userId )
{
uint8 con = 0 ;
CMessage msgout ( " CC " ) ;
msgout . serial ( userId ) ;
msgout . serial ( con ) ;
CUnifiedNetwork : : getInstance ( ) - > send ( " WS " , msgout ) ;
// remove the user association
if ( ModeTcp )
UserIdSockAssociations . erase ( userId ) ;
}
/// Call this method to retrieve the listen address
const std : : string & CLoginServer : : getListenAddress ( )
{
return ListenAddr ;
}
bool CLoginServer : : acceptsInvalidCookie ( )
{
return AcceptInvalidCookie ;
}
//
// Commands
//
NLMISC_CATEGORISED_COMMAND ( nel , lsUsers , " displays the list of all connected users " , " " )
{
nlunreferenced ( rawCommandString ) ;
nlunreferenced ( quiet ) ;
nlunreferenced ( human ) ;
if ( args . size ( ) ! = 0 ) return false ;
if ( ModeTcp )
{
log . displayNL ( " Display the %d connected users : " , UserIdSockAssociations . size ( ) ) ;
for ( map < uint32 , TSockId > : : iterator it = UserIdSockAssociations . begin ( ) ; it ! = UserIdSockAssociations . end ( ) ; it + + )
{
log . displayNL ( " > %u %s " , ( * it ) . first , ( * it ) . second - > asString ( ) . c_str ( ) ) ;
}
log . displayNL ( " End of the list " ) ;
}
else
{
log . displayNL ( " No user list in udp mode " ) ;
}
return true ;
}
NLMISC_CATEGORISED_COMMAND ( nel , lsPending , " displays the list of all pending users " , " " )
{
nlunreferenced ( rawCommandString ) ;
nlunreferenced ( quiet ) ;
nlunreferenced ( human ) ;
if ( args . size ( ) ! = 0 ) return false ;
log . displayNL ( " Display the %d pending users : " , PendingUsers . size ( ) ) ;
for ( list < CPendingUser > : : iterator it = PendingUsers . begin ( ) ; it ! = PendingUsers . end ( ) ; it + + )
{
log . displayNL ( " > %s %s " , ( * it ) . Cookie . toString ( ) . c_str ( ) , ( * it ) . UserName . c_str ( ) ) ;
}
log . displayNL ( " End of the list " ) ;
return true ;
}
NLMISC_CATEGORISED_DYNVARIABLE ( nel , string , LSListenAddress , " the listen address sended to the client to connect on this front_end " )
{
nlunreferenced ( human ) ;
if ( get )
{
* pointer = ListenAddr ;
}
else
{
if ( ( * pointer ) . find ( " : " ) = = string : : npos )
{
nlwarning ( " LS: You must set the address + port (ie: \" itsalive.nevrax.org:38000 \" ) " ) ;
return ;
}
else if ( ( * pointer ) . empty ( ) )
{
ListenAddr = Server - > listenAddress ( ) . asIPString ( ) ;
}
else
{
ListenAddr = * pointer ;
}
nlinfo ( " LS: Listen Address that will be send to client is '%s' " , ListenAddr . c_str ( ) ) ;
}
}
NLMISC_CATEGORISED_VARIABLE ( nel , string , DefaultUserPriv , " Default User priv for people who don't use the login system " ) ;
} // NLNET