diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt index 1707df65dae..6209e6872f4 100644 --- a/SQL/database_changelog.txt +++ b/SQL/database_changelog.txt @@ -2,12 +2,26 @@ Any time you make a change to the schema files, remember to increment the databa The latest database version is 5.17; The query to update the schema revision table is: -INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 17); +INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 18); or -INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 17); +INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 18); In any query remember to add a prefix to the table names if you use one. +----------------------------------------------------- +Version 5.18, 1 November 2021, by Mothblocks +Added `known_alts` table for tracking who not to create suspicious logins for. + +``` +CREATE TABLE `known_alts` ( + `id` INT NOT NULL AUTO_INCREMENT, + `ckey1` VARCHAR(32) NOT NULL, + `ckey2` VARCHAR(32) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `unique_contraints` (`ckey1` , `ckey2`) +); +``` + ----------------------------------------------------- Version 5.17, 8 October 2021, by MrStonedOne + Mothblocks Changes any table that requrired a NOT NULL round ID to now accept NULL. In the BSQL past, these were handled as 0, but in the move to rust-g this behavior was lost. diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index f0a4710227e..ac913be0149 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -663,6 +663,18 @@ CREATE TABLE `admin_connections` ( UNIQUE INDEX `unique_constraints` (`ckey`, `ip`, `cid`) ) ENGINE=InnoDB; +-- +-- Table structure for table `known_alts` +-- +DROP TABLE IF EXISTS `known_alts`; +CREATE TABLE `known_alts` ( + `id` INT NOT NULL AUTO_INCREMENT, + `ckey1` VARCHAR(32) NOT NULL, + `ckey2` VARCHAR(32) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `unique_contraints` (`ckey1` , `ckey2`) +); + /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index 3307bf43b94..bcb26de7d25 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -663,6 +663,18 @@ CREATE TABLE `SS13_admin_connections` ( UNIQUE INDEX `unique_constraints` (`ckey`, `ip`, `cid`) ) ENGINE=InnoDB; +-- +-- Table structure for table `known_alts` +-- +DROP TABLE IF EXISTS `SS13_known_alts`; +CREATE TABLE `SS13_known_alts` ( + `id` INT NOT NULL AUTO_INCREMENT, + `ckey1` VARCHAR(32) NOT NULL, + `ckey2` VARCHAR(32) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `unique_contraints` (`ckey1` , `ckey2`) +); + /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index db431d519f5..97e9142600d 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -143,3 +143,6 @@ GLOBAL_VAR_INIT(ghost_role_flags, (~0)) /// for asay pings, this is the index in the return list for [/proc/check_admin_pings] that contains the message modified with underlines for the spotted names #define ADMINSAY_PING_UNDERLINE_NAME_INDEX "!underlined_names" + +/// When passed in as the duration for ban_panel, will make the ban default to permanent +#define BAN_PANEL_PERMANENT "permanent" diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 911b828a519..32a3be09510 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -20,7 +20,7 @@ * * make sure you add an update to the schema_version stable in the db changelog */ -#define DB_MINOR_VERSION 17 +#define DB_MINOR_VERSION 18 //! ## Timing subsystem diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 2faa38ed62e..cf006dfe0b0 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -75,6 +75,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/admins/proc/open_borgopanel, /datum/admins/proc/view_all_circuits, /datum/admins/proc/view_all_sdql_spells, + /datum/admins/proc/known_alts_panel, ) GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel)) GLOBAL_PROTECT(admin_verbs_ban) diff --git a/code/modules/admin/known_alts.dm b/code/modules/admin/known_alts.dm new file mode 100644 index 00000000000..8ea595e1aca --- /dev/null +++ b/code/modules/admin/known_alts.dm @@ -0,0 +1,192 @@ +GLOBAL_DATUM_INIT(known_alts, /datum/known_alts, new) + +/datum/known_alts + var/list/cached_known_alts + COOLDOWN_DECLARE(cache_cooldown) + +/datum/known_alts/Topic(href, list/href_list) + if (!check_rights(R_ADMIN)) + return + + if (!SSdbcore.Connect()) + to_chat(usr, span_warning("Couldn't connect to the database.")) + return + + var/datum/admins/holder = usr.client?.holder + if (isnull(holder)) + return + + if (!holder.CheckAdminHref(href, href_list)) + return + + switch (href_list["action"]) + if ("add") + var/ckey1 = input(usr, "Put in the name of the main ckey") as null|text + if (!ckey1) + return + + var/ckey2 = input(usr, "Put in the name of their alt") as null|text + if (!ckey2) + return + + ckey1 = ckey(ckey1) + ckey2 = ckey(ckey2) + + var/datum/db_query/query_already_exists = SSdbcore.NewQuery({" + SELECT id FROM [format_table_name("known_alts")] + WHERE (ckey1 = :ckey1 AND ckey2 = :ckey2) + OR (ckey1 = :ckey2 AND ckey2 = :ckey1) + "}, list( + "ckey1" = ckey1, + "ckey2" = ckey2, + )) + + query_already_exists.warn_execute() + + if (query_already_exists.last_error) + qdel(query_already_exists) + return + + var/already_exists_row = query_already_exists.NextRow() + qdel(query_already_exists) + + if (already_exists_row) + alert(usr, "Those two are already in the list of known alts!") + return + + var/datum/db_query/query_add_known_alt = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("known_alts")] (ckey1, ckey2) + VALUES (:ckey1, :ckey2) + "}, list( + "ckey1" = ckey1, + "ckey2" = ckey2, + )) + + if (query_add_known_alt.warn_execute()) + var/message = "[key_name(usr)] has added a new known alt connection between [ckey1] and [ckey2]." + message_admins(message) + log_admin_private(message) + + cached_known_alts = null + load_known_alts() + + qdel(query_add_known_alt) + show_panel(usr.client) + + if (!is_banned_from(ckey2, "Server")) + var/ban_choice = alert("[ckey2] is not banned from the server. Do you want to open up the ban panel as well?",,"Yes", "No") + if (ban_choice == "Yes") + holder.ban_panel(ckey2, role = "Server", duration = BAN_PANEL_PERMANENT) + if ("delete") + var/id = text2num(href_list["id"]) + if (!id) + log_admin_private("[key_name(usr)] tried to delete an invalid known alt ID: [href_list["id"]]") + return + + var/datum/db_query/query_known_alt_info = SSdbcore.NewQuery({" + SELECT ckey1, ckey2 FROM [format_table_name("known_alts")] + WHERE id = :id + "}, list( + "id" = id, + )) + + if (!query_known_alt_info.warn_execute()) + qdel(query_known_alt_info) + return + + if (!query_known_alt_info.NextRow()) + alert("Couldn't find the known alt with the ID [id]") + qdel(query_known_alt_info) + return + + var/list/result = query_known_alt_info.item + qdel(query_known_alt_info) + + if (alert("Are you sure you want to delete the alt connection between [result[1]] and [result[2]]?",,"Yes", "No") != "Yes") + return + + var/datum/db_query/query_delete_known_alt = SSdbcore.NewQuery({" + DELETE FROM [format_table_name("known_alts")] + WHERE id = :id + "}, list( + "id" = id, + )) + + if (query_delete_known_alt.warn_execute()) + var/message = "[key_name(usr)] has deleted the known alt connection between [result[1]] and [result[2]]." + message_admins(message) + log_admin_private(message) + + cached_known_alts = null + load_known_alts() + + qdel(query_delete_known_alt) + show_panel(usr.client) + +/// Returns the list of known alts, will return an empty list if the DB could not be connected to. +/// This proc can block. +/datum/known_alts/proc/load_known_alts() + if (!isnull(cached_known_alts) && !COOLDOWN_FINISHED(src, cache_cooldown)) + return cached_known_alts + + if (!SSdbcore.Connect()) + return cached_known_alts || list() + + var/datum/db_query/query_known_alts = SSdbcore.NewQuery("SELECT id, ckey1, ckey2 FROM [format_table_name("known_alts")] ORDER BY id DESC") + query_known_alts.warn_execute() + + if (query_known_alts.last_error) + qdel(query_known_alts) + return cached_known_alts || list() + + cached_known_alts = list() + + while (query_known_alts.NextRow()) + cached_known_alts += list(list( + query_known_alts.item[2], + query_known_alts.item[3], + + // The ID + query_known_alts.item[1], + )) + + COOLDOWN_START(src, cache_cooldown, 10 SECONDS) + qdel(query_known_alts) + + return cached_known_alts + +/datum/known_alts/proc/show_panel(client/client) + if (!check_rights_for(client, R_ADMIN)) + return + + if (!SSdbcore.Connect()) + to_chat(usr, span_warning("Couldn't connect to the database.")) + return + + var/list/known_alts_html = list() + + for (var/known_alt in load_known_alts()) + known_alts_html += "\[-\] Delete [known_alt[1]] is an alt of [known_alt[2]]." + + var/html = {" + + Known Alts + + + +

