diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index e1e72e61..4f845d67 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -17,6 +17,7 @@ #define LAZYFIND(L, V) L ? L.Find(V) : 0 #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) #define LAZYCLEARLIST(L) if(L) L.Cut() #define SANITIZE_LIST(L) ( islist(L) ? L : list() ) @@ -577,3 +578,24 @@ for(var/key in input) ret += key return ret +// 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 600a4106..d940134b 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -436,3 +436,5 @@ /datum/config_entry/flag/minimaps_enabled config_entry_value = TRUE + +/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 6f6ff516..aeb2dd56 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) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 149e020e..d2c3716f 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -71,6 +71,9 @@ GLOBAL_LIST_INIT(admin_verbs_admin, world.AVerbsAdmin()) /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ /client/proc/panicbunker, + /datum/admins/proc/BC_WhitelistKeyVerb, + /datum/admins/proc/BC_RemoveKeyVerb, + /datum/admins/proc/BC_ToggleState, /client/proc/addbunkerbypass, /client/proc/discordmessage, /client/proc/revokebunkerbypass, @@ -726,4 +729,4 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( set name = "Debug Stat Panel" set category = "Debug" - src << output("", "statbrowser:create_debug") \ No newline at end of file + src << output("", "statbrowser:create_debug") diff --git a/code/modules/client/border_control.dm b/code/modules/client/border_control.dm new file mode 100644 index 00000000..7d09c02e --- /dev/null +++ b/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, 0) + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_ModeToText(var/mode) + switch(mode) + if(BORDER_CONTROL_DISABLED) + return "Disabled" + if(BORDER_CONTROL_LEARNING) + return "Learning" + if(BORDER_CONTROL_ENFORCED) + return "Enforced" + +////////////////////////////////////////////////////////////////////////////////// +proc/BC_IsKeyAllowedToConnect(var/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(var/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(var/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(var/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/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 96ba948b..ee21eb3a 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -363,6 +363,11 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) add_admin_verbs() to_chat(src, get_message_output("memo")) 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 3a4ef017..215e1b56 100644 --- a/config/config.txt +++ b/config/config.txt @@ -481,3 +481,9 @@ DEFAULT_VIEW 21x15 ## Enable Dynamic mode DYNAMIC_MODE + +## 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 \ No newline at end of file diff --git a/tgstation.dme b/tgstation.dme index 02d60d18..a83f31af 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1606,6 +1606,7 @@ #include "code\modules\cargo\packs\security.dm" #include "code\modules\cargo\packs\service.dm" #include "code\modules\chatter\chatter.dm" +#include "code\modules\client\border_control.dm" #include "code\modules\client\client_colour.dm" #include "code\modules\client\client_defines.dm" #include "code\modules\client\client_procs.dm"