• gmsv_gatekeeper - Lua controlled server authentication
    453 replies, posted
  • Avatar of ComWalk
  • [b]Update 11/13/11 - VoiDeD / Chrisaster took over development way more than a year ago and will be way more helpful than I can[/b] GateKeeper allows Lua scripts to override the source server password check, allowing replacements of or additions to the normal sv_password check. This allows servers to have additional valid passwords or to use an authentication database that allows per-user or per-ip passwords. For private servers, this could help to eliminate password leaks, as the source of the leak would be immediately known. Furthermore, the function that it exposes completely invalidates the need for reserved slots in a server, as an unwanted player can be immediately dropped after a password has been verified, allowing a player to join an otherwise full server without receiving an error message. [b][u]Features:[/u][/b] Adds one new hook: "PlayerPasswordAuth" Adds five new functions: "gatekeeper.Drop", "gatekeeper.DropAllClients", "gatekeeper.GetNumClients", "gatekeeper.GetUserByAddress", and "gatekeeper.ForceProtocol" [b][u]Usage:[/u][/b] [b]Hook: PlayerPasswordAuth[/b] Arguments: string username, string password, string steamid, string ipaddress Returns: bool allowed [b]OR[/b] string denyreason [b]OR[/b] table { bool allowed [, string denyreason] } Notes: Returning nil allows the default server password check to execute. Returning true forces the password to be accepted, while false forces it to be denied. If a string is returned, the client is denied access with the message contained in the string. If a table is used as the return value, the second member can be used as an error message to be displayed to the client in place of the typical "Bad password." message. Example: [lua] -- This function will print the username and provided password of all connection attempts. local function CheckPassword(user, pass, steam, ip) print(Format("User: '%s' Pass: '%s' SteamID: '%s'", name, pass, steam)) -- Force all password comparisons to fail and display a custom error. return {false, Format("'%s' is not the password I was looking for, %s", pass, name)} -- The above line is equivalent to: -- return Format("'%s' is not the password I was looking for, %s", pass, name) -- Or, if you do not want to specify an error message and simply want clients kicked: -- return false end hook.Add("PlayerPasswordAuth", "Example", CheckPassword) [/lua] [b]Function: gatekeeper.Drop[/b] Arguments: number userid ([i]Not[/i] the entity index!), string reason Returns: bool dropped (true if player found, false if userid was not connected) Notes: This takes place instantly; when the function has returned the client has already been disconnected. This allows for the server to drop a player during password verification and have the slot be immediately filled by the client attempting to have their password verified. Reserved slots are a thing of the past! This [b]can[/b] happen at any time, and is not limited to PlayerPasswordAuth. Be wary of this, however, as a call to this immediately removes the client from the server, so if done in a hook like PlayerSay, this can [b]crash the server[/b] if you allow the message to go through. Only use this when you can ensure that nothing will be done with the player's object for the rest of the hook. This is safe to call in concommands. This is useful for kick messages in general. Example: [lua] -- Server sterilization for k,v in pairs(player.GetAll()) do if v:Name() == "SamuraiMushroom" then gatekeeper.Drop(v:UserID(), "Worthless") end end [/lua] [b]Function: gatekeeper.GetNumClients[/b] Arguments: none Returns: table { active = ?, spawning = ?, total = ? } Notes: Gets a table returning information about the clients connected to the server: How many there are, how many are ingame, and how many are currently connecting. Example: [lua] local clients = gatekeeper.GetNumClients() print(clients.active) -- prints the number of clients currently active (have spawned) in the server print(clients.spawning) -- prints the number of clients in the connection process print(clients.total) -- prints the total number of clients in the server (connecting AND in game) [/lua] [b]Function: gatekeeper.DropAllClients[/b] Arguments: string disconnectreason Returns: nil Notes: Self explanatory Example: [lua] hook.Add("Think", "Teh Kieranator", function() gatekeeper.DropAllClients("You have been Kieranated") end) [/lua] [b]Function: gatekeeper.GetUserByAddress[/b] Arguments: string ipaddress (in form ip:port) Returns: int userid (if ip was valid and connected) or nil (no user connected from that ip) Example: This will [b]not[/b] work in PlayerPasswordAuth because the connecting player does not have a userid yet. At any other point except possibly PlayerConnect (untested), it should function as expected. [lua] for k,v in pairs(player.GetAll()) do print(v:UserID(), gatekeeper.GetUserByAddress(v:IPAddress())) -- should be the same end [/lua] [b][u]Long, drawn out example:[/u][/b] [lua] -- GateKeeper V4 Example File -- ComWalk -- -- This file demonstrates adding two additional passwords, one of which will -- force the server to make room for the client, invalidating the need for -- reserved slots to be used. require("gatekeeper") -- The cvar that will act as an additional password local pass_new = CreateConVar("sv_password_new", "", FCVAR_PROTECTED | FCVAR_NOTIFY) -- The cvar that will make room for a client if they attempt to join local pass_makeroom = CreateConVar("sv_password_makeroom", "", FCVAR_PROTECTED | FCVAR_NOTIFY) local function PasswordCheck(name, pass, steam, ip) print(Format("User: '%s' Pass: '%s' SteamID: '%s'", name, pass, steam)) -- To make use of the new error messages, you are forced to -- return a table. Garry's hook module does not support hooks -- that return multiple values, and this is the Next Best Thing. -- Valid return values are booleans and tables following the -- following format: { bool allow [, string reason] } -- The reason will only be used by the module if the client -- is not allowed in the server. if name == "SamuraiMushroom" then return {false, "Entry automatically denied: Worthless"} elseif steam == "STEAM_0:1:16258120" then return {false, "Changing your name won't help either."} elseif steam == "STEAM_0:0:9249431" then return {false, "Beat you to the punch on this one too."} end if pass_new:GetString() == pass and pass_new:GetString() != "" then -- Since the given password matches the 'new' password -- and the new password has been set, returning true -- will prevent the normal password check code -- from being executed. return true elseif pass_makeroom:GetString() == pass and pass_makeroom:GetString() != "" then local players = gatekeeper.GetNumClients().total -- There is no need to drop anybody if the server isn't full if players == MaxPlayers() then local dropme = players[math.random(1, #players)] -- Gatekeeper exposes a new function to Lua, gatekeeper.Drop, -- which allows for the server to drop a client with a custom reason. -- Unlike using RunConsoleCommand to kick the unwanted player, this -- takes place instantly, and because the maxplayers check takes place -- after the password check, any player that provides this password to -- the server will successfully join, without any error message. gatekeeper.Drop(dropme:UserID(), Format("Auto-kicking to make room for '%s'", name)) end -- Provided password was valid; force success and prevent -- the default password check from being executed. return true end -- Returning nil will allow the default password check to take place -- and, in the case of this example, check sv_password as well. -- In the event of a custom authentication system in which -- the default check doesn't need to be run at all, simply -- returning false here will suffice. return end hook.Add("PlayerPasswordAuth", "test", PasswordCheck) [/lua] [b][u]Download:[/u][/b] [url=http://gmodmodules.googlecode.com/svn/trunk/gmsv_gatekeeper/bin/gmsv_gatekeeper.dll]Win32[/url] [url=http://gmodmodules.googlecode.com/svn/trunk/gmsv_gatekeeper/bin/gmsv_gatekeeper_linux.dll]Lin
  • Avatar of Deco Da Man
  • Very nice! Now I can kick people without the "Kicked by Console: " prefix :D Thanks you very muchly.
  • Avatar of Janorkie
  • By damn this is the best module yet released. This would be absolutely PERFECT for private servers.
  • Avatar of Nev
  • Is username and ip the only things you can get from the authing player? Would it be possible to get their steam id as well? Anyways, a very nice module!
  • Avatar of ComWalk
  • [QUOTE=Nevec;13755380]Is username and ip the only things you can get from the authing player? Would it be possible to get their steam id as well? Anyways, a very nice module![/QUOTE] The steamid of a connecting client is not made available until well into the connection process, so it unfortunately is not possible at this time. Edit: Actually, it is available during connection due to a recent engine update, however not until after PlayerPasswordAuth is called and a player slot is secured for the client, so it remains impractical.
  • Avatar of Catdaemon
  • [QUOTE=ComWalk;13759599]The steamid of a connecting client is not made available until well into the connection process, so it unfortunately is not possible at this time. Edit: Actually, it is available during connection due to a recent engine update, however not until after PlayerPasswordAuth is called and a player slot is secured for the client, so it remains impractical.[/QUOTE] I suppose you could keep the password stored and compare it with the steamid later on.
  • Avatar of Coona
  • I fucking love this but for the sake of argument For all the times I've wanted to say it: [img]http://www.funny-city.com/photos/niggers.jpg[/img]
  • Avatar of Python1320
  • Awesome addon, I'm replacing all kick commands with the drop command, now I can send multiline kick-reasons.
  • Avatar of Vicis
  • It would be great if you could get the SteamID in the auth hook, too.
  • Avatar of LiamBrown
  • I think i could find some use for this, very good. Thanks! [img]http://sa.tweek.us/emots/images/emot-whip.gif[/img]
  • Avatar of kp3
  • This is fucking awesome. Just yesterday i found that a server where using some sort of modified kick message, Never would have thought it was released.
  • Avatar of ComWalk
  • I've done a major overhaul of gatekeeper, and have added support for custom 'Bad password' messages and also provide the steamid of a connecting player to the PlayerPasswordAuth hook. I will be releasing it within a day or so, but in the mean time, it would help me ensure that several things are working properly if you would attempt to join 98.232.218.93:27015 with a random password; the more people who do this the better, as it will help me determine how reliably the steamid will be provided in the hook. In my limited testing it has worked every time, but more testing could never hurt. [b]Attempt to join 98.232.218.93:27015 with a random password if you'd like to help me test![/b] If you get a password denied message, don't worry, that means that it worked! [b]Added, to be released shortly:[/b] [list] [*]Support for second return value in PlayerPasswordAuth that acts as an error message [*]SteamID is now the third argument to PlayerPasswordAuth, making gatekeeper [b]much[/b] more viable for use with client authentication! [*]C++ code is actually commented this time around [/list]
  • Avatar of Deco Da Man
  • [QUOTE=ComWalk;14183322] [*]SteamID is now the third argument to PlayerPasswordAuth, making gatekeeper [b]much[/b] more viable for use with client authentication![/QUOTE] [b]Win.[/b]
  • Avatar of kevkev
  • [QUOTE=ComWalk;14183322]I've done a major overhaul of gatekeeper, and have added support for custom 'Bad password' messages and also provide the steamid of a connecting player to the PlayerPasswordAuth hook. I will be releasing it within a day or so, but in the mean time, it would help me ensure that several things are working properly if you would attempt to join 98.232.218.93:27015 with a random password; the more people who do this the better, as it will help me determine how reliably the steamid will be provided in the hook. In my limited testing it has worked every time, but more testing could never hurt. [b]Attempt to join 98.232.218.93:27015 with a random password if you'd like to help me test![/b] If you get a password denied message, don't worry, that means that it worked! [b]Added, to be released shortly:[/b] [list] [*]Support for second return value in PlayerPasswordAuth that acts as an error message [*]SteamID is now the third argument to PlayerPasswordAuth, making gatekeeper [b]much[/b] more viable for use with client authentication! [*]C++ code is actually commented this time around [/list][/QUOTE] Whohooo!
  • Avatar of Nev
  • [QUOTE=ComWalk;14183322][list][*]SteamID is now the third argument to PlayerPasswordAuth, making gatekeeper [b]much[/b] more viable for use with client authentication! [/list][/QUOTE] Yess
  • Avatar of Python1320
  • Sadly a too long password input fucks up the drop message. Valve, fix your dialogs! =(
  • Avatar of ComWalk
  • The update has been released! Please contact me if you find anything wrong with the update. [QUOTE=Python1320;14190890]Sadly a too long password input fucks up the drop message. Valve, fix your dialogs! =([/QUOTE] The drop message is handled completely by Lua; it was the hacky Lua script I had set up that was responsible for the overflow. Server operators will have to be sure to keep the error messages rather short so that they remain legible.
  • Avatar of peanutzero
  • Bloody brilliant. I was having some trouble making a custom banning system because kickid wasn't working right in, but dropping the code into the PlayerPasswordAuth made it work like a charm! There's no reason this shouldn't be packaged with Gmod.
  • Avatar of AzuiSleet
  • [QUOTE=peanutzero;14200619]There's no reason this shouldn't be packaged with Gmod.[/QUOTE] Besides the fact that it uses detours, and you shouldn't include them in production code?
  • Avatar of ComWalk
  • [QUOTE=peanutzero;14200619]There's no reason this shouldn't be packaged with Gmod.[/QUOTE] To expand on what AzuiSleet said, I rely on both sigscanning and detours; in order to code this the 'right' way garry would have to maintain and distribute his own engine with gmod, something he has shown little to no interest in doing.
  • Avatar of huntskikbut
  • When I try to load this on my dedicated server it says "System Error 14001" Or something of the sort. Any ideas? It works fine for me in single player.
  • Avatar of ComWalk
  • Have you tried installing the VS2008 C++ redistributable? [url=http://www.microsoft.com/downloads/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf&displaylang=en]This[/url] should fix it, but if not there's more stuff that can be tried.
  • Avatar of Catdaemon
  • Is your example code supposed to work when the server is full? I'm playing with this, I'm running password admin then retry but it still says it's full and doesn't kick anyone. [lua] local function PasswordCheck(name, pass, steam, ip) if pass=="admin" then local players = player.GetAll() if #players == MaxPlayers() then local dropme = players[math.random(1, #players)] gatekeeper.Drop(dropme:UserID(), Format("Auto-kicking to make room for '%s'", name)) end return true end return end hook.Add("PlayerPasswordAuth", "DoPasr", PasswordCheck) [/lua] Doesn't seem to work with sv_password set either.
  • Avatar of ComWalk
  • [QUOTE=Catdaemon;14361390]Is your example code supposed to work when the server is full? I'm playing with this, I'm running password admin then retry but it still says it's full and doesn't kick anyone. [lua] local function PasswordCheck(name, pass, steam, ip) if pass=="admin" then local players = player.GetAll() if #players == MaxPlayers() then local dropme = players[math.random(1, #players)] gatekeeper.Drop(dropme:UserID(), Format("Auto-kicking to make room for '%s'", name)) end return true end return end hook.Add("PlayerPasswordAuth", "DoPasr", PasswordCheck) [/lua] Doesn't seem to work with sv_password set either.[/QUOTE] My best guess is that when you were testing there were players in the connection process, meaning they wouldn't be returned by player.GetAll() causing the example code to believe that there was still room in the server. Add some debug text so it prints the number of players as well as the number returned by MaxPlayers() to see if you can find the discrepancy. If need be I'll expose functions to get the actual number of players in the server or something.