File content:

  1. -- globalcombined lua fixes
  2. -- last updated 10/dez/2013
  3.  
  4. -- changelog:
  5. -- fixed an exploit /team follow2
  6. -- contains some fixes by Perlo_0ung?!
  7. -- implented b_gameformat check
  8. -- removed obsolete/unused stuff
  9. -- based on combinedfixes.lua
  10. -- first release
  11. -- all original work copyright -> reyalp@gmail.com
  12.  
  13. --
  14. modname = "globalcombined"
  15. version = "0.5"
  16.  
  17. function et_InitGame(levelTime,randomSeed,restart)
  18. et.RegisterModname(modname .. " " .. version)
  19. gameformat = tonumber(et.trap_Cvar_Get( "b_gameformat" ))
  20. maxclients = tonumber( et.trap_Cvar_Get( "sv_maxclients" ) )
  21. sniperaxis = 0
  22. sniperallies = 0
  23. end
  24.  
  25. -- client command checks, formerly wsfix
  26. -- prevent ws overrun exploit, crlf abuse
  27. -- history:
  28. -- 2 - bugfix
  29. -- TY McSteve for reporting this to us.
  30.  
  31. function et_ClientCommand(cno,cmd)
  32. cmd = string.lower(cmd)
  33.  
  34. local ref_typo = "7ref" --ref typo protection
  35. local argv1 = string.lower(et.trap_Argv(1))
  36. if string.find(argv1, "^" .. ref_typo .. "") then
  37. et.trap_SendServerCommand( cno, "cpm \"^wCkeck your typing\"" )
  38. return 1
  39. end
  40.  
  41. if cmd == "team" then --exploitfix /team follow2
  42. if et.trap_Argv(1) == "follow2" then
  43. return 1 --prevent it
  44. end
  45. end
  46.  
  47. if cmd == "forcetapout" then --forcetapout bugfix
  48. if et.gentity_get(cno, "r.contents") == 0 then --contents 0 =nobody
  49. return 1 --prevent it
  50. end
  51. end
  52.  
  53. if cmd == "b_gameformat" then
  54. et.trap_SendServerCommand( cno,"cpm \"^wb_gameformat is: "..gameformat.." default: 0\n\"")
  55. return 1
  56. end
  57.  
  58.  
  59. if gameformat ~= "6" then -- gameformat check
  60.  
  61. if cmd == "class" then
  62. if et.trap_Argv(1) == "c" then
  63. if et.trap_Argv(2) == "3" then
  64. for j = 0, (maxclients - 1) do
  65. if getTeam(j) == 2 and getWeapon(j) == 41 then
  66. sniperallies = sniperallies +1
  67. end
  68. end
  69. if getWeapon(cno) == 41 then
  70. sniperallies = sniperallies -1
  71. end
  72. if sniperallies > 0 then
  73. sniperallies = 0
  74. setPlayerWeapon(cno , 10) -- Set Weapon to Sten
  75. return 1
  76. end
  77. for j = 0, (maxclients - 1) do
  78. if getTeam(j) == 1 and getWeapon(j) == 32 then
  79. sniperaxis = sniperaxis +1
  80. end
  81. end
  82. if getWeapon(cno) == 32 then
  83. sniperaxis = sniperaxis -1
  84. end
  85. if sniperaxis > 0 then
  86. sniperaxis = 0
  87. setPlayerWeapon(cno , 10) -- Set Weapon to Sten
  88. return 1
  89. end
  90. end
  91. end
  92. end
  93.  
  94. if cmd == "team" then
  95. if et.trap_Argv(1) == "b" then
  96. if et.trap_Argv(2) == "4" then
  97. if et.trap_Argv(3) == "25" then
  98. for j = 0, (maxclients - 1) do
  99. if getTeam(j) == 2 and getWeapon(j) == 25 then
  100. sniperallies = sniperallies +1
  101. end
  102. end
  103. if getWeapon(cno) == 25 then
  104. sniperallies = sniperallies -1
  105. end
  106. if sniperallies > 0 then
  107. sniperallies = 0
  108. setPlayerWeapon(cno , 10) -- Set Weapon to Sten
  109. return 1
  110. end
  111. end
  112. end
  113. end
  114. end
  115. if cmd == "team" then
  116. if et.trap_Argv(1) == "r" then
  117. if et.trap_Argv(2) == "4" then
  118. if et.trap_Argv(3) == "32" then
  119. for j = 0, (maxclients - 1) do
  120. if getTeam(j) == 1 and getWeapon(j) == 32 then
  121. sniperaxis = sniperaxis +1
  122. end
  123. end
  124. if getWeapon(cno) == 32 then
  125. sniperaxis = sniperaxis -1
  126. end
  127. if sniperaxis > 0 then
  128. sniperaxis = 0
  129. setPlayerWeapon(cno , 10) -- Set Weapon to Sten
  130. return 1
  131. end
  132. end
  133. end
  134. end
  135. end
  136. end -- gameformat check end
  137.  
  138. if cmd == "ws" then
  139. local n = tonumber(et.trap_Argv(1))
  140. if not n then
  141. et.G_LogPrint(string.format("wsfix: client %d bad ws not a number [%s]\n",cno,tostring(et.trap_Argv(1))))
  142. return 1
  143. end
  144.  
  145. if n < 0 or n > 21 then
  146. et.G_LogPrint(string.format("wsfix: client %d bad ws %d\n",cno,n))
  147. return 1
  148. end
  149. return 0
  150. end
  151. if cmd == "callvote" or cmd == "ref" or cmd == "sa" or cmd == "semiadmin" then
  152. local args=et.ConcatArgs(1)
  153. -- et.G_LogPrint(string.format("combinedfixes: client %d %s [%s]\n",cno,cmd,args))
  154. if string.find(args,"[\r\n]") then
  155. et.G_LogPrint(string.format("combinedfixes: client %d bad %s [%s]\n",cno,cmd,args))
  156. return 1;
  157. end
  158. return 0
  159. end
  160.  
  161. return 0
  162. end --end ClientCommand
  163.  
  164. -- prevent various borkage by invalid userinfo
  165. -- version: 4
  166. -- history:
  167. -- 4 - check length and IP
  168. -- 3 - check for name exploit against guidcheck
  169. -- 2 - fix nil var ref if kicked in RunFrame
  170. -- fix incorrect cno in log message for ClientConnect kick
  171. -- 1 - initial release
  172.  
  173. -- names that can be used to exploit some log parsers
  174. -- note: only console log parsers or print hooks should be affected,
  175. -- game log parsers don't see these at the start of a line
  176. -- "^etpro IAC" check is required for guid checking
  177. -- comment/uncomment others as desired, or add your own
  178. -- NOTE: these are patterns for string.find
  179. badnames = {
  180. '^ShutdownGame',
  181. '^ClientBegin',
  182. '^ClientDisconnect',
  183. '^ExitLevel',
  184. '^Timelimit',
  185. '^EndRound',
  186. '^etpro IAC',
  187. '^Vote',
  188. '^etpro privmsg',
  189. -- "say" is relatively likely to have false positives
  190. -- but can potentially be used to exploit things that use etadmin_mod style !commands
  191. -- '^say',
  192. '^Callvote',
  193. '^broadcast',
  194. }
  195.  
  196. -- returns nil if ok, or reason
  197. function check_userinfo( cno )
  198. local userinfo = et.trap_GetUserinfo(cno)
  199. -- printf("check_userinfo: [%s]\n",userinfo)
  200.  
  201. -- bad things can happen if it's full
  202. if string.len(userinfo) > 980 then
  203. return "oversized"
  204. end
  205.  
  206. -- newlines can confuse various log parsers, and should never be there
  207. -- note this DOES NOT protect your log parsers, as the userinfo may
  208. -- already have been sent to the log
  209. if string.find(userinfo,"\n") then
  210. return "new line"
  211. end
  212.  
  213. -- the game never seems to make userinfos without a leading backslash,
  214. -- or with a trailing backslash, so reject those from the start
  215. if (string.sub(userinfo,1,1) ~= "\\" ) then
  216. return "missing leading slash"
  217. end
  218. -- shouldn't really be possible, since the engine stuffs ip\ip:port on the end
  219. if (string.sub(userinfo,-1,1) == "\\" ) then
  220. return "trailing slash"
  221. end
  222.  
  223. -- now that we know it is properly formed, count the slashes
  224. local n = 0
  225. for _ in string.gfind(userinfo,"\\") do
  226. n = n + 1
  227. end
  228.  
  229. if math.mod(n,2) == 1 then
  230. return "unbalanced"
  231. end
  232.  
  233. local m
  234. local t = {}
  235.  
  236. -- right number of slashes, check for dupe keys
  237. for m in string.gfind(userinfo,"\\([^\\]*)\\") do
  238. if string.len(m) == 0 then
  239. return "empty key"
  240. end
  241. m = string.lower(m)
  242. if t[m] then
  243. return "duplicate key"
  244. end
  245. t[m] = true
  246. end
  247.  
  248. -- they might hose the userinfo in some way that prevents the ip from being
  249. -- obtained. If so -> dump
  250. local ip = et.Info_ValueForKey( userinfo, "ip" )
  251. if ip == "" then
  252. return "missing ip"
  253. end
  254. -- printf("checkuserinfo: ip [%s]\n", ip)
  255.  
  256. -- make sure whatever is there is roughly valid while we are at it
  257. -- "localhost" may be present on a listen server. This module is not intended for listen servers.
  258. -- string.match 5.1.x
  259. -- string.find 5.0.x
  260. if string.find(ip,"^%d+%.%d+%.%d+%.%d+:%d+$") == nil then
  261. return "malformed ip"
  262. end
  263.  
  264. -- check for this to prevent exploitation of guidcheck
  265. -- note the proper solution would be for chats to always have a prefix in the console.
  266. -- Why the fuck does the server console need both
  267. -- say: [NW]reyalP: blah
  268. -- [NW]reyalP: blah
  269.  
  270. local name = et.Info_ValueForKey( userinfo, "name" )
  271. if name == "" then
  272. return "missing name"
  273. end
  274. -- printf("checkuserinfo %d name %s\n",cno,name)
  275. for _, badnamepat in ipairs(badnames) do
  276. local mstart,mend,cno = string.find(name,badnamepat)
  277. if mstart then
  278. return "name abuse"
  279. end
  280. end
  281. -- return nil
  282. end
  283.  
  284. -- 3.2.6 and earlier doesn't actually call et_ClientUserinfoChanged
  285. -- every time the userinfo changes,
  286. -- so we use et_RunFrame to check every so often
  287. -- comment this out or adjust to taste
  288. infocheck_lasttime=0
  289. infocheck_client=0
  290. -- check a client every 5 sec
  291. infocheck_freq=5000
  292.  
  293. function et_RunFrame( leveltime )
  294. if ( infocheck_lasttime + infocheck_freq > leveltime ) then
  295. return
  296. end
  297.  
  298. -- printf("infocheck %d %d\n", infocheck_client, leveltime)
  299. infocheck_lasttime = leveltime
  300. if ( et.gentity_get( infocheck_client, "inuse" ) ) then
  301. local reason = check_userinfo( infocheck_client )
  302. if ( reason ) then
  303. et.G_LogPrint(string.format("userinfocheck frame: client %d bad userinfo %s\n",infocheck_client,reason))
  304. et.trap_SetUserinfo( infocheck_client, "name\\badinfo" )
  305. et.trap_DropClient( infocheck_client, "bad userinfo", 0 )
  306. end
  307. end
  308.  
  309. infocheck_client = infocheck_client + 1
  310. if ( infocheck_client >= tonumber(et.trap_Cvar_Get("sv_maxclients")) ) then
  311. infocheck_client = 0
  312. end
  313. end
  314.  
  315. function et_ClientUserinfoChanged( cno )
  316. -- printf("clientuserinfochanged %d\n",cno)
  317. local reason = check_userinfo( cno )
  318. if ( reason ) then
  319. et.G_LogPrint(string.format("userinfocheck infochanged: client %d bad userinfo %s\n",cno,reason))
  320. et.trap_SetUserinfo( cno, "name\\badinfo" )
  321. et.trap_DropClient( cno, "bad userinfo", 0 )
  322. end
  323. end
  324.  
  325. -- prevent etpro guid borkage
  326. -- version: 1
  327. -- TY pants
  328.  
  329. -- default to kick with no temp ban for now
  330. DEF_GUIDCHECK_BANTIME = 0
  331.  
  332. function bad_guid(cno,reason)
  333. local bantime = tonumber(et.trap_Cvar_Get( "guidcheck_bantime" ))
  334. if not bantime or bantime < 0 then
  335. bantime = DEF_GUIDCHECK_BANTIME
  336. end
  337.  
  338. et.G_LogPrint(string.format("guidcheck: client %d bad GUID %s\n",cno,reason))
  339. -- we don't send them the reason. They can figure it out for themselves.
  340. et.trap_DropClient(cno,"You are banned from this server",bantime)
  341. end
  342.  
  343. function check_guid_line(text)
  344. --find a GUID line
  345. local guid,netname
  346. local mstart,mend,cno = string.find(text,"^etpro IAC: (%d+) GUID %[")
  347. if not mstart then
  348. return
  349. end
  350. text=string.sub(text,mend+1)
  351. --GUID] [NETNAME]\n
  352. mstart,mend,guid = string.find(text,"^([^%]]*)%] %[")
  353. if not mstart then
  354. bad_guid(cno,"couldn't parse guid")
  355. return
  356. end
  357. --NETNAME]\n
  358. text=string.sub(text,mend+1)
  359.  
  360. netname = et.gentity_get(cno,"pers.netname")
  361.  
  362. mstart,mend = string.find(text,netname,1,true)
  363. if not mstart or mstart ~= 1 then
  364. bad_guid(cno,"couldn't parse name")
  365. return
  366. end
  367.  
  368. text=string.sub(text,mend+1)
  369. if text ~= "]\n" then
  370. bad_guid(cno,"trailing garbage")
  371. return
  372. end
  373.  
  374. -- printf("guidcheck: etpro GUID %d %s %s\n",cno,guid,netname)
  375.  
  376. -- {N} is too complicated!
  377. mstart,mend = string.find(guid,"^%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x$")
  378. if not mstart then
  379. bad_guid(cno,"malformed")
  380. return
  381. end
  382. -- printf("guidcheck: OK\n")
  383. end
  384.  
  385. -- limit fakeplayers DOS
  386. -- http://aluigi.altervista.org/fakep.htm
  387. -- used if cvar is not set
  388. -- confugration:
  389. -- set ip_max_clients cvar as desired. If not set, defaults to the value below.
  390. --FAKEPLIMIT_VERSION = "1.0"
  391. DEF_IP_MAX_CLIENTS = 3
  392.  
  393. et.G_Printf = function(...)
  394. et.G_Print(string.format(unpack(arg)))
  395. end
  396.  
  397. function IPForClient(cno)
  398. -- TODO listen servers may be 'localhost'
  399. local userinfo = et.trap_GetUserinfo( cno )
  400. if userinfo == "" then
  401. return ""
  402. end
  403. local ip = et.Info_ValueForKey( userinfo, "ip" )
  404. -- find IP and strip port
  405. local ipstart, ipend, ipmatch = string.find(ip,"(%d+%.%d+%.%d+%.%d+)")
  406. -- don't error out if we don't match an ip
  407. if not ipstart then
  408. return ""
  409. end
  410. -- et.G_Printf("IPForClient(%d) = [%s]\n",cno,ipmatch)
  411. return ipmatch
  412. end
  413.  
  414. function et_ClientConnect( cno, firstTime, isBot )
  415. -- userinfocheck stuff. Do this before IP limit
  416. -- printf("connect %d\n",cno)
  417.  
  418. local reason = check_userinfo( cno )
  419. if ( reason ) then
  420. et.G_LogPrint(string.format("userinfocheck connect: client %d bad userinfo %s\n",cno,reason))
  421. return "bad userinfo"
  422. end
  423.  
  424. -- note IP validity should be enforced by userinfocheck stuff
  425. local ip = IPForClient( cno )
  426. local count = 1 -- we count as the first one
  427. local max = tonumber(et.trap_Cvar_Get( "ip_max_clients" ))
  428. if not max or max <= 0 then
  429. max = DEF_IP_MAX_CLIENTS
  430. end
  431. -- et.G_Printf("firstTime %d\n",firstTime);
  432. -- it's probably safe to only do this on firsttime, but checking
  433. -- every time doesn't hurt much
  434.  
  435. -- validate userinfo to filter out the people blindly using luigi's code
  436. local userinfo = et.trap_GetUserinfo( cno )
  437. -- et.G_Printf("userinfo: [%s]\n",userinfo)
  438. if et.Info_ValueForKey( userinfo, "rate" ) == "" then
  439. et.G_Printf(""..modname..": invalid userinfo from %s\n",ip)
  440. return "invalid connection"
  441. end
  442.  
  443. for i = 0, et.trap_Cvar_Get("sv_maxclients") - 1 do
  444. -- pers.connected is set correctly for fake players
  445. -- can't rely on userinfo being empty
  446. if i ~= cno and et.gentity_get(i,"pers.connected") > 0 and ip == IPForClient(i) then
  447. count = count + 1
  448. if count > max then
  449. et.G_Printf(""..modname..": too many connections from %s\n",ip)
  450. -- TODO should we drop / ban all connections from this IP ?
  451. return string.format("only %d connections per IP are allowed on this server",max)
  452. end
  453. end
  454. end
  455. end
  456. -- NoAutoDeclare()
  457.  
  458. -- Perlo_0ung
  459. -- rifle module
  460.  
  461. function et_ClientSpawn(cno,revived)
  462. if revived == 0 and et.gentity_get(cno, "sess.playerType") == 2 then
  463. et.gentity_set(cno,"ps.ammo",39,3)
  464. et.gentity_set(cno,"ps.ammo",40,3)
  465. end
  466. end
  467.  
  468. -- Perlo_0ung?!
  469. -- votefix version 2
  470. -- lowers the mapname in "callvote map" command. This fixes the bug with the wrong mapscripthash of .script files.
  471. -- note mapscripts (.script) have to be lower caps
  472. et.CS_VOTE_STRING = 7
  473.  
  474. function et_Print(text)
  475. check_guid_line(text)
  476.  
  477. local t = ParseString(text) --Vote Passed: Change map to suppLY
  478. if t[2] == "Passed:" and t[4] == "map" then
  479. if string.find(t[6],"%u") == nil or t[6] ~= getCS() then return 1 end
  480. local mapfixed = string.lower(t[6])
  481. et.trap_SendConsoleCommand( et.EXEC_APPEND, "ref map " .. mapfixed .. "\n" )
  482. end
  483. end
  484.  
  485. -- helper functions
  486.  
  487. function ParseString(inputString)
  488. local i = 1
  489. local t = {}
  490. for w in string.gfind(inputString, "([^%s]+)%s*") do
  491. t[i]=w
  492. i=i+1
  493. end
  494. return t
  495. end
  496.  
  497. function getCS()
  498. local cs = et.trap_GetConfigstring(et.CS_VOTE_STRING)
  499. local t = ParseString(cs)
  500. return t[4]
  501. end
  502.  
  503. function getWeapon(playerID)
  504. return et.gentity_get(playerID, "sess.playerWeapon")
  505. end
  506.  
  507. function getTeam(playerID)
  508. return et.gentity_get(playerID, "sess.sessionTeam")
  509. end
  510.  
  511. function setPlayerWeapon(playerID , weapon)
  512. et.gentity_set(playerID, "sess.latchPlayerWeapon", weapon)
  513. end