2019-07-17 19:37:18 +02:00
# include <iostream>
# include <algorithm>
# include <bitset>
# include <memory>
# include <Definitions.h>
# include <misc/sassert.h>
# include <misc/memtracker.h>
# include <log/LogUtils.h>
# include <ThreadPool/Timer.h>
# include "../TSServer.h"
# include "voice/VoiceClient.h"
# include "../server/VoiceServer.h"
# include "../server/file/FileServer.h"
# include "../InstanceHandler.h"
# include "ConnectedClient.h"
using namespace std ;
using namespace std : : chrono ;
using namespace ts ;
using namespace ts : : server ;
using namespace ts : : token ;
extern ts : : server : : InstanceHandler * serverInstance ;
ConnectedClient : : ConnectedClient ( sql : : SqlManager * db , const std : : shared_ptr < TSServer > & server ) : DataClient ( db , server ) {
memtrack : : allocated < ConnectedClient > ( this ) ;
memset ( & this - > remote_address , 0 , sizeof ( this - > remote_address ) ) ;
connectionStatistics = make_shared < stats : : ConnectionStatistics > ( server ? server - > getServerStatistics ( ) : nullptr , false ) ;
this - > connectionStatistics - > measure_bandwidths ( false ) ; /* done by the client and we trust this */
channels = make_shared < ClientChannelView > ( this ) ;
}
ConnectedClient : : ~ ConnectedClient ( ) {
memtrack : : freed < ConnectedClient > ( this ) ;
}
std : : shared_ptr < ConnectionInfoData > ConnectedClient : : requestConnectionInfo ( const std : : shared_ptr < ConnectedClient > & receiver ) {
auto & info = this - > connection_info ;
lock_guard info_lock ( info . lock ) ;
if ( info . data ) {
if ( chrono : : system_clock : : now ( ) - info . data_age < chrono : : seconds ( 1 ) )
return info . data ;
if ( chrono : : system_clock : : now ( ) - info . data_age > chrono : : seconds ( 5 ) ) //Data timeout
info . data = nullptr ;
}
info . receiver . erase ( std : : remove_if ( info . receiver . begin ( ) , info . receiver . end ( ) , [ & ] ( const weak_ptr < ConnectedClient > & weak ) {
auto locked = weak . lock ( ) ;
return ! locked | | locked = = receiver ;
} ) , info . receiver . end ( ) ) ;
info . receiver . push_back ( receiver ) ;
if ( chrono : : system_clock : : now ( ) - info . last_requested > = chrono : : seconds ( 1 ) ) {
info . last_requested = chrono : : system_clock : : now ( ) ;
Command cmd ( " notifyconnectioninforequest " ) ;
string invoker ;
for ( const auto & weak_request : info . receiver ) {
auto request = weak_request . lock ( ) ;
if ( ! request ) continue ;
invoker + = ( invoker . empty ( ) ? " " : " , " ) + to_string ( request - > getClientId ( ) ) ;
}
cmd [ " invokerids " ] = invoker ;
this - > sendCommand ( cmd ) ;
}
return info . data ;
}
//Attention the client should be only read only locked!
void ConnectedClient : : updateChannelClientProperties ( bool lock_channel_tree , bool notify_self ) {
/* this->server may be null! */
shared_ptr < TSServer > server_ref = this - > server ;
auto permissions = this - > permissionValues ( permission : : PERMTEST_ORDERED , {
permission : : i_client_talk_power ,
permission : : b_client_is_priority_speaker ,
permission : : b_client_ignore_antiflood ,
permission : : i_channel_view_power ,
permission : : b_channel_ignore_view_power
} , this - > currentChannel ) ;
permission : : PermissionValue
permission_talk_power = permNotGranted ,
permission_priority_speaker = permNotGranted ,
permission_ignore_antiflood = permNotGranted ,
permission_channel_view_power = permNotGranted ,
permission_channel_ignore_view_power = permNotGranted ;
for ( const auto & perm : permissions ) {
if ( perm . first = = permission : : i_client_talk_power )
permission_talk_power = perm . second ;
else if ( perm . first = = permission : : b_client_is_priority_speaker )
permission_priority_speaker = perm . second ;
else if ( perm . first = = permission : : b_client_ignore_antiflood )
permission_ignore_antiflood = perm . second ;
else if ( perm . first = = permission : : i_channel_view_power )
permission_channel_view_power = perm . second ;
else if ( perm . first = = permission : : b_channel_ignore_view_power )
permission_channel_ignore_view_power = perm . second ;
else sassert ( false ) ;
}
if ( permission_talk_power < - 2 ) permission_talk_power = - 2 ;
else if ( permission_talk_power = = - 2 ) permission_talk_power = 0 ;
deque < property : : ClientProperties > notifyList ;
debugMessage ( this - > getServerId ( ) , " {} Got a channel talk power of {} Talk power set is {} " , CLIENT_STR_LOG_PREFIX , permission_talk_power , this - > properties ( ) [ property : : CLIENT_TALK_POWER ] . as < uint64_t > ( ) ) ;
2019-07-19 22:55:03 +02:00
if ( permission_talk_power ! = this - > properties ( ) [ property : : CLIENT_TALK_POWER ] . as < uint64_t > ( ) ) { //We do not have to update tp if there's no channel
2019-07-17 19:37:18 +02:00
this - > properties ( ) [ property : : CLIENT_TALK_POWER ] = permission_talk_power ;
notifyList . emplace_back ( property : : CLIENT_TALK_POWER ) ;
auto update = this - > properties ( ) [ property : : CLIENT_IS_TALKER ] . as < bool > ( ) | | this - > properties ( ) [ property : : CLIENT_TALK_REQUEST ] . as < int64_t > ( ) > 0 ;
2019-07-19 22:55:03 +02:00
if ( update & & this - > currentChannel ) {
2019-07-17 19:37:18 +02:00
if ( this - > currentChannel - > talk_power_granted ( { permission_talk_power , permission_talk_power ! = permNotGranted } ) ) {
this - > properties ( ) [ property : : CLIENT_IS_TALKER ] = 0 ;
this - > properties ( ) [ property : : CLIENT_TALK_REQUEST ] = 0 ;
this - > properties ( ) [ property : : CLIENT_TALK_REQUEST_MSG ] = " " ;
notifyList . emplace_back ( property : : CLIENT_IS_TALKER ) ;
notifyList . emplace_back ( property : : CLIENT_TALK_REQUEST ) ;
notifyList . emplace_back ( property : : CLIENT_TALK_REQUEST_MSG ) ;
}
}
}
2019-07-17 22:17:38 +02:00
IconId iconId = 0 ;
2019-07-18 10:45:50 +02:00
auto local_permissions = this - > clientPermissions ;
if ( local_permissions ) {
2019-07-17 22:17:38 +02:00
permission : : v2 : : PermissionFlaggedValue value { 0 , false } ;
2019-07-18 10:45:50 +02:00
auto permission_flags = local_permissions - > permission_flags ( permission : : i_icon_id ) ;
2019-07-17 22:17:38 +02:00
if ( permission_flags . channel_specific & & this - > currentChannel ) {
2019-07-18 10:45:50 +02:00
auto val = local_permissions - > channel_permission ( permission : : i_icon_id , this - > currentChannel - > channelId ( ) ) ;
2019-07-17 22:17:38 +02:00
value = { val . values . value , val . flags . value_set } ;
}
if ( ! value . has_value )
2019-07-18 10:45:50 +02:00
value = local_permissions - > permission_value_flagged ( permission : : i_icon_id ) ;
2019-07-17 22:17:38 +02:00
if ( value . has_value )
iconId = value . value ;
}
2019-07-17 19:37:18 +02:00
logTrace ( this - > getServerId ( ) , " [CLIENT] Updating client icon from " + to_string ( this - > properties ( ) [ property : : CLIENT_ICON_ID ] . as < IconId > ( ) ) + " to " + to_string ( iconId ) ) ;
if ( this - > properties ( ) [ property : : CLIENT_ICON_ID ] . as < IconId > ( ) ! = iconId ) {
if ( server_ref & & iconId ! = 0 ) {
auto dir = serverInstance - > getFileServer ( ) - > iconDirectory ( server_ref ) ;
if ( ! serverInstance - > getFileServer ( ) - > iconExists ( server_ref , iconId ) ) {
logMessage ( this - > getServerId ( ) , " [FILE] Missing client icon ( " + to_string ( iconId ) + " ). " ) ;
iconId = 0 ;
}
}
if ( this - > properties ( ) [ property : : CLIENT_ICON_ID ] . as < IconId > ( ) ! = iconId ) {
this - > properties ( ) [ property : : CLIENT_ICON_ID ] = ( IconId ) iconId ;
notifyList . emplace_back ( property : : CLIENT_ICON_ID ) ;
}
}
auto pSpeaker = permission_priority_speaker > 0 ;
if ( properties ( ) [ property : : CLIENT_IS_PRIORITY_SPEAKER ] . as < bool > ( ) ! = pSpeaker ) {
properties ( ) [ property : : CLIENT_IS_PRIORITY_SPEAKER ] = pSpeaker ;
notifyList . emplace_back ( property : : CLIENT_IS_PRIORITY_SPEAKER ) ;
}
block_flood = permission_ignore_antiflood < = 0 | | permission_ignore_antiflood = = permNotGranted ;
if ( server_ref )
server_ref - > notifyClientPropertyUpdates ( _this . lock ( ) , notifyList , notify_self ) ;
this - > updateTalkRights ( permission_talk_power ) ;
if ( ( this - > channels_view_power ! = permission_channel_view_power | | this - > channels_ignore_view ! = permission_channel_ignore_view_power ) & & notify_self & & this - > currentChannel & & server_ref ) {
this - > channels_view_power = permission_channel_view_power ;
this - > channels_ignore_view = permission_channel_ignore_view_power ;
shared_lock server_channel_lock ( server_ref - > channel_tree_lock , defer_lock ) ;
unique_lock client_channel_lock ( this - > channel_lock , defer_lock ) ;
if ( lock_channel_tree ) {
/* first read lock server channel tree */
server_channel_lock . lock ( ) ;
client_channel_lock . lock ( ) ;
}
deque < ChannelId > deleted ;
for ( const auto & update_entry : this - > channels - > update_channel_path ( server_ref - > channelTree - > tree_head ( ) , server_ref - > channelTree - > find_linked_entry ( this - > currentChannel - > channelId ( ) ) ) ) {
if ( update_entry . first )
this - > notifyChannelShow ( update_entry . second - > channel ( ) , update_entry . second - > previous_channel ) ;
else deleted . push_back ( update_entry . second - > channelId ( ) ) ;
}
if ( ! deleted . empty ( ) )
this - > notifyChannelHide ( deleted , false ) ; /* we've locked the tree before */
}
}
void ConnectedClient : : updateTalkRights ( permission : : PermissionValue talk_power ) {
bool flag = false ;
flag | = this - > properties ( ) [ property : : CLIENT_IS_TALKER ] . as < bool > ( ) ;
if ( ! flag & & this - > currentChannel ) {
flag = this - > currentChannel - > talk_power_granted ( { talk_power , talk_power ! = permNotGranted } ) ;
}
this - > allowedToTalk = flag ;
}
void ConnectedClient : : resetIdleTime ( ) {
this - > idleTimestamp = chrono : : system_clock : : now ( ) ;
}
void ConnectedClient : : increaseFloodPoints ( uint16_t num ) {
this - > floodPoints + = num ;
}
bool ConnectedClient : : shouldFloodBlock ( ) {
if ( ! this - > server ) return false ;
if ( ! this - > block_flood ) return false ;
return this - > floodPoints > this - > server - > properties ( ) [ property : : VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_COMMAND_BLOCK ] . as < uint16_t > ( ) ;
}
std : : deque < std : : shared_ptr < BasicChannel > > ConnectedClient : : subscribeChannel ( const std : : deque < std : : shared_ptr < BasicChannel > > & targets , bool lock_channel , bool enforce ) {
deque < std : : shared_ptr < BasicChannel > > subscribed_channels ;
auto ref_server = this - > server ;
if ( ! ref_server )
return { } ;
auto general_granted = enforce | | this - > permission_granted ( this - > permissionValue ( permission : : b_channel_ignore_subscribe_power , nullptr ) , 1 , true ) ;
{
shared_lock server_channel_lock ( ref_server - > channel_tree_lock , defer_lock ) ;
unique_lock client_channel_lock ( this - > channel_lock , defer_lock ) ;
if ( lock_channel ) {
server_channel_lock . lock ( ) ;
client_channel_lock . lock ( ) ;
}
auto cache = make_shared < CalculateCache > ( ) ;
for ( const auto & channel : targets ) {
auto local_channel = this - > channels - > find_channel ( channel ) ;
if ( ! local_channel ) continue ; //Not visible
if ( local_channel - > subscribed ) continue ; //Already subscribed
if ( ! general_granted & & channel ! = this - > currentChannel ) {
auto granted_permission = this - > calculate_permission_value ( permission : : i_channel_subscribe_power , channel - > channelId ( ) ) ;
if ( ! channel - > permission_granted ( permission : : i_channel_needed_subscribe_power , granted_permission , false ) & &
! this - > permission_granted ( this - > permissionValue ( permission : : b_channel_ignore_subscribe_power , channel ) , 1 , true ) ) {
continue ;
}
}
local_channel - > subscribed = true ;
subscribed_channels . push_back ( channel ) ;
}
deque < shared_ptr < ConnectedClient > > visible_clients ;
for ( const auto & target_channel : subscribed_channels ) {
/* ref_server->getClientsByChannel only acquires channel client lock */
for ( const auto & client : ref_server - > getClientsByChannel ( target_channel ) ) {
visible_clients . push_back ( client ) ;
}
}
this - > notifyClientEnterView ( visible_clients , ViewReasonSystem ) ;
if ( ! subscribed_channels . empty ( ) )
this - > notifyChannelSubscribed ( subscribed_channels ) ;
}
return subscribed_channels ;
}
std : : deque < std : : shared_ptr < BasicChannel > > ConnectedClient : : unsubscribeChannel ( const std : : deque < std : : shared_ptr < BasicChannel > > & targets , bool lock_channel ) {
auto ref_server = this - > server ;
if ( ! ref_server )
return { } ;
deque < std : : shared_ptr < BasicChannel > > unsubscribed_channels ;
{
shared_lock server_channel_lock ( ref_server - > channel_tree_lock , defer_lock ) ;
unique_lock client_channel_lock ( this - > channel_lock , defer_lock ) ;
if ( lock_channel ) {
server_channel_lock . lock ( ) ;
client_channel_lock . lock ( ) ;
}
for ( const auto & channel : targets ) {
if ( this - > currentChannel = = channel ) continue ;
auto chan = this - > channels - > find_channel ( channel ) ;
if ( ! chan | | ! chan - > subscribed ) continue ;
chan - > subscribed = false ;
/* ref_server->getClientsByChannel only acquires channel client lock */
auto clients = this - > server - > getClientsByChannel ( channel ) ;
this - > visibleClients . erase ( std : : remove_if ( this - > visibleClients . begin ( ) , this - > visibleClients . end ( ) , [ & , clients ] ( const weak_ptr < ConnectedClient > & weak ) {
auto c = weak . lock ( ) ;
if ( ! c ) {
logError ( this - > getServerId ( ) , " {} Got \" dead \" client in visible client list! This can cause a remote client disconnect within the future! " , CLIENT_STR_LOG_PREFIX ) ;
return true ;
}
return std : : find ( clients . begin ( ) , clients . end ( ) , c ) ! = clients . end ( ) ;
} ) , this - > visibleClients . end ( ) ) ;
unsubscribed_channels . push_back ( channel ) ;
}
if ( ! unsubscribed_channels . empty ( ) )
this - > notifyChannelUnsubscribed ( unsubscribed_channels ) ;
}
return unsubscribed_channels ;
}
bool ConnectedClient : : isClientVisible ( const std : : shared_ptr < ts : : server : : ConnectedClient > & client , bool lock ) {
for ( const auto & entry : this - > getVisibleClients ( lock ) )
if ( entry . lock ( ) = = client )
return true ;
return false ;
}
bool ConnectedClient : : notifyClientLeftView ( const std : : deque < std : : shared_ptr < ts : : server : : ConnectedClient > > & clients , const std : : string & reason , bool lock , const ts : : ViewReasonServerLeftT & _vrsl ) {
if ( clients . empty ( ) )
return true ;
if ( lock ) {
/* TODO: add locking of server channel tree in read mode and client tree in write mode! */
assert ( false ) ;
}
Command cmd ( " notifyclientleftview " ) ;
cmd [ " reasonmsg " ] = reason ;
cmd [ " reasonid " ] = ViewReasonId : : VREASON_SERVER_LEFT ;
cmd [ " ctid " ] = 0 ;
ChannelId current_channel_id = 0 ;
size_t index = 0 ;
auto it = clients . begin ( ) ;
while ( it ! = clients . end ( ) ) {
auto client = * it ;
assert ( client - > getClientId ( ) > 0 ) ;
assert ( client - > currentChannel | | & * client = = this ) ;
if ( ! client - > currentChannel )
continue ;
if ( current_channel_id ! = ( client - > currentChannel ? client - > currentChannel - > channelId ( ) : 0 ) ) {
if ( current_channel_id ! = 0 )
break ;
else
cmd [ index ] [ " cfid " ] = ( current_channel_id = client - > currentChannel - > channelId ( ) ) ;
}
cmd [ index ] [ " clid " ] = client - > getClientId ( ) ;
it + + ;
index + + ;
}
this - > visibleClients . erase ( std : : remove_if ( this - > visibleClients . begin ( ) , this - > visibleClients . end ( ) , [ & ] ( const weak_ptr < ConnectedClient > & weak ) {
auto c = weak . lock ( ) ;
if ( ! c ) {
logError ( this - > getServerId ( ) , " {} Got \" dead \" client in visible client list! This can cause a remote client disconnect within the future! " , CLIENT_STR_LOG_PREFIX ) ;
return true ;
}
return std : : find ( clients . begin ( ) , it , c ) ! = it ;
} ) , this - > visibleClients . end ( ) ) ;
this - > sendCommand ( cmd ) ;
if ( it ! = clients . end ( ) )
return this - > notifyClientLeftView ( { it , clients . end ( ) } , reason , false , _vrsl ) ;
return true ;
}
bool ConnectedClient : : notifyClientLeftView (
const shared_ptr < ConnectedClient > & client ,
const std : : shared_ptr < BasicChannel > & target_channel ,
ViewReasonId reason_id ,
const std : : string & reason_message ,
std : : shared_ptr < ConnectedClient > invoker ,
bool lock_channel_tree ) {
assert ( ! lock_channel_tree ) ; /* not supported yet! */
assert ( client & & client - > getClientId ( ) ! = 0 ) ;
assert ( client - > currentChannel | | & * client = = this ) ;
if ( client ! = this ) {
if ( reason_id = = VREASON_SERVER_STOPPED | | reason_id = = VREASON_SERVER_SHUTDOWN ) {
debugMessage ( this - > getServerId ( ) , " Replacing notify left reason " + to_string ( reason_id ) + " with " + to_string ( VREASON_SERVER_LEFT ) ) ;
reason_id = VREASON_SERVER_LEFT ;
}
}
/*
switch ( reasonId ) {
case ViewReasonId : : VREASON_MOVED :
case ViewReasonId : : VREASON_BAN :
case ViewReasonId : : VREASON_CHANNEL_KICK :
case ViewReasonId : : VREASON_SERVER_KICK :
case ViewReasonId : : VREASON_SERVER_SHUTDOWN :
case ViewReasonId : : VREASON_SERVER_STOPPED :
if ( reasonMessage . empty ( ) ) {
logCritical ( this - > getServerId ( ) , " {} ConnectedClient::notifyClientLeftView() => missing reason message for reason id {} " , CLIENT_STR_LOG_PREFIX , reasonId ) ;
reasonMessage = " <undefined> " ;
}
break ;
default :
break ;
}
*/
switch ( reason_id ) {
case ViewReasonId : : VREASON_MOVED :
case ViewReasonId : : VREASON_BAN :
case ViewReasonId : : VREASON_CHANNEL_KICK :
case ViewReasonId : : VREASON_SERVER_KICK :
if ( ! invoker ) {
logCritical ( this - > getServerId ( ) , " {} ConnectedClient::notifyClientLeftView() => missing invoker for reason id {} " , CLIENT_STR_LOG_PREFIX , reason_id ) ;
if ( this - > server )
invoker = this - > server - > serverRoot ;
}
break ;
default :
break ;
}
Command cmd ( " notifyclientleftview " ) ;
cmd [ " reasonmsg " ] = reason_message ;
cmd [ " reasonid " ] = reason_id ;
cmd [ " clid " ] = client - > getClientId ( ) ;
cmd [ " cfid " ] = client - > currentChannel ? client - > currentChannel - > channelId ( ) : 0 ;
cmd [ " ctid " ] = target_channel ? target_channel - > channelId ( ) : 0 ;
if ( invoker ) {
cmd [ " invokerid " ] = invoker - > getClientId ( ) ;
cmd [ " invokername " ] = invoker - > getDisplayName ( ) ;
cmd [ " invokeruid " ] = invoker - > getUid ( ) ;
}
/* TODO: Critical warn if the client hasn't been found? */
this - > visibleClients . erase ( std : : remove_if ( this - > visibleClients . begin ( ) , this - > visibleClients . end ( ) , [ & , client ] ( const weak_ptr < ConnectedClient > & weak ) {
auto c = weak . lock ( ) ;
if ( ! c ) {
logError ( this - > getServerId ( ) , " {} Got \" dead \" client in visible client list! This can cause a remote client disconnect within the future! " , CLIENT_STR_LOG_PREFIX ) ;
return true ;
}
return c = = client ;
} ) , this - > visibleClients . end ( ) ) ;
this - > sendCommand ( cmd ) ;
return true ;
}
bool ConnectedClient : : notifyClientLeftViewKicked ( const std : : shared_ptr < ConnectedClient > & client ,
const std : : shared_ptr < BasicChannel > & target_channel ,
const std : : string & message ,
std : : shared_ptr < ConnectedClient > invoker ,
bool lock_channel_tree ) {
assert ( ! lock_channel_tree ) ; /* not supported yet! */
assert ( client & & client - > getClientId ( ) ! = 0 ) ;
assert ( client - > currentChannel | | & * client = = this ) ;
/* TODO: Critical warn if the client hasn't been found? */
this - > visibleClients . erase ( std : : remove_if ( this - > visibleClients . begin ( ) , this - > visibleClients . end ( ) , [ & , client ] ( const weak_ptr < ConnectedClient > & weak ) {
auto c = weak . lock ( ) ;
if ( ! c ) {
logError ( this - > getServerId ( ) , " {} Got \" dead \" client in visible client list! This can cause a remote client disconnect within the future! " , CLIENT_STR_LOG_PREFIX ) ;
return true ;
}
return c = = client ;
} ) , this - > visibleClients . end ( ) ) ;
if ( ! invoker ) {
logCritical ( this - > getServerId ( ) , " {} ConnectedClient::notifyClientLeftViewKicked() => missing invoker for reason id {} " , CLIENT_STR_LOG_PREFIX , target_channel ? ViewReasonId : : VREASON_CHANNEL_KICK : ViewReasonId : : VREASON_SERVER_KICK ) ;
if ( this - > server )
invoker = this - > server - > serverRoot ;
}
Command cmd ( " notifyclientleftview " ) ;
cmd [ " clid " ] = client - > getClientId ( ) ;
cmd [ " cfid " ] = client - > currentChannel ? client - > currentChannel - > channelId ( ) : 0 ;
cmd [ " ctid " ] = target_channel ? target_channel - > channelId ( ) : 0 ;
cmd [ " reasonid " ] = ( uint8_t ) ( target_channel ? ViewReasonId : : VREASON_CHANNEL_KICK : ViewReasonId : : VREASON_SERVER_KICK ) ;
cmd [ " reasonmsg " ] = message ;
if ( invoker ) {
cmd [ " invokerid " ] = invoker - > getClientId ( ) ;
cmd [ " invokername " ] = invoker - > getDisplayName ( ) ;
cmd [ " invokeruid " ] = invoker - > getUid ( ) ;
}
this - > sendCommand ( cmd ) ;
return true ;
}
bool ConnectedClient : : notifyClientLeftViewBanned (
const shared_ptr < ConnectedClient > & client ,
const std : : string & message ,
std : : shared_ptr < ConnectedClient > invoker ,
size_t length ,
bool lock_channel_tree ) {
assert ( ! lock_channel_tree ) ; /* not supported yet! */
assert ( client & & client - > getClientId ( ) ! = 0 ) ;
assert ( client - > currentChannel | | & * client = = this ) ;
Command cmd ( " notifyclientleftview " ) ;
cmd [ " clid " ] = client - > getClientId ( ) ;
cmd [ " cfid " ] = client - > currentChannel ? client - > currentChannel - > channelId ( ) : 0 ;
cmd [ " ctid " ] = 0 ;
cmd [ " reasonid " ] = ViewReasonId : : VREASON_BAN ;
cmd [ " reasonmsg " ] = message ;
if ( invoker ) {
cmd [ " invokerid " ] = invoker - > getClientId ( ) ;
cmd [ " invokername " ] = invoker - > getDisplayName ( ) ;
cmd [ " invokeruid " ] = invoker - > getUid ( ) ;
}
if ( length > 0 ) {
cmd [ " bantime " ] = length ;
}
/* TODO: Critical warn if the client hasn't been found? */
this - > visibleClients . erase ( std : : remove_if ( this - > visibleClients . begin ( ) , this - > visibleClients . end ( ) , [ & , client ] ( const weak_ptr < ConnectedClient > & weak ) {
auto c = weak . lock ( ) ;
if ( ! c ) {
logError ( this - > getServerId ( ) , " {} Got \" dead \" client in visible client list! This can cause a remote client disconnect within the future! " , CLIENT_STR_LOG_PREFIX ) ;
return true ;
}
return c = = client ;
} ) , this - > visibleClients . end ( ) ) ;
this - > sendCommand ( cmd ) ;
return true ;
}
bool ConnectedClient : : sendNeededPermissions ( bool enforce ) {
if ( ! enforce & & this - > state ! = ConnectionState : : CONNECTED ) return false ;
if ( ! enforce & & chrono : : system_clock : : now ( ) - this - > lastNeededNotify < chrono : : seconds ( 5 ) & & this - > lastNeededPermissionNotifyChannel = = this - > currentChannel ) { //Dont spam these (hang up ui)
this - > requireNeededPermissionResend = true ;
return true ;
}
this - > lastNeededNotify = chrono : : system_clock : : now ( ) ;
this - > lastNeededPermissionNotifyChannel = this - > currentChannel ;
this - > requireNeededPermissionResend = false ;
return this - > notifyClientNeededPermissions ( ) ;
}
bool ConnectedClient : : notifyClientNeededPermissions ( ) {
Command cmd ( " notifyclientneededpermissions " ) ;
int index = 0 ;
unique_lock cache_lock ( this - > cached_permissions_lock ) ;
auto permissions = this - > cached_permissions ;
cache_lock . unlock ( ) ;
for ( const auto & value : permissions ) {
if ( value . second ! = permNotGranted | | value . first = = permission : : b_client_force_push_to_talk ) {
cmd [ index ] [ " permid " ] = value . first ;
cmd [ index + + ] [ " permvalue " ] = value . second = = permNotGranted ? 0 : value . second ;
}
}
if ( permissions . empty ( ) ) {
cmd [ 0 ] [ " permid " ] = permission : : b_client_force_push_to_talk ;
cmd [ 0 ] [ " permvalue " ] = false ;
}
this - > sendCommand ( cmd ) ;
return true ;
}
bool ConnectedClient : : notifyError ( const CommandResult & result , const std : : string & retCode ) {
Command cmd ( " error " ) ;
cmd [ " id " ] = result . error . errorId ;
cmd [ " msg " ] = result . error . message ;
if ( retCode . length ( ) > 0 )
cmd [ " return_code " ] = retCode ;
for ( const auto & extra : result . extraProperties )
cmd [ extra . first ] = extra . second ;
this - > sendCommand ( cmd ) ;
return true ;
}
inline std : : shared_ptr < ViewEntry > pop_view_entry ( std : : deque < std : : shared_ptr < ViewEntry > > & pool , ChannelId id ) {
for ( auto it = pool . begin ( ) ; it ! = pool . end ( ) ; it + + ) {
if ( ( * it ) - > channelId ( ) = = id ) {
auto handle = * it ;
pool . erase ( it ) ;
return handle ;
}
}
return nullptr ;
}
using ChannelIT = std : : deque < std : : shared_ptr < ViewEntry > > : : iterator ;
inline void send_channels ( ConnectedClient * client , ChannelIT begin , const ChannelIT & end , bool override_orderid ) {
if ( begin = = end )
return ;
Command channellist ( " channellist " ) ;
size_t index = 0 ;
while ( begin ! = end ) {
auto channel = ( * begin ) - > channel ( ) ;
if ( ! channel ) continue ;
for ( const auto & elm : channel - > properties ( ) . list_properties ( property : : FLAG_CHANNEL_VIEW , client - > getType ( ) = = CLIENT_TEAMSPEAK ? property : : FLAG_NEW : ( uint16_t ) 0 ) ) {
if ( elm . type ( ) = = property : : CHANNEL_ORDER )
channellist [ index ] [ elm . type ( ) . name ] = override_orderid ? 0 : ( * begin ) - > previous_channel ;
else
channellist [ index ] [ elm . type ( ) . name ] = elm . as < string > ( ) ;
}
begin + + ;
if ( + + index > 6 )
break ;
}
2019-07-21 14:56:53 +02:00
if ( dynamic_cast < VoiceClient * > ( client ) ) {
auto vc = dynamic_cast < VoiceClient * > ( client ) ;
vc - > sendCommand0 ( channellist , false , true ) ; /* we need to process this command directly so it will be processed before the channellistfinished stuff */
} else {
client - > sendCommand ( channellist ) ;
}
2019-07-17 19:37:18 +02:00
if ( begin ! = end )
send_channels ( client , begin , end , override_orderid ) ;
}
void ConnectedClient : : sendChannelList ( bool lock_channel_tree ) {
shared_lock server_channel_lock ( this - > server - > channel_tree_lock , defer_lock ) ;
unique_lock client_channel_lock ( this - > channel_lock , defer_lock ) ;
if ( lock_channel_tree ) {
server_channel_lock . lock ( ) ;
client_channel_lock . lock ( ) ;
}
auto channels = this - > channels - > insert_channels ( this - > server - > channelTree - > tree_head ( ) , true , false ) ;
if ( this - > currentChannel ) {
bool send_success ;
for ( const auto & channel : this - > channels - > show_channel ( this - > server - > channelTree - > find_linked_entry ( this - > currentChannel - > channelId ( ) ) , send_success ) )
channels . push_back ( channel ) ;
if ( ! send_success )
logCritical ( this - > getServerId ( ) , " ConnectedClient::sendChannelList => failed to insert default channel! " ) ;
}
/*
this - > channels - > print ( ) ;
auto channels_left = channels ;
for ( const auto & channel : channels ) {
if ( channel - > previous_channel = = 0 ) continue ;
bool erased = false ;
bool own = true ;
for ( const auto & entry : channels_left ) {
if ( entry - > channelId ( ) = = channel - > channelId ( ) )
own = true ;
if ( entry - > channelId ( ) = = channel - > previous_channel ) {
channels_left . erase ( find ( channels_left . begin ( ) , channels_left . end ( ) , entry ) ) ;
erased = true ;
break ;
}
}
if ( ! erased | | ! own ) {
logCritical ( this - > getServerId ( ) , " Client {} would get an invalid channel order disconnect! Channel {} is not send before his channel! (Flags: erased := {} | own := {}) " , CLIENT_STR_LOG_PREFIX_ ( this ) , channel - > previous_channel , erased , own ) ;
}
}
*/
std : : deque < std : : shared_ptr < ViewEntry > > entry_channels { pop_view_entry ( channels , this - > currentChannel - > channelId ( ) ) } ;
while ( entry_channels . front ( ) ) entry_channels . push_front ( pop_view_entry ( channels , entry_channels . front ( ) - > parentId ( ) ) ) ;
entry_channels . pop_front ( ) ;
if ( entry_channels . empty ( ) )
logCritical ( this - > getServerId ( ) , " ConnectedClient::sendChannelList => invalid (empty) own channel path! " ) ;
send_channels ( this , entry_channels . begin ( ) , entry_channels . end ( ) , true ) ;
this - > notifyClientEnterView ( _this . lock ( ) , nullptr , " " , this - > currentChannel , ViewReasonId : : VREASON_SYSTEM , nullptr , false ) ; //Notify self after path is send
send_channels ( this , channels . begin ( ) , channels . end ( ) , false ) ;
this - > sendCommand ( Command ( " channellistfinished " ) ) ;
}
void ConnectedClient : : sendChannelDescription ( const std : : shared_ptr < BasicChannel > & channel , bool lock ) {
shared_lock tree_lock ( this - > channel_lock , defer_lock ) ;
if ( lock )
tree_lock . lock ( ) ;
if ( ! this - > channels - > channel_visible ( channel ) ) return ;
Command cmd ( " notifychanneledited " ) ;
cmd [ " cid " ] = channel - > channelId ( ) ;
cmd [ " reasonid " ] = 9 ;
cmd [ " channel_description " ] = channel - > properties ( ) [ property : : CHANNEL_DESCRIPTION ] . as < std : : string > ( ) ;
this - > sendCommand ( cmd , true ) ;
}
void ConnectedClient : : tick ( const std : : chrono : : system_clock : : time_point & time ) {
ALARM_TIMER ( A1 , " ConnectedClient::tick " , milliseconds ( 2 ) ) ;
if ( this - > state = = ConnectionState : : CONNECTED ) {
if ( this - > requireNeededPermissionResend )
this - > sendNeededPermissions ( false ) ;
if ( this - > lastOnlineTimestamp . time_since_epoch ( ) . count ( ) = = 0 ) {
this - > lastOnlineTimestamp = time ;
} else if ( time - this - > lastOnlineTimestamp > seconds ( 120 ) ) {
this - > properties ( ) [ property : : CLIENT_MONTH_ONLINE_TIME ] + = duration_cast < seconds > ( time - lastOnlineTimestamp ) . count ( ) ;
this - > properties ( ) [ property : : CLIENT_TOTAL_ONLINE_TIME ] + = duration_cast < seconds > ( time - lastOnlineTimestamp ) . count ( ) ;
lastOnlineTimestamp = time ;
}
if ( time - this - > lastTransfareTimestamp > seconds ( 5 ) ) {
lastTransfareTimestamp = time ;
auto update = this - > connectionStatistics - > mark_file_bytes ( ) ;
if ( update . first > 0 ) {
this - > properties ( ) [ property : : CLIENT_MONTH_BYTES_DOWNLOADED ] + = update . first ;
this - > properties ( ) [ property : : CLIENT_TOTAL_BYTES_DOWNLOADED ] + = update . first ;
}
if ( update . second > 0 ) {
this - > properties ( ) [ property : : CLIENT_MONTH_BYTES_UPLOADED ] + = update . second ;
this - > properties ( ) [ property : : CLIENT_TOTAL_BYTES_UPLOADED ] + = update . second ;
}
}
}
if ( this - > last_statistics_tick + seconds ( 5 ) < time ) {
this - > last_statistics_tick = time ;
this - > connectionStatistics - > tick ( ) ;
}
}
void ConnectedClient : : sendServerInit ( ) {
Command command ( " initserver " ) ;
for ( const auto & prop : this - > server - > properties ( ) . list_properties ( property : : FLAG_SERVER_VIEW , this - > getType ( ) = = CLIENT_TEAMSPEAK ? property : : FLAG_NEW : ( uint16_t ) 0 ) ) {
command [ prop . type ( ) . name ] = prop . value ( ) ;
}
command [ " virtualserver_maxclients " ] = 32 ;
//Server stuff
command [ " client_talk_power " ] = this - > properties ( ) [ property : : CLIENT_TALK_POWER ] . as < string > ( ) ;
command [ " client_needed_serverquery_view_power " ] = this - > properties ( ) [ property : : CLIENT_NEEDED_SERVERQUERY_VIEW_POWER ] . as < string > ( ) ;
command [ " client_myteamspeak_id " ] = this - > properties ( ) [ property : : CLIENT_MYTEAMSPEAK_ID ] . as < string > ( ) ;
command [ " client_integrations " ] = this - > properties ( ) [ property : : CLIENT_INTEGRATIONS ] . as < string > ( ) ;
if ( ts : : config : : server : : DefaultServerLicense = = LicenseType : : LICENSE_AUTOMATIC_INSTANCE ) {
if ( serverInstance - > getVoiceServerManager ( ) - > usedSlots ( ) < = 32 )
command [ " lt " ] = LicenseType : : LICENSE_NONE ;
else if ( serverInstance - > getVoiceServerManager ( ) - > usedSlots ( ) < = 512 )
command [ " lt " ] = LicenseType : : LICENSE_NPL ;
else
command [ " lt " ] = LicenseType : : LICENSE_HOSTING ;
} else if ( ts : : config : : server : : DefaultServerLicense = = LicenseType : : LICENSE_AUTOMATIC_SERVER ) {
if ( this - > server - > properties ( ) [ property : : VIRTUALSERVER_MAXCLIENTS ] . as < size_t > ( ) < = 32 )
command [ " lt " ] = LicenseType : : LICENSE_NONE ;
else if ( this - > server - > properties ( ) [ property : : VIRTUALSERVER_MAXCLIENTS ] . as < size_t > ( ) < = 512 )
command [ " lt " ] = LicenseType : : LICENSE_NPL ;
else
command [ " lt " ] = LicenseType : : LICENSE_HOSTING ;
} else
command [ " lt " ] = ts : : config : : server : : DefaultServerLicense ;
command [ " pv " ] = 6 ; //Protocol version
command [ " acn " ] = this - > getDisplayName ( ) ;
command [ " aclid " ] = this - > getClientId ( ) ;
2019-07-21 14:58:16 +02:00
if ( dynamic_cast < VoiceClient * > ( this ) ) {
dynamic_cast < VoiceClient * > ( this ) - > sendCommand0 ( command , false , true ) ; /* process it directly so the order for the channellist entries is ensured. (First serverinit then everything else) */
} else {
this - > sendCommand ( command ) ;
}
2019-07-17 19:37:18 +02:00
}
bool ConnectedClient : : handleCommandFull ( Command & cmd , bool disconnectOnFail ) {
system_clock : : time_point start , end ;
start = system_clock : : now ( ) ;
# ifdef PKT_LOG_CMD
logTrace ( this - > getServerId ( ) , " {}[Command][Client -> Server] Processing command: {} " , CLIENT_STR_LOG_PREFIX , cmd . build ( false ) ) ;
# endif
CommandResult result ;
try {
result = this - > handleCommand ( cmd ) ;
} catch ( invalid_argument & ex ) {
debugMessage ( this - > getServerId ( ) , " {}[Command] Execution throws invalid_argument exception ({}). " , CLIENT_STR_LOG_PREFIX , ex . what ( ) ) ;
if ( disconnectOnFail ) {
this - > disconnect ( " Invalid argument ( " + string ( ex . what ( ) ) + " ) " ) ;
return false ;
} else {
result = { findError ( " parameter_convert " ) , " Invalid argument ( " + string ( ex . what ( ) ) + " ) " } ;
}
} catch ( exception & ex ) {
if ( disconnectOnFail ) {
this - > disconnect ( " Error while command handling ( " + string ( ex . what ( ) ) + " )! " ) ;
return false ;
} else {
result = { findError ( " vs_critical " ) , " error while command handling ( " + string ( ex . what ( ) ) + " ) " } ;
}
} catch ( . . . ) {
this - > disconnect ( " Error while command handling! (unknown) " ) ;
return false ;
}
bool generateReturnStatus = false ;
if ( result . type ( ) = = PERM_ERROR ) {
generateReturnStatus = true ;
} else if ( cmd [ " return_code " ] . size ( ) > 0 ) {
generateReturnStatus = ! cmd [ " return_code " ] . string ( ) . empty ( ) ;
}
if ( this - > getType ( ) = = ClientType : : CLIENT_QUERY )
generateReturnStatus = true ;
if ( ! result ) {
generateReturnStatus = true ;
stringstream ss ;
ss < < " { " ;
for ( auto it = result . extraProperties . begin ( ) ; it ! = result . extraProperties . end ( ) ; ) {
ss < < it - > first < < " = " < < it - > second ;
if ( + + it ! = result . extraProperties . end ( ) )
ss < < " , " ;
}
ss < < " } " < < endl ;
logTrace ( this - > getServerId ( ) , " {}[Command] Command {} with return code {} fails and returns error code {:#06x}. Properties: {} " , CLIENT_STR_LOG_PREFIX , cmd . command ( ) , cmd [ " return_code " ] . size ( ) ? " \" " + cmd [ " return_code " ] . first ( ) . as < std : : string > ( ) + " \" " : " <undefined> " , result . error . errorId , ss . str ( ) ) ;
}
if ( generateReturnStatus )
this - > notifyError ( result , cmd [ " return_code " ] . size ( ) > 0 ? cmd [ " return_code " ] . first ( ) . as < std : : string > ( ) : " " ) ;
if ( ! result & & this - > state = = ConnectionState : : INIT_HIGH ) {
this - > closeConnection ( system_clock : : now ( ) ) ; //Disconnect now
}
for ( const auto & handler : postCommandHandler )
handler ( ) ;
postCommandHandler . clear ( ) ;
end = system_clock : : now ( ) ;
if ( end - start > milliseconds ( 10 ) ) {
if ( end - start > milliseconds ( 100 ) )
logError ( this - > getServerId ( ) , " Command handling of command {} needs {}ms. This could be an issue! " , cmd . command ( ) , duration_cast < milliseconds > ( end - start ) . count ( ) ) ;
else
logWarning ( this - > getServerId ( ) , " Command handling of command {} needs {}ms. " , cmd . command ( ) , duration_cast < milliseconds > ( end - start ) . count ( ) ) ;
}
return true ;
}
std : : shared_ptr < BanRecord > ConnectedClient : : resolveActiveBan ( const std : : string & ip_address ) {
if ( this - > permissionGranted ( permission : : PERMTEST_ORDERED , permission : : b_client_ignore_bans , 1 ) ) return nullptr ;
//Check if manager banned
auto banManager = serverInstance - > banManager ( ) ;
shared_ptr < BanRecord > banEntry = nullptr ;
deque < shared_ptr < BanRecord > > entries ;
if ( ! banEntry ) {
banEntry = banManager - > findBanByName ( this - > server - > getServerId ( ) , this - > getDisplayName ( ) ) ;
if ( banEntry )
debugMessage ( this - > getServerId ( ) , " {} Resolved name ban ({}). Record id {}, server id {} " ,
CLIENT_STR_LOG_PREFIX ,
this - > getDisplayName ( ) ,
banEntry - > banId ,
banEntry - > serverId ) ;
}
if ( ! banEntry ) {
banEntry = banManager - > findBanByUid ( this - > server - > getServerId ( ) , this - > getUid ( ) ) ;
if ( banEntry )
debugMessage ( this - > getServerId ( ) , " {} Resolved uuid ban ({}). Record id {}, server id {} " ,
CLIENT_STR_LOG_PREFIX ,
this - > getUid ( ) ,
banEntry - > banId ,
banEntry - > serverId ) ;
}
if ( ! banEntry & & ! ip_address . empty ( ) ) {
banEntry = banManager - > findBanByIp ( this - > server - > getServerId ( ) , ip_address ) ;
if ( banEntry )
debugMessage ( this - > getServerId ( ) , " {} Resolved ip ban ({}). Record id {}, server id {} " ,
CLIENT_STR_LOG_PREFIX ,
ip_address ,
banEntry - > banId ,
banEntry - > serverId ) ;
}
auto vclient = dynamic_cast < VoiceClient * > ( this ) ;
if ( vclient )
if ( ! banEntry ) {
banEntry = banManager - > findBanByHwid ( this - > server - > getServerId ( ) , vclient - > getHardwareId ( ) ) ;
if ( banEntry )
debugMessage ( this - > getServerId ( ) , " {} Resolved hwid ban ({}). Record id {}, server id {} " ,
CLIENT_STR_LOG_PREFIX ,
vclient - > getHardwareId ( ) ,
banEntry - > banId ,
banEntry - > serverId ) ;
}
return banEntry ;
}
bool ConnectedClient : : update_cached_permissions ( ) {
auto values = this - > permissionValues ( permission : : PERMTEST_ORDERED , permission : : neededPermissions , shared_ptr ( this - > currentChannel ) ) ; /* copy the channel here so it does not change */
auto updated = false ;
{
lock_guard cached_lock ( this - > cached_permissions_lock ) ;
vector < permission : : PermissionType > old_permissions ;
old_permissions . reserve ( this - > cached_permissions . size ( ) ) ;
for ( const auto & value : this - > cached_permissions )
old_permissions . push_back ( value . first ) ;
for ( const auto & value : values ) {
auto value_it = cached_permissions . find ( value . first ) ;
if ( value_it = = cached_permissions . end ( ) ) { /* new entry */
updated = true ;
this - > cached_permissions [ value . first ] = value . second ;
continue ; /* no need to remove that from old_permissions because it isn't there */
} else if ( value_it - > second ! = value . second ) { /* entry changed */
updated = true ;
value_it - > second = value . second ;
}
{ /* we've updated the value or verified it */
auto old_it = find ( old_permissions . begin ( ) , old_permissions . end ( ) , value . first ) ;
if ( old_it ! = old_permissions . end ( ) )
old_permissions . erase ( old_it ) ;
}
}
for ( const auto & left : old_permissions ) {
auto value_it = cached_permissions . find ( left ) ;
if ( value_it ! = cached_permissions . end ( ) ) {
cached_permissions . erase ( value_it ) ;
updated = true ;
}
}
}
return updated ;
}
void ConnectedClient : : sendTSPermEditorWarning ( ) {
if ( config : : voice : : warn_on_permission_editor ) {
if ( system_clock : : now ( ) - this - > command_times . servergrouplist > milliseconds ( 1000 ) ) return ;
if ( system_clock : : now ( ) - this - > command_times . channelgrouplist > milliseconds ( 1000 ) ) return ;
this - > command_times . last_notify = system_clock : : now ( ) ;
this - > notifyClientPoke ( this - > server - > serverRoot , config : : messages : : teamspeak_permission_editor ) ;
}
}
permission : : v2 : : PermissionFlaggedValue ConnectedClient : : calculate_permission_value ( const ts : : permission : : PermissionType & permission , ts : : ChannelId channel_id ) {
if ( channel_id = = ( this - > currentChannel ? this - > currentChannel - > channelId ( ) : 0 ) | | channel_id = = - 1 ) {
std : : lock_guard lock ( this - > cached_permissions_lock ) ;
auto index = this - > cached_permissions . find ( permission ) ;
if ( index ! = this - > cached_permissions . end ( ) )
return { index - > second , index - > second ! = permNotGranted } ;
}
2019-07-19 21:32:31 +02:00
auto ref_server = this - > server ;
if ( ref_server ) {
auto result = this - > server - > calculatePermissions2 ( this - > getClientDatabaseId ( ) , { permission } , this - > getType ( ) , channel_id , false ) ;
if ( ! result . empty ( ) ) /* it should never be empty! */
return result . back ( ) . second ;
}
2019-07-17 19:37:18 +02:00
auto value = this - > permissionValue ( permission : : PERMTEST_ORDERED , permission , nullptr ) ;
return { value , value ! = permNotGranted } ;
2019-07-23 10:37:56 +02:00
}
# define RESULT(flag) \
do { \
ventry - > join_state_id = this - > join_state_id ; \
ventry - > joinable = ( flag ) ; \
return flag ; \
} while ( 0 )
bool ConnectedClient : : calculate_and_get_join_state ( const std : : shared_ptr < BasicChannel > & channel ) {
shared_ptr < ViewEntry > ventry ;
{
shared_lock view_lock ( this - > channel_lock ) ;
ventry = this - > channel_view ( ) - > find_channel ( channel ) ;
if ( ! ventry )
return false ;
}
if ( ventry - > join_state_id = = this - > join_state_id )
return ventry - > joinable ;
auto permission_cache = make_shared < CalculateCache > ( ) ;
switch ( channel - > channelType ( ) ) {
case ChannelType : : permanent :
if ( ! this - > permissionGranted ( permission : : PERMTEST_ORDERED , permission : : b_channel_join_permanent , 1 , channel , true , permission_cache ) )
RESULT ( false ) ;
break ;
case ChannelType : : semipermanent :
if ( ! this - > permissionGranted ( permission : : PERMTEST_ORDERED , permission : : b_channel_join_semi_permanent , 1 , channel , true , permission_cache ) )
RESULT ( false ) ;
break ;
case ChannelType : : temporary :
if ( ! this - > permissionGranted ( permission : : PERMTEST_ORDERED , permission : : b_channel_join_temporary , 1 , channel , true , permission_cache ) )
RESULT ( false ) ;
break ;
}
if ( ! this - > permissionGranted ( permission : : PERMTEST_ORDERED , permission : : b_channel_ignore_join_power , 1 , channel , true , permission_cache ) ) {
auto result = this - > server - > calculatePermissions2 ( this - > getClientDatabaseId ( ) , { permission : : i_channel_join_power } , this - > getType ( ) , channel - > channelId ( ) , false , permission_cache ) ;
if ( result . empty ( ) )
RESULT ( false ) ;
if ( ! channel - > permission_granted ( permission : : i_channel_needed_join_power , result . back ( ) . second , false ) )
RESULT ( false ) ;
}
auto val = this - > permissionValue ( permission : : PERMTEST_ORDERED , permission : : b_client_is_sticky , this - > currentChannel , permission_cache ) ;
if ( val ! = permNotGranted & & val > 0 ) {
auto st = this - > permissionValue ( permission : : PERMTEST_ORDERED , permission : : b_client_ignore_sticky , this - > currentChannel , permission_cache ) ;
if ( st ! = 1 )
RESULT ( false ) ;
}
RESULT ( true ) ;
2019-07-17 19:37:18 +02:00
}