init.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. if not minetest.global_exists("xban") then xban = {} end
  2. xban.db = xban.db or {}
  3. xban.tempbans = xban.tempbans or {}
  4. xban.MP = minetest.get_modpath("xban2")
  5. -- Localize for performance.
  6. local vector_round = vector.round
  7. -- Reloadable.
  8. dofile(xban.MP.."/serialize.lua")
  9. local DEF_SAVE_INTERVAL = 300 -- 5 minutes
  10. local DEF_DB_FILENAME = minetest.get_worldpath().."/xban.db"
  11. local DB_FILENAME = minetest.settings:get("xban.db_filename")
  12. local SAVE_INTERVAL = tonumber(
  13. minetest.settings:get("xban.db_save_interval")) or DEF_SAVE_INTERVAL
  14. if (not DB_FILENAME) or (DB_FILENAME == "") then
  15. DB_FILENAME = DEF_DB_FILENAME
  16. end
  17. local function make_logger(level)
  18. return function(text, ...)
  19. minetest.log(level, "[xban] "..text:format(...))
  20. end
  21. end
  22. local ACTION = make_logger("action")
  23. local WARNING = make_logger("warning")
  24. local unit_to_secs = {
  25. s = 1, m = 60, h = 3600,
  26. D = 86400, W = 604800, M = 2592000, Y = 31104000,
  27. [""] = 1,
  28. }
  29. local function parse_time(t) --> secs
  30. local secs = 0
  31. for num, unit in t:gmatch("(%d+)([smhDWMY]?)") do
  32. secs = secs + (tonumber(num) * (unit_to_secs[unit] or 1))
  33. end
  34. return secs
  35. end
  36. function xban.find_entry(player, create) --> entry, index
  37. for index, e in ipairs(xban.db) do
  38. for name in pairs(e.names) do
  39. if name == player then
  40. return e, index
  41. end
  42. end
  43. end
  44. if create then
  45. ACTION("Created new entry for `%s'", player)
  46. local e = {
  47. names = { [player]=true },
  48. banned = false,
  49. record = { },
  50. last_pos = { },
  51. last_seen = { },
  52. }
  53. table.insert(xban.db, e)
  54. return e, #xban.db
  55. end
  56. return nil
  57. end
  58. function xban.get_info(player) --> ip_name_list, banned, last_record
  59. local e = xban.find_entry(player)
  60. if not e then
  61. return nil, "No such entry."
  62. end
  63. return e.names, e.banned, e.record[#e.record]
  64. end
  65. function xban.ban_player(player, source, expires, reason) --> bool, err
  66. if xban.get_whitelist(player) then
  67. return nil, "Player is whitelisted; remove from whitelist first!"
  68. end
  69. local e = xban.find_entry(player, true)
  70. if e.banned then
  71. return nil, "Player already banned."
  72. end
  73. if minetest.check_player_privs(player, {server=true}) then
  74. return nil, "Administrators cannot be banned!"
  75. end
  76. local rec = {
  77. source = source,
  78. time = os.time(), -- Date of ban.
  79. expires = expires,
  80. reason = reason,
  81. }
  82. table.insert(e.record, rec)
  83. e.names[player] = true
  84. local pl = minetest.get_player_by_name(player)
  85. if pl then
  86. local ip = minetest.get_player_ip(player)
  87. if ip then
  88. e.names[ip] = true
  89. end
  90. e.last_pos[player] = pl:get_pos()
  91. end
  92. e.reason = reason
  93. e.time = rec.time
  94. e.expires = expires
  95. e.banned = true
  96. local msg
  97. local date = (expires and os.date("%c", expires)
  98. or "The End of Time")
  99. if expires then
  100. table.insert(xban.tempbans, e)
  101. msg = ("Banned: Expires: %s, Reason: %s"):format(date, reason)
  102. else
  103. msg = ("Banned: Reason: %s"):format(reason)
  104. end
  105. for nm in pairs(e.names) do
  106. minetest.kick_player(nm, msg)
  107. end
  108. ACTION("%s bans %s until %s for reason: %s", source, player,
  109. date, reason)
  110. ACTION("Banned Names/IPs: %s", table.concat(e.names, ", "))
  111. return true
  112. end
  113. function xban.unban_player(player, source) --> bool, err
  114. local e = xban.find_entry(player)
  115. if not e then
  116. return nil, "No such entry."
  117. end
  118. if not e.banned then
  119. return nil, "Player not banned."
  120. end
  121. local rec = {
  122. source = source,
  123. time = os.time(),
  124. reason = "Unbanned",
  125. }
  126. table.insert(e.record, rec)
  127. e.banned = false
  128. e.reason = nil
  129. e.expires = nil
  130. e.time = nil
  131. ACTION("%s unbans %s", source, player)
  132. ACTION("Unbanned Names/IPs: %s", table.concat(e.names, ", "))
  133. return true
  134. end
  135. function xban.get_whitelist(name_or_ip)
  136. return xban.db.whitelist and xban.db.whitelist[name_or_ip]
  137. end
  138. function xban.remove_whitelist(name_or_ip)
  139. if xban.db.whitelist then
  140. xban.db.whitelist[name_or_ip] = nil
  141. end
  142. end
  143. function xban.add_whitelist(name_or_ip, source)
  144. local wl = xban.db.whitelist
  145. if not wl then
  146. wl = { }
  147. xban.db.whitelist = wl
  148. end
  149. wl[name_or_ip] = {
  150. source=source,
  151. }
  152. return true
  153. end
  154. function xban.get_record(player)
  155. local e = xban.find_entry(player)
  156. if not e then
  157. return nil, ("No entry for <%s>."):format(rename.gpn(player))
  158. elseif (not e.record) or (#e.record == 0) then
  159. return nil, ("<%s> has no ban records."):format(rename.gpn(player))
  160. end
  161. local record = { }
  162. for _, rec in ipairs(e.record) do
  163. local msg = rec.reason and ("Reason: '" .. rec.reason .. "'") or "No reason given"
  164. if rec.expires then
  165. msg = msg..(", Expires: %s"):format(os.date("%c", e.expires))
  166. end
  167. if rec.source then
  168. msg = msg..", Source: '"..rec.source.."'"
  169. end
  170. table.insert(record, ("[%s]: %s."):format(os.date("%c", e.time), msg))
  171. end
  172. local last_pos
  173. if e.last_pos and e.last_pos[player] then
  174. last_pos = ("User was last seen at %s."):format(
  175. rc.pos_to_namestr(vector_round(e.last_pos[player])))
  176. end
  177. return record, last_pos
  178. end
  179. function xban.on_prejoinplayer(name, ip)
  180. if minetest.check_player_privs(name, {server=true}) then
  181. return
  182. end
  183. local wl = xban.db.whitelist or { }
  184. if wl[name] or wl[ip] then return end
  185. local e = xban.find_entry(name) or xban.find_entry(ip)
  186. if not e then return end
  187. if e.banned then
  188. local date = (e.expires and os.date("%c", e.expires) or "The End of Time")
  189. return ("\nBanned!\nExpires: %s\nReason: %s"):format(date, e.reason)
  190. end
  191. end
  192. function xban.on_joinplayer(player)
  193. local name = player:get_player_name()
  194. local e = xban.find_entry(name)
  195. local ip = minetest.get_player_ip(name)
  196. if not e then
  197. -- Don't create database entries for players who never registered.
  198. -- This keeps the database size limited to only those players who
  199. -- decided to play on the server for a while. Guests are not included.
  200. if ip and passport.player_registered(name) then
  201. e = xban.find_entry(ip, true)
  202. else
  203. return
  204. end
  205. end
  206. e.names[name] = true
  207. if ip then
  208. e.names[ip] = true
  209. end
  210. e.last_seen[name] = os.time()
  211. end
  212. function xban.on_leaveplayer(player, timeout)
  213. local pname = player:get_player_name()
  214. -- Don't record last_pos for temporary accounts.
  215. if not passport.player_registered(pname) then
  216. return
  217. end
  218. local e = xban.find_entry(pname)
  219. if e then
  220. e.last_pos[pname] = player:get_pos()
  221. end
  222. end
  223. function xban.chatcommand_ban(name, params)
  224. local plname, reason = params:match("^(%S+)%s+(.+)$")
  225. if not (plname and reason) then
  226. minetest.chat_send_player(name, "# Server: Usage: '/xban <player> <reason>'.")
  227. return false
  228. end
  229. reason = reason:trim()
  230. local ok, e = xban.ban_player(plname, rename.gpn(name), nil, reason)
  231. local msg = ("# Server: Banned <%s>."):format(rename.gpn(plname))
  232. if not ok then
  233. msg = "# Server: " .. e
  234. end
  235. minetest.chat_send_player(name, msg)
  236. return ok
  237. end
  238. function xban.chatcommand_tempban(name, params)
  239. local plname, time, reason = params:match("^(%S+)%s+(%S+)%s+(.+)$")
  240. if not (plname and time and reason) then
  241. minetest.chat_send_player(name, "# Server: Usage: '/xtempban <player> <time> <reason>'.")
  242. return false
  243. end
  244. time = parse_time(time)
  245. if time < 60 then
  246. minetest.chat_send_player(name, "# Server: You must ban for at least 60 seconds.")
  247. return false
  248. end
  249. reason = reason:trim()
  250. local expires = os.time() + time
  251. local ok, e = xban.ban_player(plname, rename.gpn(name), expires, reason)
  252. local msg = ("# Server: Banned <%s> until %s."):format(rename.gpn(plname), os.date("%c", expires))
  253. if not ok then
  254. msg = "# Server: " .. e
  255. end
  256. minetest.chat_send_player(name, msg)
  257. return ok
  258. end
  259. function xban.chatcommand_xunban(name, params)
  260. local plname = params:match("^%S+$")
  261. if not plname or plname == "" then
  262. minetest.chat_send_player(name, "# Server: Usage: '/xunban <player>|<ip>'.")
  263. return
  264. end
  265. local ok, e = xban.unban_player(plname, rename.gpn(name))
  266. local msg = ("# Server: Unbanned <%s>."):format(rename.gpn(plname))
  267. if not ok then
  268. msg = "# Server: " .. e
  269. end
  270. minetest.chat_send_player(name, msg)
  271. return ok
  272. end
  273. function xban.chatcommand_record(name, params)
  274. local plname = params:match("^%S+$")
  275. if not plname or plname == "" then
  276. minetest.chat_send_player(name, "# Server: Usage: '/xban_record <player>|<ip>'.")
  277. return false
  278. end
  279. local record, last_pos = xban.get_record(plname)
  280. if not record then
  281. local err = last_pos
  282. minetest.chat_send_player(name, "# Server: [xban]: "..err)
  283. return
  284. end
  285. for _, e in ipairs(record) do
  286. minetest.chat_send_player(name, "# Server: [xban]: "..e)
  287. end
  288. if last_pos then
  289. minetest.chat_send_player(name, "# Server: [xban]: "..last_pos)
  290. end
  291. minetest.chat_send_player(name, "# Server: [xban]: End of record.")
  292. return true
  293. end
  294. function xban.chatcommand_wl(name, params)
  295. local cmd, plname = params:match("^%s*(%S+)%s*(%S+)%s*$")
  296. if not cmd or cmd == "" then
  297. minetest.chat_send_player(name, "# Server: Usage: '/xban_wl (add|del|get) <name>|<ip>'.")
  298. return false
  299. end
  300. if not plname or plname == "" then
  301. minetest.chat_send_player(name, "# Server: Usage: '/xban_wl (add|del|get) <name>|<ip>'.")
  302. return false
  303. end
  304. if cmd == "add" then
  305. xban.add_whitelist(plname, rename.gpn(name))
  306. ACTION("%s adds %s to whitelist", name, plname)
  307. minetest.chat_send_player(name, "# Server: Added <"..rename.gpn(plname).."> to whitelist!")
  308. return true
  309. elseif cmd == "del" then
  310. xban.remove_whitelist(plname)
  311. ACTION("%s removes %s to whitelist", name, plname)
  312. minetest.chat_send_player(name, "# Server: Removed <"..rename.gpn(plname).."> from whitelist!")
  313. return true
  314. elseif cmd == "get" then
  315. local e = xban.get_whitelist(plname)
  316. if e then
  317. minetest.chat_send_player(name, "# Server: Source: "..(e.source and ("'"..e.source.."'") or "Unknown")..".")
  318. return true
  319. else
  320. minetest.chat_send_player(name, "# Server: No whitelist for <"..rename.gpn(plname)..">!")
  321. return true
  322. end
  323. end
  324. end
  325. function xban.check_temp_bans()
  326. minetest.after(60, function() xban.check_temp_bans() end)
  327. local to_rm = { }
  328. local now = os.time()
  329. for i, e in ipairs(xban.tempbans) do
  330. if e.expires and (e.expires <= now) then
  331. table.insert(to_rm, i)
  332. e.banned = false
  333. e.expires = nil
  334. e.reason = nil
  335. e.time = nil
  336. end
  337. end
  338. for _, i in ipairs(to_rm) do
  339. table.remove(xban.tempbans, i)
  340. end
  341. end
  342. function xban.save_db()
  343. minetest.after(SAVE_INTERVAL, function() xban.save_db() end)
  344. local f, e = io.open(DB_FILENAME, "wt")
  345. xban.db.timestamp = os.time()
  346. if f then
  347. local ok, err = f:write(xban.serialize(xban.db))
  348. if not ok then
  349. WARNING("Unable to save database: %s", err)
  350. end
  351. else
  352. WARNING("Unable to save database: %s", e)
  353. end
  354. if f then f:close() end
  355. return
  356. end
  357. function xban.load_db()
  358. local f, e = io.open(DB_FILENAME, "rt")
  359. if not f then
  360. WARNING("Unable to load database: %s", e)
  361. return
  362. end
  363. local cont = f:read("*a")
  364. if not cont then
  365. WARNING("Unable to load database: %s", "Read failed")
  366. return
  367. end
  368. local t, e2 = minetest.deserialize(cont)
  369. if not t then
  370. WARNING("Unable to load database: %s",
  371. "Deserialization failed: "..(e2 or "unknown error"))
  372. return
  373. end
  374. local function is_vector(data)
  375. local count = 0
  376. if type(data) == "table" then
  377. for k, v in pairs(data) do
  378. count = count + 1
  379. end
  380. end
  381. if count ~= 3 then
  382. return false
  383. end
  384. if type(data) == "table"
  385. and type(data.x) == "number"
  386. and type(data.y) == "number"
  387. and type(data.z) == "number"
  388. and count == 3 then
  389. return true
  390. end
  391. return false
  392. end
  393. -- Update DB format. Remove entries that are in an old, invalid format.
  394. for k, v in ipairs(t) do
  395. if type(v.last_seen) ~= "nil" and type(v.last_seen) ~= "table" then
  396. v.last_seen = nil
  397. end
  398. if type(v.last_pos) ~= "nil" and is_vector(v.last_pos) then
  399. v.last_pos = nil
  400. end
  401. -- Add empty tables for missing elements.
  402. if not v.last_pos then
  403. v.last_pos = {}
  404. end
  405. if not v.last_seen then
  406. v.last_seen = {}
  407. end
  408. end
  409. xban.db = t
  410. xban.tempbans = {}
  411. for _, entry in ipairs(xban.db) do
  412. if entry.banned and entry.expires then
  413. table.insert(xban.tempbans, entry)
  414. end
  415. end
  416. end
  417. if not xban.registered then
  418. minetest.register_on_joinplayer(function(...)
  419. return xban.on_joinplayer(...)
  420. end)
  421. minetest.register_on_prejoinplayer(function(...)
  422. return xban.on_prejoinplayer(...)
  423. end)
  424. minetest.register_on_leaveplayer(function(...)
  425. return xban.on_leaveplayer(...)
  426. end)
  427. minetest.register_chatcommand("xban_wl", {
  428. description = "Manages the XBan whitelist.",
  429. params = "(add|del|get) <name>|<ip>",
  430. privs = { ban=true },
  431. func = function(...)
  432. return xban.chatcommand_wl(...)
  433. end,
  434. })
  435. minetest.register_chatcommand("xban_record", {
  436. description = "Show the ban records of a player.",
  437. params = "<player>|<ip>",
  438. privs = { ban=true },
  439. func = function(...)
  440. return xban.chatcommand_record(...)
  441. end,
  442. })
  443. minetest.register_chatcommand("xunban", {
  444. description = "XUnban a player.",
  445. params = "<player>|<ip>",
  446. privs = { ban=true },
  447. func = function(...)
  448. return xban.chatcommand_xunban(...)
  449. end,
  450. })
  451. minetest.register_chatcommand("xtempban", {
  452. description = "XBan a player temporarily.",
  453. params = "<player> <time> <reason>",
  454. privs = { ban=true },
  455. func = function(...)
  456. return xban.chatcommand_tempban(...)
  457. end,
  458. })
  459. minetest.register_chatcommand("xban", {
  460. description = "XBan a player.",
  461. params = "<player> <reason>",
  462. privs = { ban=true },
  463. func = function(...)
  464. return xban.chatcommand_ban(...)
  465. end,
  466. })
  467. minetest.register_on_shutdown(function(...) return xban.save_db(...) end)
  468. minetest.after(SAVE_INTERVAL, function() xban.save_db() end)
  469. minetest.after(1, function() xban.check_temp_bans() end)
  470. xban.load_db()
  471. local c = "xban:core"
  472. local f = xban.MP .. "/init.lua"
  473. reload.register_file(c, f, false)
  474. xban.registered = true
  475. end
  476. -- Reloadable.
  477. dofile(xban.MP.."/dbimport.lua")
  478. dofile(xban.MP.."/gui.lua")