-- lua module to prevent various borkage by invalid userinfo -- version: 3 -- author: reyalp@gmail.com -- confugration: -- add userinfocheck.lua to lua_modules, or merge into your existing module -- history: -- 3 - check for name exploit against guidcheck -- 2 - fix nil var ref if kicked in RunFrame -- fix incorrect clientNum in log message for ClientConnect kick -- 1 - initial release -- require("rllib/vstrict").init() -- AutoDeclare() -- names that can be used to exploit some log parsers -- etpro IAC check is required for guidfix.lua -- comment/uncomment others as desired, or add your own -- NOTE: these are patterns for string.find badnames = { -- '^ShutdownGame', -- '^ClientBegin', -- '^ClientDisconnect', -- '^ExitLevel', -- '^Timelimit', -- '^EndRound', '^etpro IAC', -- '^etpro privmsg', -- "say" is relatively likely to have false positives -- but can potentially be used to exploit things that use etadmin_mod style !commands -- '^say', -- '^Callvote', -- '^broadcast' } -- returns nil if ok, or reason function check_userinfo( cno ) local userinfo = et.trap_GetUserinfo(cno) -- printf("check_userinfo: [%s]\n",userinfo) -- newlines can confuse various log parsers, and should never be there -- note this DOES NOT protect your log parsers, as the userinfo may -- already have been sent to the log if string.find(userinfo,"\n") then return "new line" end -- the game never seems to make userinfos without a leading backslash, -- or with a trailing backslash, so reject those from the start if (string.sub(userinfo,1,1) ~= "\\" ) then return "missing leading slash" end -- shouldn't really be possible, since the engine stuffs ip\ip:port on the end if (string.sub(userinfo,-1,1) == "\\" ) then return "trailing slash" end -- now that we know it is properly formed, count the slashes local n = 0 for _ in string.gfind(userinfo,"\\") do n = n + 1 end if math.mod(n,2) == 1 then return "unbalanced" end local m local t = {} -- right number of slashes, check for dupe keys for m in string.gfind(userinfo,"\\([^\\]*)\\") do if string.len(m) == 0 then return "empty key" end m = string.lower(m) if t[m] then return "duplicate key" end t[m] = true end -- check for this to prevent exploitation of guidcheck -- note the proper solution would be for chats to always have a prefix in the console. -- Why the fuck does the server console need both -- say: [NW]reyalP: blah -- [NW]reyalP: blah local name = et.Info_ValueForKey( userinfo, "name" ) -- printf("checkuserinfo %d name %s\n",cno,name) for _, badnamepat in ipairs(badnames) do local mstart,mend,cno = string.find(name,badnamepat) if mstart then return "name abuse" end end -- return nil end -- check in et_ClientConnect since kicking in -- the initial et_ClientUserinfoChanged is a bit screwy function et_ClientConnect( cno, firsttime, isbot ) -- printf("connect %d\n",cno) local reason = check_userinfo( cno ) if ( reason ) then et.G_LogPrint(string.format("userinfocheck connect: client %d bad userinfo %s\n",cno,reason)) return "bad userinfo" end end -- 3.2.6 and earlier doesn't actually call et_ClientUserinfoChanged -- every time the userinfo changes, -- so we use et_RunFrame to check every so often -- comment this out or adjust to taste infocheck_lasttime=0 infocheck_client=0 -- check a client every 5 sec infocheck_freq=5000 function et_RunFrame( leveltime ) if ( infocheck_lasttime + infocheck_freq > leveltime ) then return end -- printf("infocheck %d %d\n", infocheck_client, leveltime) infocheck_lasttime = leveltime if ( et.gentity_get( infocheck_client, "inuse" ) ) then local reason = check_userinfo( infocheck_client ) if ( reason ) then et.G_LogPrint(string.format("userinfocheck frame: client %d bad userinfo %s\n",infocheck_client,reason)) et.trap_SetUserinfo( infocheck_client, "name\\badinfo" ) et.trap_DropClient( infocheck_client, "bad userinfo", 0 ) end end infocheck_client = infocheck_client + 1 if ( infocheck_client >= tonumber(et.trap_Cvar_Get("sv_maxclients")) ) then infocheck_client = 0 end end function et_ClientUserinfoChanged( cno ) -- printf("clientuserinfochanged %d\n",cno) local reason = check_userinfo( cno ) if ( reason ) then et.G_LogPrint(string.format("userinfocheck infochanged: client %d bad userinfo %s\n",cno,reason)) et.trap_SetUserinfo( cno, "name\\badinfo" ) et.trap_DropClient( cno, "bad userinfo", 0 ) end end --NoAutoDeclare()