From b8b3d603c4c002022fd7ee9558675dd28854a338 Mon Sep 17 00:00:00 2001 From: Metis <100518708+sheepishgoat@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:21:02 -0400 Subject: [PATCH] border --- .../code/modules/client/border_control.dm | 189 ++++++++++++++++++ code/__HELPERS/_lists.dm | 23 +++ .../configuration/entries/general.dm | 2 + code/modules/admin/admin.dm | 6 + code/modules/admin/admin_verbs.dm | 3 + code/modules/client/client_procs.dm | 5 + config/config.txt | 1 + config/entries/border.txt | 5 + tgstation.dme | 1 + 9 files changed, 235 insertions(+) create mode 100644 GainStation13/code/modules/client/border_control.dm create mode 100644 config/entries/border.txt diff --git a/GainStation13/code/modules/client/border_control.dm b/GainStation13/code/modules/client/border_control.dm new file mode 100644 index 0000000000..013b50483b --- /dev/null +++ b/GainStation13/code/modules/client/border_control.dm @@ -0,0 +1,189 @@ +#define BORDER_CONTROL_DISABLED 0 +#define BORDER_CONTROL_LEARNING 1 +#define BORDER_CONTROL_ENFORCED 2 + + +GLOBAL_LIST_EMPTY(whitelistedCkeys) +GLOBAL_VAR_INIT(borderControlFile, new /savefile("data/bordercontrol.db")) +GLOBAL_VAR_INIT(whitelistLoaded, FALSE) + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_ModeToText(mode) + switch(mode) + if(BORDER_CONTROL_DISABLED) + return "Disabled" + if(BORDER_CONTROL_LEARNING) + return "Learning" + if(BORDER_CONTROL_ENFORCED) + return "Enforced" + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_IsKeyAllowedToConnect(key) + key = ckey(key) + + var/borderControlMode = CONFIG_GET(number/border_control) + + if(borderControlMode == BORDER_CONTROL_DISABLED) + return 1 + else if (borderControlMode == BORDER_CONTROL_LEARNING) + if(!BC_IsKeyWhitelisted(key)) + log_and_message_admins("[key] has joined and was added to the border whitelist.") + BC_WhitelistKey(key) + return 1 + else + return BC_IsKeyWhitelisted(key) + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_IsKeyWhitelisted(key) + key = ckey(key) + + if(!GLOB.whitelistLoaded) + BC_LoadWhitelist() + + if(LAZYISIN(GLOB.whitelistedCkeys, key)) + return 1 + else + return 0 + +////////////////////////////////////////////////////////////////////////////////// +//ADMIN_VERB_ADD(/client/proc/BC_WhitelistKeyVerb, R_ADMIN, FALSE) +///client/proc/BC_WhitelistKeyVerb() +/datum/admins/proc/BC_WhitelistKeyVerb() + + set name = "Border Control - Whitelist Key" + set category = "Admin.Border Control" + + var/key = input("CKey to Whitelist", "Whitelist Key") as null|text + + if(key) + var/confirm = alert("Add [key] to the border control whitelist?", , "Yes", "No") + if(confirm == "Yes") + log_and_message_admins("added [key] to the border whitelist.") + BC_WhitelistKey(key) + + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_WhitelistKey(key) + var/keyAsCkey = ckey(key) + + if(!GLOB.whitelistLoaded) + BC_LoadWhitelist() + + if(!keyAsCkey) + return 0 + else + if(LAZYISIN(GLOB.whitelistedCkeys,keyAsCkey)) + // Already in + return 0 + else + LAZYINITLIST(GLOB.whitelistedCkeys) + + ADD_SORTED(GLOB.whitelistedCkeys, keyAsCkey, /proc/cmp_text_asc) + + BC_SaveWhitelist() + return 1 + + + +////////////////////////////////////////////////////////////////////////////////// +//ADMIN_VERB_ADD(/client/proc/BC_RemoveKeyVerb, R_ADMIN, FALSE) +///client/proc/BC_RemoveKeyVerb() +/datum/admins/proc/BC_RemoveKeyVerb() + + set name = "Border Control - Remove Key" + set category = "Admin.Border Control" + + var/keyToRemove = input("CKey to Remove", "Remove Key") as null|anything in GLOB.whitelistedCkeys + + if(keyToRemove) + var/confirm = alert("Remove [keyToRemove] from the border control whitelist?", , "Yes", "No") + if(confirm == "Yes") + log_and_message_admins("removed [keyToRemove] from the border whitelist.") + BC_RemoveKey(keyToRemove) + + return + + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_RemoveKey(key) + key = ckey(key) + + if(!LAZYISIN(GLOB.whitelistedCkeys, key)) + return 1 + else + if(GLOB.whitelistedCkeys) + GLOB.whitelistedCkeys.Remove(key) + BC_SaveWhitelist() + return 1 + + + + +////////////////////////////////////////////////////////////////////////////////// +//ADMIN_VERB_ADD(/client/proc/BC_ToggleState, R_ADMIN, FALSE) +///client/proc/BC_ToggleState() +/datum/admins/proc/BC_ToggleState() + + set name = "Border Control - Toggle Mode" + set category = "Admin.Border Control" + set desc="Enables or disables border control" + + var/borderControlMode = CONFIG_GET(number/border_control) + + var/choice = input("New State (Current state is: [BC_ModeToText(borderControlMode)])", "Border Control State") as null|anything in list("Disabled", "Learning", "Enforced") + + switch(choice) + if("Disabled") + if(borderControlMode != BORDER_CONTROL_DISABLED) + borderControlMode = BORDER_CONTROL_DISABLED + log_and_message_admins("has disabled border control.") + if("Learning") + if(borderControlMode != BORDER_CONTROL_LEARNING) + borderControlMode = BORDER_CONTROL_LEARNING + log_and_message_admins("has set border control to learn new keys on connection!") + var/confirm = alert("Learn currently connected keys?", , "Yes", "No") + if(confirm == "Yes") + for(var/client/C in GLOB.clients) + if (BC_WhitelistKey(C.key)) + log_and_message_admins("[key_name(usr)] added [C.key] to the border whitelist by adding all current clients.") + + if("Enforced") + if(borderControlMode != BORDER_CONTROL_ENFORCED) + borderControlMode = BORDER_CONTROL_ENFORCED + log_and_message_admins("has enforced border controls. New keys can no longer join.") + + CONFIG_SET(number/border_control, borderControlMode) + + return + + +////////////////////////////////////////////////////////////////////////////////// + +/hook/startup/proc/loadBorderControlWhitelistHook() + BC_LoadWhitelist() + return 1 + +////////////////////////////////////////////////////////////////////////////////// +/proc/BC_LoadWhitelist() + + LAZYCLEARLIST(GLOB.whitelistedCkeys) + + LAZYINITLIST(GLOB.whitelistedCkeys) + + if(!GLOB.borderControlFile) + return 0 + + GLOB.borderControlFile["WhitelistedCkeys"] >> GLOB.whitelistedCkeys + + GLOB.whitelistLoaded = 1 + + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_SaveWhitelist() + if(!GLOB.whitelistedCkeys) + return 0 + + if(!GLOB.borderControlFile) + return 0 + + GLOB.borderControlFile["WhitelistedCkeys"] << GLOB.whitelistedCkeys diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 3b092fd3b7..726787b552 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -31,6 +31,7 @@ ///returns L[I] if L exists and I is a valid index of L, runtimes if L is not a list #define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) #define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; +#define LAZYISIN(L, V) L ? (V in L) : FALSE #define LAZYLEN(L) length(L) ///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects) #define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V); @@ -789,3 +790,25 @@ /proc/safe_json_decode(string, default = list()) . = default return json_decode(string) + +// Insert an object A into a sorted list using cmp_proc (/code/_helpers/cmp.dm) for comparison. +#define ADD_SORTED(list, A, cmp_proc) if(!list.len) {list.Add(A)} else {list.Insert(FindElementIndex(A, list, cmp_proc), A)} + +// Return the index using dichotomic search +/proc/FindElementIndex(atom/A, list/L, cmp) + var/i = 1 + var/j = L.len + var/mid + + while(i < j) + mid = round((i+j)/2) + + if(call(cmp)(L[mid],A) < 0) + i = mid + 1 + else + j = mid + + if(i == 1 || i == L.len) // Edge cases + return (call(cmp)(L[i],A) > 0) ? i : i+1 + else + return i diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index f7c864a89f..f210a9be76 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -347,3 +347,5 @@ /datum/config_entry/str_list/randomizing_station_name_message default = list() + +/datum/config_entry/number/border_control // If border control is enabled diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 891f6250da..726dade41e 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1,5 +1,11 @@ //////////////////////////////// +/proc/log_and_message_admins(var/message as text, var/mob/user = usr) + var/finalMessage = user ? "[key_name(user)] [message]" : "EVENT [message]" + log_admin(finalMessage) + message_admins(finalMessage) + log_world(finalMessage) + /proc/message_admins(msg) msg = "ADMIN LOG: [msg]" to_chat(GLOB.admins, msg, confidential = TRUE) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 29c9aaebc5..a0889be7eb 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -91,6 +91,9 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/admins/proc/open_borgopanel, /datum/admins/proc/change_laws, //change AI laws /datum/admins/proc/display_tags, + /datum/admins/proc/BC_WhitelistKeyVerb, + /datum/admins/proc/BC_RemoveKeyVerb, + /datum/admins/proc/BC_ToggleState, ) GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/DB_ban_panel, /client/proc/stickybanpanel)) GLOBAL_PROTECT(admin_verbs_ban) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 1a9f4cd49c..59c93094f4 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -429,6 +429,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(admin_memo_note) to_chat(src, admin_memo_note) adminGreet() + else if(!BC_IsKeyAllowedToConnect(ckey)) + to_chat(src, "Sorry, but the server is currently only accepting whitelisted players. Please see the discord to be whitelisted.") + log_and_message_admins("[ckey] was denied a connection due to not being whitelisted.") + qdel(src) + return 0 add_verbs_from_config() var/cached_player_age = set_client_age_from_db(tdata) //we have to cache this because other shit may change it and we need it's current value now down below. diff --git a/config/config.txt b/config/config.txt index 63adaee716..9508924e78 100644 --- a/config/config.txt +++ b/config/config.txt @@ -40,3 +40,4 @@ $include entries/urls.txt $include entries/vote.txt $include plushies/defines.txt +$include entries/border.txt diff --git a/config/entries/border.txt b/config/entries/border.txt new file mode 100644 index 0000000000..9f8c5f2362 --- /dev/null +++ b/config/entries/border.txt @@ -0,0 +1,5 @@ +## If the server only accepts whitelisted connections +## 0 Disables Border Control +## 1 Puts Border Control into 'learning mode' and will have it automatically whitelist new connections +## 2 Puts Border Control into 'enforcing mode' and will have it deny connections that are not already whitelisted. +BORDER_CONTROL 1 diff --git a/tgstation.dme b/tgstation.dme index 78c3079e1f..bca9471b1a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3968,6 +3968,7 @@ #include "GainStation13\code\mobs\chocoslime.dm" #include "GainStation13\code\mobs\races\caloritegolem.dm" #include "GainStation13\code\modules\cargo\packs.dm" +#include "GainStation13\code\modules\client\border_control.dm" #include "GainStation13\code\modules\client\preferences\preferences.dm" #include "GainStation13\code\modules\clothing\under\jobs\clothing.dm" #include "GainStation13\code\modules\clothing\under\jobs\modcivilian.dm"