diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt index 24ad3f1a659..10b4a4178ad 100644 --- a/SQL/database_changelog.txt +++ b/SQL/database_changelog.txt @@ -2,14 +2,49 @@ Any time you make a change to the schema files, remember to increment the databa The latest database version is 4.7; The query to update the schema revision table is: -INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 0); +INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 1); or -INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 0); +INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 1); In any query remember to add a prefix to the table names if you use one. ---------------------------------------------------- +Version 5.1, 23 Dec 2018, by MrStonedOne +Added four tables to enable storing of stickybans in the database since byond can lose them, and to enable disabling stickybans for a round without depending on a crash free round. Existing stickybans are automagically imported to the tables. + +CREATE TABLE `stickyban` ( + `ckey` VARCHAR(32) NOT NULL, + `reason` VARCHAR(2048) NOT NULL, + `banning_admin` VARCHAR(32) NOT NULL, + `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB; + +CREATE TABLE `stickyban_matched_ckey` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ckey` VARCHAR(32) NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `exempt` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`stickyban`, `matched_ckey`) +) ENGINE=InnoDB; + +CREATE TABLE `stickyban_matched_ip` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ip` INT UNSIGNED NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_ip`) +) ENGINE=InnoDB; + +CREATE TABLE `stickyban_matched_cid` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_cid` INT UNSIGNED NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_cid`) +) ENGINE=InnoDB; + +---------------------------------------------------- + Version 5.0, 28 October 2018, by Jordie0608 Modified ban table to remove the need for the `bantype` column, a python script is used to migrate data to this new format. @@ -88,8 +123,7 @@ Added table `role_time_log` and triggers `role_timeTlogupdate`, `role_timeTlogin CREATE TABLE `role_time_log` ( `id` BIGINT NOT NULL AUTO_INCREMENT , `ckey` VARCHAR(32) NOT NULL , `job` VARCHAR(128) NOT NULL , `delta` INT NOT NULL , `datetime` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , PRIMARY KEY (`id`), INDEX (`ckey`), INDEX (`job`), INDEX (`datetime`)) ENGINE = InnoDB; -DELIMITER -$$ +DELIMITER $$ CREATE TRIGGER `role_timeTlogupdate` AFTER UPDATE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes); END $$ @@ -99,7 +133,7 @@ $$ CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); END $$ - +DELIMITER ; ---------------------------------------------------- Version 4.2, 17 April 2018, by Jordie0608 diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 6127c5b37de..7ef6ba39354 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -462,6 +462,54 @@ $$ CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); END $$ +DELIMITER ; + +-- +-- Table structure for table `SS13_stickyban` +-- +DROP TABLE IF EXISTS `SS13_stickyban`; +CREATE TABLE `SS13_stickyban` ( + `ckey` VARCHAR(32) NOT NULL, + `reason` VARCHAR(2048) NOT NULL, + `banning_admin` VARCHAR(32) NOT NULL, + `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ss13_stickyban_matched_ckey` +-- +DROP TABLE IF EXISTS `ss13_stickyban_matched_ckey`; +CREATE TABLE `ss13_stickyban_matched_ckey` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ckey` VARCHAR(32) NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `exempt` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`stickyban`, `matched_ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ss13_stickyban_matched_ip` +-- +DROP TABLE IF EXISTS `ss13_stickyban_matched_ip`; +CREATE TABLE `ss13_stickyban_matched_ip` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ip` INT UNSIGNED NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_ip`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ss13_stickyban_matched_cid` +-- +DROP TABLE IF EXISTS `ss13_stickyban_matched_cid`; +CREATE TABLE `ss13_stickyban_matched_cid` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_cid` INT UNSIGNED NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_cid`) +) ENGINE=InnoDB; + /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index e17ef712472..6b145a30a64 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -462,6 +462,55 @@ $$ CREATE TRIGGER `SS13_role_timeTlogdelete` AFTER DELETE ON `SS13_role_time` FOR EACH ROW BEGIN INSERT into SS13_role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes); END $$ +DELIMITER ; + +-- +-- Table structure for table `SS13_stickyban` +-- +DROP TABLE IF EXISTS `SS13_stickyban`; +CREATE TABLE `SS13_stickyban` ( + `ckey` VARCHAR(32) NOT NULL, + `reason` VARCHAR(2048) NOT NULL, + `banning_admin` VARCHAR(32) NOT NULL, + `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ss13_stickyban_matched_ckey` +-- +DROP TABLE IF EXISTS `ss13_stickyban_matched_ckey`; +CREATE TABLE `ss13_stickyban_matched_ckey` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ckey` VARCHAR(32) NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `exempt` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`stickyban`, `matched_ckey`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ss13_stickyban_matched_ip` +-- +DROP TABLE IF EXISTS `ss13_stickyban_matched_ip`; +CREATE TABLE `ss13_stickyban_matched_ip` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_ip` INT UNSIGNED NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_ip`) +) ENGINE=InnoDB; + +-- +-- Table structure for table `ss13_stickyban_matched_cid` +-- +DROP TABLE IF EXISTS `ss13_stickyban_matched_cid`; +CREATE TABLE `ss13_stickyban_matched_cid` ( + `stickyban` VARCHAR(32) NOT NULL, + `matched_cid` INT UNSIGNED NOT NULL, + `first_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`stickyban`, `matched_cid`) +) ENGINE=InnoDB; + + /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index b84f79b5c72..044af51c940 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -81,3 +81,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 SECONDS \ No newline at end of file diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 0fcab54f0f8..3fba67e2b26 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -1,7 +1,8 @@ //Update this whenever the db schema changes //make sure you add an update to the schema_version stable in the db changelog #define DB_MAJOR_VERSION 5 -#define DB_MINOR_VERSION 0 +#define DB_MINOR_VERSION 1 + //Timing subsystem //Don't run if there is an identical unique timer active diff --git a/code/controllers/subsystem/stickyban.dm b/code/controllers/subsystem/stickyban.dm index 189efa99fed..9b463b219f6 100644 --- a/code/controllers/subsystem/stickyban.dm +++ b/code/controllers/subsystem/stickyban.dm @@ -1,34 +1,82 @@ 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") + var/list/bannedkeys = sticky_banned_ckeys() //sanitize the sticky ban list 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])) + + if (!CONFIG_GET(flag/ban_legacy_system) && (SSdbcore.Connect() || length(SSstickyban.dbcache))) + for (var/oldban in (world.GetConfig("ban") - bannedkeys)) + world.SetConfig("ban", oldban, null) //remove bans that no longer exist. 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/DBQuery/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDERED BY ckey") + if (!query_stickybans.warn_execute()) + return + + var/datum/DBQuery/query_stickyban_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, exempt FROM [format_table_name("stickyban_matrched_ckey")]") + if (!query_stickyban_matches.warn_execute()) + return + query_stickyban_matches.SetConversion(4, SSdbcore.NUMBER_CONV) //read exempt as a number, not a string + + while (query_stickybans.NextRow()) + var/list/ban = list() + + ban["ckey"] = query_stickybans.item[1] + ban["reason"] = query_stickybans.item[2] + ban["banning_admin"] = query_stickybans.item[3] + ban["datetime"] = query_stickybans.item[4] + + newdbcache["[query_stickybans.item[1]]"] = ban + + + while (query_stickyban_matches.NextRow()) + var/list/match = list() + + match["stickyban"] = query_stickyban_matches.item[1] + match["matched_ckey"] = query_stickyban_matches.item[2] + match["first_matched"] = query_stickyban_matches.item[3] + match["exempt"] = query_stickyban_matches.item[4] + + var/ban = newdbcache[match["stickyban"]] + if (!ban) + continue + var/keys = ban["keys"] + if (!keys) + keys = ban["keys"] = list() + keys[match["matched_ckey"]] = match + + dbcache = newdbcache + dbcacheexpire = world.time+STICKYBAN_DB_CACHE_TIME diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 9840bf72fc9..19a3fbc3249 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -2,12 +2,12 @@ //How many new ckey matches before we revert the stickyban to it's roundstart state //These are exclusive, so once it goes over one of these numbers, it reverts the ban -#define STICKYBAN_MAX_MATCHES 20 -#define STICKYBAN_MAX_EXISTING_USER_MATCHES 5 //ie, users who were connected before the ban triggered -#define STICKYBAN_MAX_ADMIN_MATCHES 2 +#define STICKYBAN_MAX_MATCHES 15 +#define STICKYBAN_MAX_EXISTING_USER_MATCHES 3 //ie, users who were connected before the ban triggered +#define STICKYBAN_MAX_ADMIN_MATCHES 1 -/world/IsBanned(key,address,computer_id,type,real_bans_only=FALSE) - if (!key || !address || !computer_id) +/world/IsBanned(key, address, computer_id, type, real_bans_only=FALSE) + if (!key || (!real_bans_only && (!address || !computer_id))) if(real_bans_only) return FALSE log_access("Failed Login (invalid data): [key] [address]-[computer_id]") @@ -82,10 +82,32 @@ var/newmatch = FALSE var/client/C = GLOB.directory[ckey] - var/cachedban = SSstickyban.cache[bannedckey] + var/list/cachedban = SSstickyban.cache[bannedckey] + if (!CONFIG_GET(flag/ban_legacy_system) && (SSdbcore.Connect() || length(SSstickyban.dbcache))) + ban = get_stickyban_from_ckey(bannedckey) + var/list/bancache = list() + while (ban["ckey"] && ban["keys"] && ban["keys"][ckey] && ban["keys"][ckey]["exempt"]) + if (C || SSstickyban.confirmed_exempt[ckey]) //modifing/re-adding a stickyban makes it re-match everybody + return + bancache[ban["ckey"]] = world.GetConfig("ban", ban["ckey"]) + //Hacky way to ensure somebody exempt from one stickyban doesn't get exempt from all stickybans + world.SetConfig("ban", bannedckey, null) + var/list/newban = ..() + if (!newban || newban["ckey"] == ban["ckey"]) + SSstickyban.confirmed_exempt[ckey] = TRUE + return + if (!newban["ckey"]) + ban = newban + break + ban = get_stickyban_from_ckey(ban["ckey"]) + + spawn() + for(var/bancacheckey in bancache) + world.SetConfig("ban", bancacheckey, bancache[bancacheckey]) //rogue ban in the process of being reverted. - if (cachedban && cachedban["reverting"]) + if (cachedban && cachedban["reverting"] || cachedban["timeout"]) + world.SetConfig("ban", bannedckey, null) return null if (cachedban && ckey != bannedckey) @@ -98,40 +120,66 @@ if (newmatch && cachedban) var/list/newmatches = cachedban["matches_this_round"] + var/list/pendingmatches = cachedban["matches_this_round"] var/list/newmatches_connected = cachedban["existing_user_matches_this_round"] var/list/newmatches_admin = cachedban["admin_matches_this_round"] - newmatches[ckey] = ckey + pendingmatches[ckey] = ckey + if (C) newmatches_connected[ckey] = ckey + newmatches_connected = cachedban["existing_user_matches_this_round"] if (admin) newmatches_admin[ckey] = ckey + sleep(STICKYBAN_ROGUE_CHECK_TIME) + + pendingmatches -= ckey + + if (cachedban["reverting"] || cachedban["timeout"]) + return null + + newmatches[ckey] = ckey + + if (\ - newmatches.len > STICKYBAN_MAX_MATCHES || \ + newmatches.len+pendingmatches.len > STICKYBAN_MAX_MATCHES || \ newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \ newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \ ) - if (cachedban["reverting"]) - return null - cachedban["reverting"] = TRUE + + var/action + if (ban["fromdb"]) + cachedban["timeout"] = TRUE + action = "putting it on timeout for the remainder of the round" + else + cachedban["reverting"] = TRUE + action = "reverting to its roundstart state" world.SetConfig("ban", bannedckey, null) - log_game("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state") - message_admins("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state") + log_game("Stickyban on [bannedckey] detected as rogue, [action]") + message_admins("Stickyban on [bannedckey] detected as rogue, [action]") //do not convert to timer. spawn (5) world.SetConfig("ban", bannedckey, null) sleep(1) world.SetConfig("ban", bannedckey, null) - cachedban["matches_this_round"] = list() - cachedban["existing_user_matches_this_round"] = list() - cachedban["admin_matches_this_round"] = list() - cachedban -= "reverting" - world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) + if (!ban["fromdb"]) + cachedban = cachedban.Copy() //so old references to the list still see the ban as reverting + cachedban["matches_this_round"] = list() + cachedban["existing_user_matches_this_round"] = list() + cachedban["admin_matches_this_round"] = list() + cachedban -= "reverting" + SSstickyban.cache[bannedckey] = cachedban + world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) return null + if (ban["fromdb"]) + if(!CONFIG_GET(flag/ban_legacy_system) && SSdbcore.Connect()) + var/datum/DBQuery/query_add_match = SSdbcore.NewQuery("INSERT IGNORE INTO [format_table_name("stickyban_matched_ckey")] (matchedckey, stickyban) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(bannedckey)]')") + query_add_match.warn_execute() + //byond will not trigger isbanned() for "global" host bans, //ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked) //So it's safe to let admins walk thru host/sticky bans here @@ -142,7 +190,7 @@ return null if (C) //user is already connected!. - to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was reversed.") + to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was automatically reversed.") var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n" . = list("reason" = "Stickyban", "desc" = desc) diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm index df6a9b89ad4..0cfc9a7ff3e 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 @@ -22,6 +22,7 @@ if (get_stickyban_from_ckey(ckey)) to_chat(usr, "Error: Can not add a stickyban: User already has a current sticky ban") + return if (data["reason"]) ban["message"] = data["reason"] @@ -32,6 +33,11 @@ ban["message"] = "[reason]" world.SetConfig("ban",ckey,list2stickyban(ban)) + SSstickyban.cache[ckey] = ban + + if(!CONFIG_GET(flag/ban_legacy_system) && SSdbcore.Connect()) + var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery("INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(ban["message"])]', '[sanitizeSQL(usr.ckey)]')") + query_create_stickyban.warn_execute() log_admin_private("[key_name(usr)] has stickybanned [ckey].\nReason: [ban["message"]]") message_admins("[key_name_admin(usr)] has stickybanned [ckey].\nReason: [ban["message"]]") @@ -51,6 +57,11 @@ to_chat(usr, "Error: The ban disappeared.") return world.SetConfig("ban",ckey, null) + SSstickyban.cache -= ckey + + if (!CONFIG_GET(flag/ban_legacy_system) && SSdbcore.Connect()) + var/datum/DBQuery/query_remove_stickyban = SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = '[sanitizeSQL(ckey)]'") + query_remove_stickyban.warn_execute() log_admin_private("[key_name(usr)] removed [ckey]'s stickyban") message_admins("[key_name_admin(usr)] removed [ckey]'s stickyban") @@ -67,18 +78,12 @@ to_chat(usr, "Error: No sticky ban for [ckey] found!") 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) + var/key = ban["keys"][alt] + if (!key) to_chat(usr, "Error: [alt] is not linked to [ckey]'s sticky ban!") 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 @@ -87,19 +92,20 @@ to_chat(usr, "Error: The ban disappeared.") return - found = 0 - for (var/key in ban["keys"]) - if (ckey(key) == alt) - ban["keys"] -= key - found = 1 - break - - if (!found) + key = ban["keys"][alt] + if (!key) to_chat(usr, "Error: [alt] link to [ckey]'s sticky ban disappeared.") return + ban["keys"] -= alt world.SetConfig("ban",ckey,list2stickyban(ban)) + SSstickyban.cache[ckey] = ban + + if (!CONFIG_GET(flag/ban_legacy_system) && SSdbcore.Connect()) + var/datum/DBQuery/query_remove_stickyban_alt = SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE ckey = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(alt)]'") + query_remove_stickyban_alt.warn_execute() + 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") @@ -124,14 +130,170 @@ world.SetConfig("ban",ckey,list2stickyban(ban)) + SSstickyban.cache[ckey] = ban + + if (!CONFIG_GET(flag/ban_legacy_system) && SSdbcore.Connect()) + var/datum/DBQuery/query_edit_stickyban = SSdbcore.NewQuery("UPDATE [format_table_name("stickyban")] SET reason = '[sanitizeSQL(reason)]' WHERE ckey = '[sanitizeSQL(ckey)]'") + query_edit_stickyban.warn_execute() + 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 + if (!CONFIG_GET(flag/ban_legacy_system) || !SSdbcore.Connect()) + to_chat(usr, "No database connection!") + 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!") + return + + var/key = ban["keys"][alt] + if (!key) + to_chat(usr, "Error: [alt] is not linked to [ckey]'s sticky ban!") + 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.") + return + + key = ban["keys"][alt] + if (!key) + to_chat(usr, "Error: [alt] link to [ckey]'s sticky ban disappeared.") + return + + key["exempt"] = TRUE + + world.SetConfig("ban",ckey,list2stickyban(ban)) + + SSstickyban.cache[ckey] = ban + + + var/datum/DBQuery/query_exempt_stickyban_alt = SSdbcore.NewQuery("UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = ckey = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(alt)]'") + query_exempt_stickyban_alt.warn_execute() + + 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 + if (!CONFIG_GET(flag/ban_legacy_system) || !SSdbcore.Connect()) + to_chat(usr, "No database connection!") + 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!") + return + + var/key = ban["keys"][alt] + if (!key) + to_chat(usr, "Error: [alt] is not linked to [ckey]'s sticky ban!") + 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.") + return + + key = ban["keys"][alt] + if (!key) + to_chat(usr, "Error: [alt] link to [ckey]'s sticky ban disappeared.") + return + + key["exempt"] = FALSE + + world.SetConfig("ban",ckey,list2stickyban(ban)) + + SSstickyban.cache[ckey] = ban + + + var/datum/DBQuery/query_unexempt_stickyban_alt = SSdbcore.NewQuery("UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = ckey = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(alt)]'") + query_unexempt_stickyban_alt.warn_execute() + + 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 (CONFIG_GET(flag/ban_legacy_system) || !SSdbcore.Connect()) + to_chat(usr, "No database connection!") + 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!") + 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 (CONFIG_GET(flag/ban_legacy_system) || !SSdbcore.Connect()) + to_chat(usr, "No database connection!") + 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!") + return + ban = cachedban + + ban["timeout"] = FALSE + + world.SetConfig("ban",ckey,list2stickyban(ban)) + + 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 ("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) @@ -150,14 +312,21 @@ 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 (!CONFIG_GET(flag/ban_legacy_system) && SSdbcore.Connect()) + timeout = "\[[(ban["timeout"] ? "untimeout" : "timeout" )]\]" + . = list({" \[-\] \[revert\] + [timeout] [ckey]
" [ban["message"]] \[Edit\]
- "} + "}) if (ban["admin"]) . += "[ban["admin"]]
" else @@ -166,19 +335,21 @@ for (var/key in ban["keys"]) if (ckey(key) == ckey) continue - . += "
  • \[-\][key]
  • " + var/exempt + if (!CONFIG_GET(flag/ban_legacy_system) && SSdbcore.Connect()) + exempt = "\[[(ban["keys"][key]["exempt"] ? "UE" : "E")]\]" + . += "
  • \[-\][key][exempt]
  • " . += "\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,20 +357,43 @@

    All Sticky Bans:

    \[+\]
    - [banhtml] + [banhtml.Join("")] "} usr << browse(html,"window=stickybans;size=700x400") +/proc/sticky_banned_ckeys() + if (!CONFIG_GET(flag/ban_legacy_system) && (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(var/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 (!CONFIG_GET(flag/ban_legacy_system) && (SSdbcore.Connect() || length(SSstickyban.dbcache))) + if (SSstickyban.dbcacheexpire < world.time) + SSstickyban.Populatedbcache() + if (SSstickyban.dbcacheexpire) + . = SSstickyban.dbcache[ckey] + var/list/cachedban = SSstickyban.cache["ckey"]["timeout"] + if (cachedban) + .["timeout"] = cachedban["timeout"] + .["fromdb"] = TRUE + return + + . = stickyban2list(world.GetConfig("ban", ckey)) || stickyban2list(world.GetConfig("ban", ckey(ckey))) || list() + + if (!length(.) && ckey != ckey(ckey)) //say that 10 times fast + . = stickyban2list(world.GetConfig("ban", ckey(ckey))) + + if (!length(.)) + return null /proc/stickyban2list(var/ban) if (!ban) @@ -226,11 +420,8 @@ if (.["type"]) .["type"] = jointext(.["type"], ",") - //internal tracking only, shouldn't be stored - . -= "existing_user_matches_this_round" - . -= "admin_matches_this_round" - . -= "matches_this_round" . -= "reverting" + . -= "fromdb" //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