Page 1 of 1

Best way to disconnect player on connect by SteamID

Posted: Sun Jun 12, 2016 6:47 pm
by iPlayer
Hey there,

I'm trying to make sure particular SteamIDs won't connect to my server. There're some conditions and custom disconnect message, so using stock ban system is not an option.

The best way would be just refusing connection

Syntax: Select all

from listeners import OnClientConnect


@OnClientConnect
def listener_on_client_connect(
allow_connect, index, name, address, reject_message, max_reject_len):

allow_connect.set_bool(False)
reject_message.set_string_array("Another time")


But OnClientConnect doesn't provide any sort of SteamID-related info, because, as far as I know, SteamID might be unknown at that time.

Then most obvious choice would be OnNetworkidValidated:

Syntax: Select all

from filters.players import PlayerIter
from listeners import OnNetworkidValidated


BANNED_STEAMID = "[U:1:66103826]"


@OnNetworkidValidated
def listener_on_networkid_validated(user_name, networkid):
if networkid != BANNED_STEAMID:
return

for player in PlayerIter():
if player.steamid == networkid:
break
else:
return

player.kick("Another time")


However, suprisingly I realized that OnNetworkidValidated is not the first listener to fire. OnClientActive happens (or at least can happen) way before OnNetworkidValidated does:

Syntax: Select all

from listeners import OnClientActive
from players.entity import Player


BANNED_STEAMID = "[U:1:66103826]"


@OnClientActive
def listener_on_client_active(index):
player = Player(index)
if player.steamid != BANNED_STEAMID:
return

player.kick("Another time")


What is more, I tested on a listen server, and sometimes OnNetworkidValidated didn't fire at all. Or it could fire when the player is already able to see server's MoTD.


So, I have several questions.
#1. What is the best way to disconnect player by their SteamID as early as possible? Preferably without them downloading the map and other resources, and preferably without "Player iPlayer has joined the server" - though the latter message can be disabled separately.
#2. What exactly OnNetworkidValidated is? Is this related to some separate verification process with SRCDS communicating with Steam? If so, if I check player's SteamID before it has been validated, is there's any chance that this SteamID was faked?
#3. Maybe there's another way to do this - hook some function that checks if player is banned via banid and apply our own checks?

P.S. As a reference, I tested the following code 2 times on a listen server (Windows, CS:S)

Syntax: Select all

from time import strftime

from core import echo_console
from listeners import OnClientActive
from listeners import OnClientConnect
from listeners import OnClientFullyConnect
from listeners import OnClientPutInServer
from listeners import OnNetworkidValidated


@OnClientActive
def listener_on_client_active(*args):
echo_console("OnClientActive at {}".format(strftime('%X')))

@OnClientConnect
def listener_on_client_connect(*args):
echo_console("OnClientConnect at {}".format(strftime('%X')))

@OnClientFullyConnect
def listener_on_client_fully_connect(*args):
echo_console("OnClientFullyConnect at {}".format(strftime('%X')))

@OnClientPutInServer
def listener_on_client_put_in_server(*args):
echo_console("OnClientPutInServer at {}".format(strftime('%X')))

@OnNetworkidValidated
def listener_on_networkid_validated(user_name, networkid):
echo_console("OnNetworkidValidated at {}".format(strftime('%X')))


First test:

Code: Select all

OnClientConnect at 21:42:54
OnNetworkidValidated at 21:42:55
OnClientPutInServer at 21:42:55
OnClientActive at 21:42:55


Second test:

Code: Select all

OnClientConnect at 21:44:15
OnClientPutInServer at 21:44:15
OnClientActive at 21:44:15
OnNetworkidValidated at 21:44:17


OnClientFullyConnect didn't fire at all.

Re: Best way to disconnect player on connect by SteamID

Posted: Sun Jun 12, 2016 7:16 pm
by Ayuto
I will need to take a closer look at it, but regarding OnClientFullyConnect see the note on the wiki:
http://builds.sourcepython.com/job/Sour ... llyconnect

It only fires on on CS:GO and Blade Symphony.

Edit:
You could also use the player_connect event. Though, it will display "xy joined the server".

Re: Best way to disconnect player on connect by SteamID

Posted: Sun Jun 12, 2016 8:16 pm
by Ayuto
I have updated the OnClientConnect and OnClientPutInServer listeners:
https://github.com/Source-Python-Dev-Te ... 63bf9987fd

Though, those two listeners are still a bit useless.

However, I have also added Client.steamid and Client.disconnect():
https://github.com/Source-Python-Dev-Te ... 0516f5f1e2

Syntax: Select all

from engines.server import server
from listeners import OnNetworkidValidated

BAD_STEAMID = 'xy'

@OnNetworkidValidated
def listener_on_networkid_validated(user_name, steamid):
if steamid != BAD_STEAMID:
return

client = find_client(steamid)
if client is None:
# IDK what to do now...
print('Unable to find client.')
return

client.disconnect('You shall not pass.')

def find_client(steamid):
for x in range(server.num_clients):
client = server.get_client(x)
if client.steamid == steamid:
return client

return None
I don't know if that prints the "xy joined the server" message, but I'm pretty sure that this is one of the earliest situations where you can do SteamID comparisons and kick players.

Re: Best way to disconnect player on connect by SteamID

Posted: Sun Jun 12, 2016 8:46 pm
by iPlayer
Thanks, Ayuto

