GLOBAL_LIST_EMPTY(admin_datums) GLOBAL_PROTECT(admin_datums) GLOBAL_LIST_EMPTY(protected_admins) GLOBAL_PROTECT(protected_admins) GLOBAL_VAR_INIT(href_token, GenerateToken()) GLOBAL_PROTECT(href_token) #define RESULT_2FA_VALID 1 #define RESULT_2FA_ID 2 /datum/admins var/list/datum/admin_rank/ranks var/target var/name = "nobody's admin datum (no rank)" //Makes for better runtimes var/client/owner = null var/fakekey = null var/datum/marked_datum var/spamcooldown = 0 ///Randomly generated signature used for security records authorization name. var/admin_signature var/href_token /// Link from the database pointing to the admin's feedback forum var/cached_feedback_link var/deadmined var/datum/filter_editor/filteriffic var/datum/particle_editor/particle_test var/datum/colorblind_tester/color_test = new var/datum/plane_master_debug/plane_debug var/obj/machinery/computer/libraryconsole/admin_only_do_not_map_in_you_fucker/library_manager /// Whether or not the user tried to connect, but was blocked by 2FA var/blocked_by_2fa = FALSE /// Whether or not this user can bypass 2FA var/bypass_2fa = FALSE /// A lazylist of tagged datums, for quick reference with the View Tags verb var/list/tagged_datums var/given_profiling = FALSE /datum/admins/New(list/datum/admin_rank/ranks, ckey, force_active = FALSE, protected) if(IsAdminAdvancedProcCall()) alert_to_permissions_elevation_attempt(usr) if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins QDEL_IN(src, 0) CRASH("Admin proc call creation of admin datum") return if(!ckey) QDEL_IN(src, 0) CRASH("Admin datum created without a ckey") if(!istype(ranks)) QDEL_IN(src, 0) CRASH("Admin datum created with invalid ranks: [ranks] ([json_encode(ranks)])") target = ckey name = "[ckey]'s admin datum ([join_admin_ranks(ranks)])" src.ranks = ranks admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" href_token = GenerateToken() //only admins with +ADMIN start admined if(protected) GLOB.protected_admins[target] = src if (force_active || (rank_flags() & R_AUTOADMIN)) activate() else deactivate() /datum/admins/Destroy() if(IsAdminAdvancedProcCall()) alert_to_permissions_elevation_attempt(usr) return QDEL_HINT_LETMELIVE . = ..() /datum/admins/proc/activate() if(IsAdminAdvancedProcCall()) alert_to_permissions_elevation_attempt(usr) return GLOB.deadmins -= target GLOB.admin_datums[target] = src deadmined = FALSE plane_debug = new(src) if (GLOB.directory[target]) associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us /datum/admins/proc/deactivate() if(IsAdminAdvancedProcCall()) alert_to_permissions_elevation_attempt(usr) return GLOB.deadmins[target] = src GLOB.admin_datums -= target QDEL_NULL(plane_debug) deadmined = TRUE var/client/client = owner || GLOB.directory[target] if (!isnull(client)) disassociate() add_verb(client, /client/proc/readmin) client.disable_combo_hud() client.update_special_keybinds() /datum/admins/proc/associate(client/client) if(IsAdminAdvancedProcCall()) alert_to_permissions_elevation_attempt(usr) return if(!istype(client)) return if(client?.ckey != target) var/msg = " has attempted to associate with [target]'s admin datum" message_admins("[key_name_admin(client)][msg]") log_admin("[key_name(client)][msg]") return var/result_2fa = check_2fa(client) if (!result_2fa[RESULT_2FA_VALID]) blocked_by_2fa = TRUE alert_2fa_necessary(client) start_2fa_process(client, result_2fa[RESULT_2FA_ID]) return else if (blocked_by_2fa) //previously blocked by 2fa but has now verified, sync the lastadminrank column on the player table. sync_lastadminrank(client.ckey, client.key, src) blocked_by_2fa = FALSE if (deadmined) activate() remove_verb(client, /client/proc/admin_2fa_verify) owner = client owner.holder = src owner.add_admin_verbs() remove_verb(owner, /client/proc/readmin) owner.init_verbs() //re-initialize the verb list owner.update_special_keybinds() GLOB.admins |= client try_give_profiling() /datum/admins/proc/disassociate() if(IsAdminAdvancedProcCall()) alert_to_permissions_elevation_attempt(usr) return if(owner) GLOB.admins -= owner owner.remove_admin_verbs() owner.holder = null owner = null /// Returns the feedback forum thread for the admin holder's owner, as according to DB. /datum/admins/proc/feedback_link() // This intentionally does not follow the 10-second maximum TTL rule, // as this can be reloaded through the Reload-Admins verb. if (cached_feedback_link == NO_FEEDBACK_LINK) return null if (!isnull(cached_feedback_link)) return cached_feedback_link if (!SSdbcore.IsConnected()) return FALSE var/datum/db_query/feedback_query = SSdbcore.NewQuery("SELECT feedback FROM [format_table_name("admin")] WHERE ckey = '[owner.ckey]'") if(!feedback_query.Execute()) log_sql("Error retrieving feedback link for [src]") qdel(feedback_query) return FALSE if(!feedback_query.NextRow()) qdel(feedback_query) return FALSE // no feedback link exists cached_feedback_link = feedback_query.item[1] || NO_FEEDBACK_LINK qdel(feedback_query) if (cached_feedback_link == NO_FEEDBACK_LINK) // Because we don't want to send fake clickable links. return null return cached_feedback_link /datum/admins/proc/check_for_rights(rights_required) if(rights_required && !(rights_required & rank_flags())) return FALSE return TRUE /datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other) if(!other) return TRUE //they have no rights if(rank_flags() == R_EVERYTHING) return TRUE //we have all the rights if(src == other) return TRUE //you always have more rights than yourself if(rank_flags() != other.rank_flags()) if( (rank_flags() & other.rank_flags()) == other.rank_flags() ) return TRUE //we have all the rights they have and more return FALSE // TRUE for a vaild connection, null is the id (it is unnecessary) #define VALID_2FA_CONNECTION list(TRUE, null) /// Returns whether or not the given client has a verified 2FA connection. /// The output is in the form of a list with the first index being whether or not the /// check was successful, the 2nd is the ID of the associated database entry /// if its a false result and if one can be found. /datum/admins/proc/check_2fa(client/client) if (bypass_2fa) return VALID_2FA_CONNECTION var/admin_2fa_url = CONFIG_GET(string/admin_2fa_url) // 2FA not being enabled == everyone passes if (isnull(admin_2fa_url) || admin_2fa_url == "") return VALID_2FA_CONNECTION // I believe this is only in the case of Dream Seeker. if (isnull(client?.address)) return VALID_2FA_CONNECTION if (!SSdbcore.Connect()) if (verify_admin_from_local_cache(client) || (client.ckey in GLOB.protected_admins)) return VALID_2FA_CONNECTION else return list(FALSE, null) var/datum/db_query/query = SSdbcore.NewQuery({" SELECT id, verification_time FROM [format_table_name("admin_connections")] WHERE ckey = :ckey AND ip = INET_ATON(:ip) AND cid = :cid "}, list( "ckey" = client.ckey, "ip" = client.address, "cid" = client.computer_id, )) if (!query.Execute()) if (verify_admin_from_local_cache(client) || (client.ckey in GLOB.protected_admins)) return VALID_2FA_CONNECTION return list(FALSE, null) var/is_valid = FALSE var/id = null if (query.NextRow()) id = query.item[1] is_valid = !isnull(query.item[2]) qdel(query) return list(is_valid, id) #undef VALID_2FA_CONNECTION #define ERROR_2FA_REQUEST_PERMISSIONS "