diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index 143063b4e9..f6293454ee 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -91,3 +91,6 @@ #define SPAM_TRIGGER_WARNING 5 //Number of identical messages required before the spam-prevention will warn you to stfu #define SPAM_TRIGGER_AUTOMUTE 10 //Number of identical messages required before the spam-prevention will automute you + +#define STICKYBAN_DB_CACHE_TIME 10 SECONDS +#define STICKYBAN_ROGUE_CHECK_TIME 5 diff --git a/code/_globalvars/admin.dm b/code/_globalvars/admin.dm new file mode 100644 index 0000000000..81037ff3dd --- /dev/null +++ b/code/_globalvars/admin.dm @@ -0,0 +1,12 @@ +GLOBAL_LIST_EMPTY(stickybanadminexemptions) //stores a list of ckeys exempted from a stickyban (workaround for a bug) +GLOBAL_LIST_EMPTY(stickybanadmintexts) //stores the entire stickyban list temporarily +GLOBAL_VAR(stickbanadminexemptiontimerid) //stores the timerid of the callback that restores all stickybans after an admin joins + +// /proc/init_smites() //todo: add on the second wave +// var/list/smites = list() +// for (var/_smite_path in subtypesof(/datum/smite)) +// var/datum/smite/smite_path = _smite_path +// smites[initial(smite_path.name)] = smite_path +// return smites + +// GLOBAL_LIST_INIT_TYPED(smites, /datum/smite, init_smites()) diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index 4eff4fbf1d..c779c9f26d 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -177,6 +177,25 @@ SUBSYSTEM_DEF(dbcore) return FALSE return new /datum/DBQuery(sql_query, connection) +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) + if (!islist(querys)) + if (!istype(querys, /datum/DBQuery)) + CRASH("Invalid query passed to QuerySelect: [querys]") + querys = list(querys) + + for (var/thing in querys) + var/datum/DBQuery/query = thing + if (warn) + INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute) + else + INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute) + + for (var/thing in querys) + var/datum/DBQuery/query = thing + UNTIL(!query.in_progress) + if (qdel) + qdel(query) + /* Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query. Rows missing columns present in other rows will resolve to SQL NULL @@ -361,5 +380,5 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table //strip sensitive stuff if(findtext(message, ": CreateConnection(")) message = "CreateConnection CENSORED" - + log_sql("BSQL_DEBUG: [message]") diff --git a/code/controllers/subsystem/stickyban.dm b/code/controllers/subsystem/stickyban.dm index 189efa99fe..0c71777bc0 100644 --- a/code/controllers/subsystem/stickyban.dm +++ b/code/controllers/subsystem/stickyban.dm @@ -1,34 +1,231 @@ SUBSYSTEM_DEF(stickyban) - name = "Sticky Ban" + name = "PRISM" init_order = INIT_ORDER_STICKY_BAN flags = SS_NO_FIRE var/list/cache = list() + var/list/dbcache = list() + var/list/confirmed_exempt = list() + var/dbcacheexpire = 0 + /datum/controller/subsystem/stickyban/Initialize(timeofday) - var/list/bannedkeys = world.GetConfig("ban") + if (length(GLOB.stickybanadminexemptions)) + restore_stickybans() + var/list/bannedkeys = sticky_banned_ckeys() //sanitize the sticky ban list + + //delete db bans that no longer exist in the database and add new legacy bans to the database + if (SSdbcore.Connect() || length(SSstickyban.dbcache)) + if (length(GLOB.stickybanadminexemptions)) + restore_stickybans() + for (var/oldban in (world.GetConfig("ban") - bannedkeys)) + var/ckey = ckey(oldban) + if (ckey != oldban && (ckey in bannedkeys)) + continue + + var/list/ban = params2list(world.GetConfig("ban", oldban)) + if (ban && !ban["fromdb"]) + if (!import_raw_stickyban_to_db(ckey, ban)) + log_world("Could not import stickyban on [oldban] into the database. Ignoring") + continue + dbcacheexpire = 0 + bannedkeys += ckey + world.SetConfig("ban", oldban, null) + + if (length(GLOB.stickybanadminexemptions)) //the previous loop can sleep + restore_stickybans() + for (var/bannedkey in bannedkeys) var/ckey = ckey(bannedkey) - var/list/ban = stickyban2list(world.GetConfig("ban", bannedkey)) + var/list/ban = get_stickyban_from_ckey(bannedkey) - //byond stores sticky bans by key, that can end up confusing things - //i also remove it here so that if any stickybans cause a runtime, they just stop existing - world.SetConfig("ban", bannedkey, null) + //byond stores sticky bans by key, that's lame + if (ckey != bannedkey) + world.SetConfig("ban", bannedkey, null) if (!ban["ckey"]) ban["ckey"] = ckey - //storing these can break things and isn't needed for sticky ban tracking - ban -= "IP" - ban -= "computer_id" - ban["matches_this_round"] = list() ban["existing_user_matches_this_round"] = list() ban["admin_matches_this_round"] = list() + ban["pending_matches_this_round"] = list() + cache[ckey] = ban - - for (var/bannedckey in cache) - world.SetConfig("ban", bannedckey, list2stickyban(cache[bannedckey])) + world.SetConfig("ban", ckey, list2stickyban(ban)) return ..() + +/datum/controller/subsystem/stickyban/proc/Populatedbcache() + var/newdbcache = list() //so if we runtime or the db connection dies we don't kill the existing cache + + // var/datum/db_query/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey") + // var/datum/db_query/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched") + // var/datum/db_query/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched") + // var/datum/db_query/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched") + + var/datum/DBQuery/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey") + var/datum/DBQuery/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched") + var/datum/DBQuery/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched") + var/datum/DBQuery/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched") + + SSdbcore.QuerySelect(list(query_stickybans, query_ckey_matches, query_cid_matches, query_ip_matches)) + + if (query_stickybans.last_error) + qdel(query_stickybans) + qdel(query_ckey_matches) + qdel(query_cid_matches) + qdel(query_ip_matches) + return + + while (query_stickybans.NextRow()) + var/list/ban = list() + + ban["ckey"] = query_stickybans.item[1] + ban["message"] = query_stickybans.item[2] + ban["reason"] = "(InGameBan)([query_stickybans.item[3]])" + ban["admin"] = query_stickybans.item[3] + ban["datetime"] = query_stickybans.item[4] + ban["type"] = list("sticky") + + newdbcache["[query_stickybans.item[1]]"] = ban + + + if (!query_ckey_matches.last_error) + while (query_ckey_matches.NextRow()) + var/list/match = list() + + match["stickyban"] = query_ckey_matches.item[1] + match["matched_ckey"] = query_ckey_matches.item[2] + match["first_matched"] = query_ckey_matches.item[3] + match["last_matched"] = query_ckey_matches.item[4] + match["exempt"] = text2num(query_ckey_matches.item[5]) + + var/ban = newdbcache[query_ckey_matches.item[1]] + if (!ban) + continue + var/keys = ban[text2num(query_ckey_matches.item[5]) ? "whitelist" : "keys"] + if (!keys) + keys = ban[text2num(query_ckey_matches.item[5]) ? "whitelist" : "keys"] = list() + keys[query_ckey_matches.item[2]] = match + + if (!query_cid_matches.last_error) + while (query_cid_matches.NextRow()) + var/list/match = list() + + match["stickyban"] = query_cid_matches.item[1] + match["matched_cid"] = query_cid_matches.item[2] + match["first_matched"] = query_cid_matches.item[3] + match["last_matched"] = query_cid_matches.item[4] + + var/ban = newdbcache[query_cid_matches.item[1]] + if (!ban) + continue + var/computer_ids = ban["computer_id"] + if (!computer_ids) + computer_ids = ban["computer_id"] = list() + computer_ids[query_cid_matches.item[2]] = match + + + if (!query_ip_matches.last_error) + while (query_ip_matches.NextRow()) + var/list/match = list() + + match["stickyban"] = query_ip_matches.item[1] + match["matched_ip"] = query_ip_matches.item[2] + match["first_matched"] = query_ip_matches.item[3] + match["last_matched"] = query_ip_matches.item[4] + + var/ban = newdbcache[query_ip_matches.item[1]] + if (!ban) + continue + var/IPs = ban["IP"] + if (!IPs) + IPs = ban["IP"] = list() + IPs[query_ip_matches.item[2]] = match + + dbcache = newdbcache + dbcacheexpire = world.time+STICKYBAN_DB_CACHE_TIME + + qdel(query_stickybans) + qdel(query_ckey_matches) + qdel(query_cid_matches) + qdel(query_ip_matches) + + +/datum/controller/subsystem/stickyban/proc/import_raw_stickyban_to_db(ckey, list/ban) + . = FALSE + if (!ban["admin"]) + ban["admin"] = "LEGACY" + if (!ban["message"]) + ban["message"] = "Evasion" + + // TODO: USE NEW DB IMPLEMENTATION + var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery( + "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES ([ckey], [ban["message"]], ban["admin"]))" + ) + + if (query_create_stickyban.warn_execute()) + qdel(query_create_stickyban) + return + qdel(query_create_stickyban) + + // var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery( + // "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES (:ckey, :message, :admin)", + // list("ckey" = ckey, "message" = ban["message"], "admin" = ban["admin"]) + // ) + // if (!query_create_stickyban.warn_execute()) + // qdel(query_create_stickyban) + // return + // qdel(query_create_stickyban) + + var/list/sqlckeys = list() + var/list/sqlcids = list() + var/list/sqlips = list() + + if (ban["keys"]) + var/list/keys = splittext(ban["keys"], ",") + for (var/key in keys) + var/list/sqlckey = list() + sqlckey["stickyban"] = ckey + sqlckey["matched_ckey"] = ckey(key) + sqlckey["exempt"] = FALSE + sqlckeys[++sqlckeys.len] = sqlckey + + if (ban["whitelist"]) + var/list/keys = splittext(ban["whitelist"], ",") + for (var/key in keys) + var/list/sqlckey = list() + sqlckey["stickyban"] = ckey + sqlckey["matched_ckey"] = ckey(key) + sqlckey["exempt"] = TRUE + sqlckeys[++sqlckeys.len] = sqlckey + + if (ban["computer_id"]) + var/list/cids = splittext(ban["computer_id"], ",") + for (var/cid in cids) + var/list/sqlcid = list() + sqlcid["stickyban"] = ckey + sqlcid["matched_cid"] = cid + sqlcids[++sqlcids.len] = sqlcid + + if (ban["IP"]) + var/list/ips = splittext(ban["IP"], ",") + for (var/ip in ips) + var/list/sqlip = list() + sqlip["stickyban"] = ckey + sqlip["matched_ip"] = ip + sqlips[++sqlips.len] = sqlip + + if (length(sqlckeys)) + SSdbcore.MassInsert(format_table_name("stickyban_matched_ckey"), sqlckeys, ignore_errors = TRUE) + + if (length(sqlcids)) + SSdbcore.MassInsert(format_table_name("stickyban_matched_cid"), sqlcids, ignore_errors = TRUE) + + if (length(sqlips)) + SSdbcore.MassInsert(format_table_name("stickyban_matched_ip"), sqlips, ignore_errors = TRUE) + + + return TRUE diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 4ddb92ab19..63facade2e 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -226,6 +226,14 @@ key_cache[key] = 0 return . +/proc/restore_stickybans() + for (var/banned_ckey in GLOB.stickybanadmintexts) + world.SetConfig("ban", banned_ckey, GLOB.stickybanadmintexts[banned_ckey]) + GLOB.stickybanadminexemptions = list() + GLOB.stickybanadmintexts = list() + if (GLOB.stickbanadminexemptiontimerid) + deltimer(GLOB.stickbanadminexemptiontimerid) + GLOB.stickbanadminexemptiontimerid = null #undef STICKYBAN_MAX_MATCHES #undef STICKYBAN_MAX_EXISTING_USER_MATCHES diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm index df6a9b89ad..ef0bfe8e70 100644 --- a/code/modules/admin/stickyban.dm +++ b/code/modules/admin/stickyban.dm @@ -7,7 +7,7 @@ if ("add") var/list/ban = list() var/ckey - ban["admin"] = usr.key + ban["admin"] = usr.ckey ban["type"] = list("sticky") ban["reason"] = "(InGameBan)([usr.key])" //this will be displayed in dd only @@ -21,7 +21,8 @@ ban["ckey"] = ckey if (get_stickyban_from_ckey(ckey)) - to_chat(usr, "Error: Can not add a stickyban: User already has a current sticky ban") + to_chat(usr, "Error: Can not add a stickyban: User already has a current sticky ban", confidential = TRUE) + return if (data["reason"]) ban["message"] = data["reason"] @@ -31,7 +32,26 @@ return ban["message"] = "[reason]" + if(SSdbcore.Connect()) // todo: second wave + // var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery({" + // INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) + // VALUES (:ckey, :message, :banning_admin) + // "}, list("ckey" = ckey, "message" = ban["message"], "banning_admin" = usr.ckey)) + var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) + VALUES ([ckey], [ban["message"]], [usr.ckey]) + "}) + if (query_create_stickyban.warn_execute()) + ban["fromdb"] = TRUE + qdel(query_create_stickyban) + world.SetConfig("ban",ckey,list2stickyban(ban)) + ban = stickyban2list(list2stickyban(ban)) + ban["matches_this_round"] = list() + ban["existing_user_matches_this_round"] = list() + ban["admin_matches_this_round"] = list() + ban["pending_matches_this_round"] = list() + SSstickyban.cache[ckey] = ban log_admin_private("[key_name(usr)] has stickybanned [ckey].\nReason: [ban["message"]]") message_admins("[key_name_admin(usr)] has stickybanned [ckey].\nReason: [ban["message"]]") @@ -43,14 +63,29 @@ var/ban = get_stickyban_from_ckey(ckey) if (!ban) - to_chat(usr, "Error: No sticky ban for [ckey] found!") + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) return if (alert("Are you sure you want to remove the sticky ban on [ckey]?","Are you sure","Yes","No") == "No") return if (!get_stickyban_from_ckey(ckey)) - to_chat(usr, "Error: The ban disappeared.") + to_chat(usr, "Error: The ban disappeared.", confidential = TRUE) return world.SetConfig("ban",ckey, null) + SSstickyban.cache -= ckey + + if (SSdbcore.Connect()) + // SSdbcore.QuerySelect(list( + // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = :ckey", list("ckey" = ckey)), + // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey", list("ckey" = ckey)), + // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = :ckey", list("ckey" = ckey)), + // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = :ckey", list("ckey" = ckey)) + // ), warn = TRUE, qdel = TRUE) + SSdbcore.QuerySelect(list( + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = [ckey]"), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = [ckey]"), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = [ckey]"), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = [ckey]") + ), warn = TRUE, qdel = TRUE) log_admin_private("[key_name(usr)] removed [ckey]'s stickyban") message_admins("[key_name_admin(usr)] removed [ckey]'s stickyban") @@ -64,42 +99,45 @@ var/alt = ckey(data["alt"]) var/ban = get_stickyban_from_ckey(ckey) if (!ban) - to_chat(usr, "Error: No sticky ban for [ckey] found!") + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) return - var/found = 0 - //we have to do it this way because byond keeps the case in its sticky ban matches WHY!!! - for (var/key in ban["keys"]) - if (ckey(key) == alt) - found = 1 - break - - if (!found) - to_chat(usr, "Error: [alt] is not linked to [ckey]'s sticky ban!") + var/key = LAZYACCESS(ban["keys"], alt) + if (!key) + to_chat(usr, "Error: [alt] is not linked to [ckey]'s sticky ban!", confidential = TRUE) return - if (alert("Are you sure you want to disassociate [alt] from [ckey]'s sticky ban? \nNote: Nothing stops byond from re-linking them","Are you sure","Yes","No") == "No") + if (alert("Are you sure you want to disassociate [alt] from [ckey]'s sticky ban? \nNote: Nothing stops byond from re-linking them, Use \[E] to exempt them","Are you sure","Yes","No") == "No") return //we have to do this again incase something changes ban = get_stickyban_from_ckey(ckey) if (!ban) - to_chat(usr, "Error: The ban disappeared.") + to_chat(usr, "Error: The ban disappeared.", confidential = TRUE) return - found = 0 - for (var/key in ban["keys"]) - if (ckey(key) == alt) - ban["keys"] -= key - found = 1 - break + key = LAZYACCESS(ban["keys"], alt) - if (!found) - to_chat(usr, "Error: [alt] link to [ckey]'s sticky ban disappeared.") + if (!key) + to_chat(usr, "Error: [alt] link to [ckey]'s sticky ban disappeared.", confidential = TRUE) return + LAZYREMOVE(ban["keys"], alt) world.SetConfig("ban",ckey,list2stickyban(ban)) + SSstickyban.cache[ckey] = ban + + if (SSdbcore.Connect()) + // var/datum/db_query/query_remove_stickyban_alt = SSdbcore.NewQuery( + // "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey AND matched_ckey = :alt", + // list("ckey" = ckey, "alt" = alt) + // ) + var/datum/DBQuery/query_remove_stickyban_alt = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = [ckey] AND matched_ckey = [alt]" + ) + query_remove_stickyban_alt.warn_execute() + qdel(query_remove_stickyban_alt) + log_admin_private("[key_name(usr)] has disassociated [alt] from [ckey]'s sticky ban") message_admins("[key_name_admin(usr)] has disassociated [alt] from [ckey]'s sticky ban") @@ -109,7 +147,7 @@ var/ckey = data["ckey"] var/ban = get_stickyban_from_ckey(ckey) if (!ban) - to_chat(usr, "Error: No sticky ban for [ckey] found!") + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) return var/oldreason = ban["message"] var/reason = input(usr,"Reason","Reason","[ban["message"]]") as text|null @@ -118,28 +156,203 @@ //we have to do this again incase something changed while we waited for input ban = get_stickyban_from_ckey(ckey) if (!ban) - to_chat(usr, "Error: The ban disappeared.") + to_chat(usr, "Error: The ban disappeared.", confidential = TRUE) return ban["message"] = "[reason]" world.SetConfig("ban",ckey,list2stickyban(ban)) + SSstickyban.cache[ckey] = ban + + if (SSdbcore.Connect()) + // var/datum/db_query/query_edit_stickyban = SSdbcore.NewQuery( + // "UPDATE [format_table_name("stickyban")] SET reason = :reason WHERE ckey = :ckey", + // list("reason" = reason, "ckey" = ckey) + // ) + var/datum/DBQuery/query_edit_stickyban = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban")] SET reason = [reason] WHERE ckey = [ckey]" + ) + query_edit_stickyban.warn_execute() + qdel(query_edit_stickyban) + log_admin_private("[key_name(usr)] has edited [ckey]'s sticky ban reason from [oldreason] to [reason]") message_admins("[key_name_admin(usr)] has edited [ckey]'s sticky ban reason from [oldreason] to [reason]") + if ("exempt") + if (!data["ckey"]) + return + var/ckey = data["ckey"] + if (!data["alt"]) + return + var/alt = ckey(data["alt"]) + var/ban = get_stickyban_from_ckey(ckey) + if (!ban) + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) + return + + var/key = LAZYACCESS(ban["keys"], alt) + if (!key) + to_chat(usr, "Error: [alt] is not linked to [ckey]'s sticky ban!", confidential = TRUE) + return + + if (alert("Are you sure you want to exempt [alt] from [ckey]'s sticky ban?","Are you sure","Yes","No") == "No") + return + + //we have to do this again incase something changes + ban = get_stickyban_from_ckey(ckey) + if (!ban) + to_chat(usr, "Error: The ban disappeared.", confidential = TRUE) + return + + key = LAZYACCESS(ban["keys"], alt) + + if (!key) + to_chat(usr, "Error: [alt]'s link to [ckey]'s sticky ban disappeared.", confidential = TRUE) + return + LAZYREMOVE(ban["keys"], alt) + key["exempt"] = TRUE + LAZYSET(ban["whitelist"], alt, key) + + world.SetConfig("ban",ckey,list2stickyban(ban)) + + SSstickyban.cache[ckey] = ban + + if (SSdbcore.Connect()) + // var/datum/db_query/query_exempt_stickyban_alt = SSdbcore.NewQuery( + // "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = :ckey AND matched_ckey = :alt", + // list("ckey" = ckey, "alt" = alt) + // ) + var/datum/DBQuery/query_exempt_stickyban_alt = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = [ckey] AND matched_ckey = [alt]" + ) + query_exempt_stickyban_alt.warn_execute() + qdel(query_exempt_stickyban_alt) + + log_admin_private("[key_name(usr)] has exempted [alt] from [ckey]'s sticky ban") + message_admins("[key_name_admin(usr)] has exempted [alt] from [ckey]'s sticky ban") + + if ("unexempt") + if (!data["ckey"]) + return + var/ckey = data["ckey"] + if (!data["alt"]) + return + var/alt = ckey(data["alt"]) + var/ban = get_stickyban_from_ckey(ckey) + if (!ban) + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) + return + + var/key = LAZYACCESS(ban["whitelist"], alt) + if (!key) + to_chat(usr, "Error: [alt] is not exempt from [ckey]'s sticky ban!", confidential = TRUE) + return + + if (alert("Are you sure you want to unexempt [alt] from [ckey]'s sticky ban?","Are you sure","Yes","No") == "No") + return + + //we have to do this again incase something changes + ban = get_stickyban_from_ckey(ckey) + if (!ban) + to_chat(usr, "Error: The ban disappeared.", confidential = TRUE) + return + + key = LAZYACCESS(ban["whitelist"], alt) + if (!key) + to_chat(usr, "Error: [alt]'s exemption from [ckey]'s sticky ban disappeared.", confidential = TRUE) + return + + LAZYREMOVE(ban["whitelist"], alt) + key["exempt"] = FALSE + LAZYSET(ban["keys"], alt, key) + + world.SetConfig("ban",ckey,list2stickyban(ban)) + + SSstickyban.cache[ckey] = ban + + if (SSdbcore.Connect()) + // var/datum/db_query/query_unexempt_stickyban_alt = SSdbcore.NewQuery( + // "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = :ckey AND matched_ckey = :alt", + // list("ckey" = ckey, "alt" = alt) + // ) + var/datum/DBQuery/query_unexempt_stickyban_alt = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = [ckey] AND matched_ckey = [alt]" + ) + query_unexempt_stickyban_alt.warn_execute() + qdel(query_unexempt_stickyban_alt) + + log_admin_private("[key_name(usr)] has unexempted [alt] from [ckey]'s sticky ban") + message_admins("[key_name_admin(usr)] has unexempted [alt] from [ckey]'s sticky ban") + + if ("timeout") + if (!data["ckey"]) + return + if (!SSdbcore.Connect()) + to_chat(usr, "No database connection!", confidential = TRUE) + return + + var/ckey = data["ckey"] + + if (alert("Are you sure you want to put [ckey]'s stickyban on timeout until next round (or removed)?","Are you sure","Yes","No") == "No") + return + var/ban = get_stickyban_from_ckey(ckey) + if (!ban) + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) + return + + ban["timeout"] = TRUE + + world.SetConfig("ban", ckey, null) + + var/cachedban = SSstickyban.cache[ckey] + if (cachedban) + cachedban["timeout"] = TRUE + + log_admin_private("[key_name(usr)] has put [ckey]'s sticky ban on timeout.") + message_admins("[key_name_admin(usr)] has put [ckey]'s sticky ban on timeout.") + + if ("untimeout") + if (!data["ckey"]) + return + if (!SSdbcore.Connect()) + to_chat(usr, "No database connection!", confidential = TRUE) + return + var/ckey = data["ckey"] + + if (alert("Are you sure you want to lift the timeout on [ckey]'s stickyban?","Are you sure","Yes","No") == "No") + return + + var/ban = get_stickyban_from_ckey(ckey) + var/cachedban = SSstickyban.cache[ckey] + if (cachedban) + cachedban["timeout"] = FALSE + if (!ban) + if (!cachedban) + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) + return + ban = cachedban + + ban["timeout"] = FALSE + + world.SetConfig("ban",ckey,list2stickyban(ban)) + + log_admin_private("[key_name(usr)] has taken [ckey]'s sticky ban off of timeout.") + message_admins("[key_name_admin(usr)] has taken [ckey]'s sticky ban off of timeout.") + + if ("revert") if (!data["ckey"]) return var/ckey = data["ckey"] - if (alert("Are you sure you want to revert the sticky ban on [ckey] to its state at round start?","Are you sure","Yes","No") == "No") + if (alert("Are you sure you want to revert the sticky ban on [ckey] to its state at round start (or last edit)?","Are you sure","Yes","No") == "No") return var/ban = get_stickyban_from_ckey(ckey) if (!ban) - to_chat(usr, "Error: No sticky ban for [ckey] found!") + to_chat(usr, "Error: No sticky ban for [ckey] found!", confidential = TRUE) return var/cached_ban = SSstickyban.cache[ckey] if (!cached_ban) - to_chat(usr, "Error: No cached sticky ban for [ckey] found!") + to_chat(usr, "Error: No cached sticky ban for [ckey] found!", confidential = TRUE) world.SetConfig("ban",ckey,null) log_admin_private("[key_name(usr)] has reverted [ckey]'s sticky ban to its state at round start.") @@ -150,14 +363,22 @@ world.SetConfig("ban",ckey,list2stickyban(cached_ban)) -/datum/admins/proc/stickyban_gethtml(ckey, ban) - . = {" +/datum/admins/proc/stickyban_gethtml(ckey) + var/ban = get_stickyban_from_ckey(ckey) + if (!ban) + return + var/timeout + if (SSdbcore.Connect()) + timeout = "\[[(ban["timeout"] ? "untimeout" : "timeout" )]\]" + else + timeout = "\[revert\]" + . = list({" \[-\] - \[revert\] + [timeout] [ckey]
" [ban["message"]] \[Edit\]
- "} + "}) if (ban["admin"]) . += "[ban["admin"]]
" else @@ -166,19 +387,24 @@ for (var/key in ban["keys"]) if (ckey(key) == ckey) continue - . += "
  • \[-\][key]
  • " + . += "
  • \[-\][key]\[E\]
  • " + + for (var/key in ban["whitelist"]) + if (ckey(key) == ckey) + continue + . += "
  • \[-\][key]\[UE\]
  • " + . += "\n" /datum/admins/proc/stickyban_show() if(!check_rights(R_BAN)) return - var/list/bans = sortList(world.GetConfig("ban")) - var/banhtml = "" + var/list/bans = sticky_banned_ckeys() + var/list/banhtml = list() for(var/key in bans) var/ckey = ckey(key) - var/ban = stickyban2list(world.GetConfig("ban",key)) banhtml += "

    \n" - banhtml += stickyban_gethtml(ckey,ban) + banhtml += stickyban_gethtml(ckey) var/html = {" @@ -186,22 +412,49 @@

    All Sticky Bans:

    \[+\]
    - [banhtml] + [banhtml.Join("")] "} usr << browse(html,"window=stickybans;size=700x400") -/proc/get_stickyban_from_ckey(var/ckey) +/proc/sticky_banned_ckeys() + if (SSdbcore.Connect() || length(SSstickyban.dbcache)) + if (SSstickyban.dbcacheexpire < world.time) + SSstickyban.Populatedbcache() + if (SSstickyban.dbcacheexpire) + return SSstickyban.dbcache.Copy() + + return sortList(world.GetConfig("ban")) + + +/proc/get_stickyban_from_ckey(ckey) + . = list() if (!ckey) return null - ckey = ckey(ckey) - . = null - for (var/key in world.GetConfig("ban")) - if (ckey(key) == ckey) - . = stickyban2list(world.GetConfig("ban",key)) - break + if (SSdbcore.Connect() || length(SSstickyban.dbcache)) + if (SSstickyban.dbcacheexpire < world.time) + SSstickyban.Populatedbcache() + if (SSstickyban.dbcacheexpire) + . = SSstickyban.dbcache[ckey] + //reset the cache incase its a newer ban (but only if we didn't update the cache recently) + if (!. && SSstickyban.dbcacheexpire != world.time+STICKYBAN_DB_CACHE_TIME) + SSstickyban.dbcacheexpire = 1 + SSstickyban.Populatedbcache() + . = SSstickyban.dbcache[ckey] + if (.) + var/list/cachedban = SSstickyban.cache["[ckey]"] + if (cachedban) + .["timeout"] = cachedban["timeout"] -/proc/stickyban2list(var/ban) + .["fromdb"] = TRUE + return + + . = stickyban2list(world.GetConfig("ban", ckey)) || stickyban2list(world.GetConfig("ban", ckey(ckey))) || list() + + if (!length(.)) + return null + +/proc/stickyban2list(ban, strictdb = TRUE) if (!ban) return null . = params2list(ban) @@ -212,30 +465,40 @@ var/ckey = ckey(key) ckeys[ckey] = ckey //to make searching faster. .["keys"] = ckeys + if (.["whitelist"]) + var/keys = splittext(.["whitelist"], ",") + var/ckeys = list() + for (var/key in keys) + var/ckey = ckey(key) + ckeys[ckey] = ckey //to make searching faster. + .["whitelist"] = ckeys .["type"] = splittext(.["type"], ",") .["IP"] = splittext(.["IP"], ",") .["computer_id"] = splittext(.["computer_id"], ",") + . -= "fromdb" -/proc/list2stickyban(var/list/ban) +/proc/list2stickyban(list/ban) if (!ban || !islist(ban)) return null . = ban.Copy() if (.["keys"]) .["keys"] = jointext(.["keys"], ",") + if (.["IP"]) + .["IP"] = jointext(.["IP"], ",") + if (.["computer_id"]) + .["computer_id"] = jointext(.["computer_id"], ",") + if (.["whitelist"]) + .["whitelist"] = jointext(.["whitelist"], ",") if (.["type"]) .["type"] = jointext(.["type"], ",") - //internal tracking only, shouldn't be stored + . -= "reverting" + . -= "matches_this_round" . -= "existing_user_matches_this_round" . -= "admin_matches_this_round" - . -= "matches_this_round" - . -= "reverting" + . -= "pending_matches_this_round" - //storing these can sometimes cause sticky bans to start matching everybody - // and isn't even needed for sticky ban matching, as the hub tracks these separately - . -= "IP" - . -= "computer_id" . = list2params(.) diff --git a/tgstation.dme b/tgstation.dme index 99b1799b08..31a374ddbc 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -202,6 +202,7 @@ #include "code\__HELPERS\sorts\InsertSort.dm" #include "code\__HELPERS\sorts\MergeSort.dm" #include "code\__HELPERS\sorts\TimSort.dm" +#include "code\_globalvars\admin.dm" #include "code\_globalvars\bitfields.dm" #include "code\_globalvars\configuration.dm" #include "code\_globalvars\game_modes.dm"