Regarding to player_connect:
http://forums.eventscripts.com/viewtopic.php?t=6478
player_connect will never work for steamid's. Use player_activate and do a check for if you allready caught the steamid.

http://www.eventscripts.com/pages/STEAM_ID_PENDING
Player has just joined, but not yet validated. For example you have this value as event_var(networkid) in player_connect event.


Regarding to OnNetworkidValidated: thanks for adding stuff and showing the way on how to disconnect players. I guess I will stick to this method. But I'm still curious why networkid validation happens so late (sometimes after player_activate and sometimes when player is actually in the game) and if this listener is reliable enough to restrict access from the server.

Re: Best way to disconnect player on connect by SteamID

Posted: Sun Jun 12, 2016 9:54 pm
by BackRaw
iPlayer wrote:... But I'm still curious why networkid validation happens so late (sometimes after player_activate and sometimes when player is actually in the game) and if this listener is reliable enough to restrict access from the server.

I don't know for sure, but isn't it possible that when the client is connecting to the server, right when the map starts to load (or somewhere around that corner), another thread gets executed which checks if the SteamID is a valid one, and thus you experience these issues. The checking thread would only be finished when the steam server would respond - simply to not interrupt the player from connecting and loading the map.

Don't take my word for that I'm just thinking of possible reasons :D

Re: Best way to disconnect player on connect by SteamID

Posted: Sun Jun 12, 2016 10:11 pm
by iPlayer
BackRaw wrote:I don't know for sure, but isn't it possible that when the client is connecting to the server, right when the map starts to load (or somewhere around that corner), another thread gets executed which checks if the SteamID is a valid one, and thus you experience these issues. The checking thread would only be finished when the steam server would respond - simply to not interrupt the player from connecting and loading the map.

Don't take my word for that I'm just thinking of possible reasons :D


I thought that too, that could explain why validation finishes when player has already joined the gameplay. But still: stock ban system is able to refuse connection by SteamID. And it disconnects banned players way before they join the game.

As I see it, there're 2 SteamIDs:
1. Unvalidated, the SteamID that is told by the client itself - stock ban system checks this SteamID as it's available very early.
2. Validated one - when previous SteamID is confirmed to belong to the client by Steam itself

Question is, how reliable stock ban system then is. Will the server kick the player if validation of their SteamID fails?

It may be other way tho, but then I don't understand how stock "banid" works. It disconnects the player so early that "Player X has joined the game" doesn't even pop up.

For now, I see 2 approaches:
1. Freeze/gag/mute the player until their SteamID is validated. When it's validated, either let them play or kick them.
2. Look at how stock ban system works. Unfortunately, I haven't had good luck investigating it in Source SDK 2013 - it's not there. It's in engine.dll though, but I couldn't understand it from assembler code in IDA. Maybe I should try debugging.

My next step: disconnect the server from the internet and see if OnNetworkidValidated fires and if stock ban system works.

Re: Best way to disconnect player on connect by SteamID

Posted: Mon Jun 13, 2016 11:54 am
by Ayuto
The function Filter_IsUserBanned() in the engine binary does the ban checking. It gets called by these two methods:
  1. CBaseServer::ConnectClient()
  2. Steam3Server::OnValidateAuthTicketResponse()
Steam3Server::OnValidateAuthTicketResponse() also calls the OnNetworkidValidated listener and it only calls the listener when the server was able to contact the steam server. So, when your server does not have an internet connection Steam3Server::OnValidateAuthTicketResponse()/OnNetworkidValidated are never called. So, your guess is probably right that the client tells the server the SteamID at first.

Re: Best way to disconnect player on connect by SteamID

Posted: Mon Jun 13, 2016 12:15 pm
by iPlayer
That clears things up. If my server lost connection to Steam (or Steam is simply down), banned users can probably fake their SteamID.

I will stick to disabling the player until their SteamID is validated.

Re: Best way to disconnect player on connect by SteamID

Posted: Mon Jun 13, 2016 1:09 pm
by Ayuto
This is the earliest (unvalidated) SteamID based banning system I was able to create while being able to send a custom message to the client.

Syntax: Select all

import memory

from memory import Convention
from memory import DataType

from memory.hooks import PostHook

from core import PLATFORM
from engines.server import server
from players import Client


server_ptr = memory.get_object_pointer(server)

# bool CBaseServer::CheckChallengeType(CBaseClient *, int, netadr_s &, int, char const*, int, int)
CheckChallengeType = server_ptr.make_virtual_function(
57 if PLATFORM == 'windows' else 58,
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER, DataType.INT, DataType.POINTER,
DataType.INT, DataType.STRING, DataType.INT, DataType.INT],
DataType.BOOL
)

# void CBaseServer::RejectConnection(netadr_s const&, int, char const*)
RejectConnection = server_ptr.make_virtual_function(
47 if PLATFORM == 'windows' else 48,
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER, DataType.INT, DataType.STRING],
DataType.VOID
)

@PostHook(CheckChallengeType)
def post_check_challenge_type(args, return_value=0):
# CheckChallengeType() seems to set the SteamID of the client
client = memory.make_object(Client, args[1] + 4)
print(client.steamid)

RejectConnection(server, args[3], args[7], 'blabla\n')
return False

Re: Best way to disconnect player on connect by SteamID

Posted: Mon Jun 13, 2016 1:13 pm
by iPlayer
Wow. Thanks! I will combine the two.