Any two ckeys in this panel will not show in "banned connection history".

+

Sometimes players switch account, and it's customary to perma-ban the old one.

+ +

All Known Alts:

\[+\] Add
+ + [known_alts_html.Join("
")] + + "} + + client << browse(html, "window=known_alts;size=700x400") + +/datum/admins/proc/known_alts_panel() + set name = "Known Alts Panel" + set category = "Admin" + + GLOB.known_alts.show_panel(usr.client) diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index 3193d9f9796..5e89467c645 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -115,6 +115,9 @@ /datum/admins/proc/ban_panel(player_key, player_ip, player_cid, role, duration = 1440, applies_to_admins, reason, edit_id, page, admin_key) + if (duration == BAN_PANEL_PERMANENT) + duration = null + var/panel_height = 620 if(edit_id) panel_height = 240 diff --git a/code/modules/tgui_panel/telemetry.dm b/code/modules/tgui_panel/telemetry.dm index 98ba1f14b8b..7d08fc997a7 100644 --- a/code/modules/tgui_panel/telemetry.dm +++ b/code/modules/tgui_panel/telemetry.dm @@ -60,18 +60,34 @@ message_admins("[key_name(client)] was kicked for sending a huge telemetry payload") qdel(client) return + + var/list/all_known_alts = GLOB.known_alts.load_known_alts() + var/list/our_known_alts = list() + + for (var/known_alt in all_known_alts) + if (known_alt[1] == client?.ckey) + our_known_alts += known_alt[2] + else if (known_alt[2] == client?.ckey) + our_known_alts += known_alt[1] + var/list/found for(var/i in 1 to len) if(QDELETED(client)) // He got cleaned up before we were done return var/list/row = telemetry_connections[i] + // Check for a malformed history object if (!row || row.len < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"])) return + + if (row["ckey"] in our_known_alts) + continue + if (world.IsBanned(row["ckey"], row["address"], row["computer_id"], real_bans_only = TRUE)) found = row break + CHECK_TICK // This fucker has a history of playing on a banned account. if(found) diff --git a/tgstation.dme b/tgstation.dme index 70faf791cce..4022ea3263e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1665,6 +1665,7 @@ #include "code\modules\admin\holder2.dm" #include "code\modules\admin\ipintel.dm" #include "code\modules\admin\IsBanned.dm" +#include "code\modules\admin\known_alts.dm" #include "code\modules\admin\outfit_editor.dm" #include "code\modules\admin\outfit_manager.dm" #include "code\modules\admin\outfits.dm"