diff --git a/aurorastation.dme b/aurorastation.dme index 4cf8d5c0685..cec9e87fa54 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -271,6 +271,7 @@ #include "code\__HELPERS\logging\subsystems\garbage.dm" #include "code\__HELPERS\logging\subsystems\ghostroles.dm" #include "code\__HELPERS\logging\subsystems\http.dm" +#include "code\__HELPERS\logging\subsystems\ipintel.dm" #include "code\__HELPERS\logging\subsystems\law.dm" #include "code\__HELPERS\logging\subsystems\mapfinalization.dm" #include "code\__HELPERS\logging\subsystems\odyssey.dm" diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm index 4e8369bd454..af3ef98cf72 100644 --- a/code/__DEFINES/MC.dm +++ b/code/__DEFINES/MC.dm @@ -83,9 +83,10 @@ #define SS_PAUSING 5 /// in the middle of pausing // Subsystem init stages -#define INITSTAGE_EARLY 1 //! Early init stuff that doesn't need to wait for mapload -#define INITSTAGE_MAIN 2 //! Main init stage -#define INITSTAGE_MAX 2 //! Highest initstage. +#define INITSTAGE_FIRST 1 //! Stuff that needs to start firing before everything else (DBCore) +#define INITSTAGE_EARLY 2 //! Early init stuff that doesn't need to wait for mapload +#define INITSTAGE_MAIN 3 //! Main init stage +#define INITSTAGE_MAX 3 //! Highest initstage. #define SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/##X);\ /datum/controller/subsystem/##X/New(){\ diff --git a/code/__HELPERS/logging/subsystems/ipintel.dm b/code/__HELPERS/logging/subsystems/ipintel.dm new file mode 100644 index 00000000000..b6f90c17e1e --- /dev/null +++ b/code/__HELPERS/logging/subsystems/ipintel.dm @@ -0,0 +1,4 @@ +/// Logging for the IPIntel (XKeyScore) subsystem +/proc/log_subsystem_ipintel(text) + if (GLOB.config?.logsettings["log_subsystems_ipintel"]) + WRITE_LOG(GLOB.config.logfiles["world_subsystems_ipintel"], "IPINTEL: [text]") diff --git a/code/controllers/subsystems/dbcore.dm b/code/controllers/subsystems/dbcore.dm index cb85832882c..2863c9b06aa 100644 --- a/code/controllers/subsystems/dbcore.dm +++ b/code/controllers/subsystems/dbcore.dm @@ -5,6 +5,7 @@ SUBSYSTEM_DEF(dbcore) wait = 10 // Not seconds because we're running on SS_TICKER runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT init_order = INIT_ORDER_DBCORE + init_stage = INITSTAGE_FIRST priority = FIRE_PRIORITY_DATABASE @@ -308,6 +309,8 @@ SUBSYSTEM_DEF(dbcore) /// Check if we have established a DB Connection /datum/controller/subsystem/dbcore/proc/IsConnected() + PRIVATE_PROC(TRUE) + if(!GLOB.config.sql_enabled) return FALSE if (!connection) @@ -489,7 +492,7 @@ SUBSYSTEM_DEF(dbcore) if(status == DB_QUERY_STARTED) CRASH("Attempted to start a new query while waiting on the old one") - if(!SSdbcore.IsConnected()) + if(!SSdbcore.Connect()) last_error = "No connection!" return FALSE @@ -528,7 +531,7 @@ SUBSYSTEM_DEF(dbcore) if(status == DB_QUERY_STARTED) CRASH("Attempted to start a new query while waiting on the old one") - if(!SSdbcore.IsConnected()) + if(!SSdbcore.Connect()) last_error = "No connection!" return FALSE diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index 7db7a59c7a1..2fb5dea2b4d 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -28,7 +28,7 @@ SUBSYSTEM_DEF(garbage) flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY init_order = INIT_ORDER_GARBAGE - init_stage = INITSTAGE_EARLY + init_stage = INITSTAGE_FIRST var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level diff --git a/code/controllers/subsystems/ipintel.dm b/code/controllers/subsystems/ipintel.dm index 47b88153f48..074c48d443f 100644 --- a/code/controllers/subsystems/ipintel.dm +++ b/code/controllers/subsystems/ipintel.dm @@ -2,13 +2,242 @@ SUBSYSTEM_DEF(ipintel) name = "XKeyScore" init_order = INIT_ORDER_MISC_FIRST flags = SS_NO_FIRE - var/enabled = FALSE //disable at round start to avoid checking reconnects + /// Whether IPIntel lookups are currently enabled. Disabled until Initialize() to avoid checking reconnects at roundstart. + var/enabled = FALSE + /// world.timeofday value before which all API requests are suppressed due to prior rate-limit or connection errors var/throttle = 0 + /// Number of consecutive API failures; used to scale the throttle window var/errors = 0 - + /// In-memory cache of recent [/datum/ipintel] results, keyed by IP address string var/list/cache = list() /datum/controller/subsystem/ipintel/Initialize() enabled = TRUE - return SS_INIT_SUCCESS + +/** + * Looks up the IPIntel score for the given IP address. + * + * Called from [/client/proc/InitClient] during client connection to check + * whether the connecting IP is likely a proxy or VPN. + * + * Checks sources in priority order: + * 1. In-memory cache ([/datum/controller/subsystem/ipintel/var/cache]) + * 2. Database cache (`ss13_ipintel` table) + * 3. Live IPIntel API request via [/datum/controller/subsystem/ipintel/proc/query] + * + * The result is always delivered via `on_complete`, which is called with a [/datum/ipintel] + * as its sole argument. For cache hits the callback is invoked before this proc returns; + * for live requests it is invoked asynchronously once the HTTP response arrives. + * + * The return value is set to the [/datum/ipintel] datum immediately, but its `intel` field + * is only guaranteed to be populated by the time `on_complete` fires. + * + * Arguments: + * * ip - The IP address string to look up + * * bypasscache - If TRUE, skips both memory and database cache and always queries the API + * * updatecache - If TRUE, writes a successful live result back to memory and database cache + * * on_complete - Callback invoked with the final [/datum/ipintel] datum when the result is ready + */ +/datum/controller/subsystem/ipintel/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE, datum/callback/on_complete) + var/datum/ipintel/res = new() + res.ip = ip + . = res + if (!ip || !GLOB.config.ipintel_email || !enabled) + if (on_complete) + on_complete.Invoke(res) + return + if (!bypasscache) + var/datum/ipintel/cachedintel = cache[ip] + if (cachedintel && cachedintel.is_valid()) + cachedintel.cache = TRUE + if (on_complete) + on_complete.Invoke(cachedintel) + return cachedintel + + var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery( + {"SELECT date, intel, TIMESTAMPDIFF(MINUTE, date, NOW()) + FROM ss13_ipintel + WHERE + ip = INET6_ATON(:ip) + AND (( + intel < :rating_bad + AND + date + INTERVAL :save_good HOUR > NOW() + ) OR ( + intel >= :rating_bad + AND + date + INTERVAL :save_bad HOUR > NOW() + ))"}, + list( + "ip" = ip, + "rating_bad" = GLOB.config.ipintel_rating_bad, + "save_good" = GLOB.config.ipintel_save_good, + "save_bad" = GLOB.config.ipintel_save_bad, + ) + ) + if (!query_get_ip_intel.Execute()) + qdel(query_get_ip_intel) + if (on_complete) + on_complete.Invoke(res) + return + if (query_get_ip_intel.NextRow()) + res.cache = TRUE + res.cachedate = query_get_ip_intel.item[1] + res.intel = text2num(query_get_ip_intel.item[2]) + res.cacheminutesago = text2num(query_get_ip_intel.item[3]) + res.cacherealtime = world.realtime - (text2num(query_get_ip_intel.item[3])*10*60) + cache[ip] = res + qdel(query_get_ip_intel) + if (on_complete) + on_complete.Invoke(res) + return + qdel(query_get_ip_intel) + query(ip, CALLBACK(src, PROC_REF(on_query_complete), ip, res, updatecache, on_complete)) + +/** + * Internal callback for [/datum/controller/subsystem/ipintel/proc/get_ip_intel] — invoked by + * [/datum/controller/subsystem/ipintel/proc/query] once the live API result is available. + * + * Writes the intel score into `res`, updates memory and database caches when appropriate, + * then invokes the outer `on_complete` callback. + * + * Arguments: + * * ip - The IP address that was looked up + * * res - The [/datum/ipintel] datum being populated + * * updatecache - Whether to persist the result to memory and database cache + * * on_complete - The callback originally passed to [/datum/controller/subsystem/ipintel/proc/get_ip_intel] + * * intel - The raw intel score returned by [/datum/controller/subsystem/ipintel/proc/query], or -1 on error + */ +/datum/controller/subsystem/ipintel/proc/on_query_complete(ip, datum/ipintel/res, updatecache, datum/callback/on_complete, intel) + PRIVATE_PROC(TRUE) + res.intel = intel + if (updatecache && res.intel >= 0) + cache[ip] = res + var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery( + "INSERT INTO ss13_ipintel (ip, intel) VALUES (INET6_ATON(:ip), :intel) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()", + list("ip" = ip, "intel" = res.intel) + ) + query_add_ip_intel.SetSuccessCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) + query_add_ip_intel.SetFailCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) + query_add_ip_intel.ExecuteNoSleep(TRUE) // Allow to run while we still load the game + if (on_complete) + on_complete.Invoke(res) + +/** + * Submits an async IPIntel API request for the given IP address via [/datum/controller/subsystem/http]. + * + * The result is delivered to `on_complete` as a raw intel score (0.0–1.0), or -1 on any + * failure. Does nothing and invokes `on_complete(-1)` if IPIntel is disabled or currently + * throttled due to prior errors. + * + * Use [/datum/controller/subsystem/ipintel/proc/get_ip_intel] instead of calling this directly; + * it handles cache lookup and result storage around this proc. + * + * Arguments: + * * ip - The IP address to query + * * on_complete - Callback invoked with the intel score (num) when the response arrives + * * retry - TRUE if this is an automatic retry after a transient failure + */ +/datum/controller/subsystem/ipintel/proc/query(ip, datum/callback/on_complete, retry = FALSE) + PRIVATE_PROC(TRUE) + if (!ip || !enabled) + if (on_complete) + on_complete.Invoke(-1) + return + if (throttle > world.timeofday) + if (on_complete) + on_complete.Invoke(-1) + return + + var/url = "https://[GLOB.config.ipintel_domain]/check.php?ip=[ip]&contact=[GLOB.config.ipintel_email]&format=json&flags=f" + SShttp.create_async_request( + RUSTG_HTTP_METHOD_GET, + url, + "", + null, + CALLBACK(src, PROC_REF(process_response), ip, on_complete, retry) + ) + +/** + * SShttp callback for [/datum/controller/subsystem/ipintel/proc/query] — processes the HTTP + * response from the IPIntel API. + * + * On success, parses the JSON body and invokes `on_complete` with the numeric result. + * On transient errors (connection failure, unexpected status, bad JSON) the request is + * retried once after a brief delay. On a second failure, or on a hard 429 rate-limit + * response, `on_complete` is invoked with -1. + * + * Arguments: + * * ip - The IP address that was queried + * * on_complete - Callback to invoke with the final intel score or -1 + * * retry - TRUE if this invocation is already a retry + * * http - The [/datum/http_response] returned by SShttp + */ +/datum/controller/subsystem/ipintel/proc/process_response(ip, datum/callback/on_complete, retry, datum/http_response/http) + PRIVATE_PROC(TRUE) + if (http.errored) + handle_error("Unable to connect to API.", ip, retry) + if (!retry) + sleep(25) + query(ip, on_complete, TRUE) + else + if (on_complete) + on_complete.Invoke(-1) + return + + if (http.status_code == 200) + var/response = json_decode(http.body) + if (response) + if (response["status"] == "success") + var/intelnum = text2num(response["result"]) + if (isnum(intelnum)) + if (on_complete) + on_complete.Invoke(intelnum) + return + else + handle_error("Bad intel from server: [response["result"]].", ip, retry) + else + handle_error("Bad response from server: [response["status"]].", ip, retry) + if (!retry) + sleep(25) + query(ip, on_complete, TRUE) + else + if (on_complete) + on_complete.Invoke(-1) + else if (http.status_code == 429) + handle_error("Status Code 429: We have exceeded the rate limit.", ip, TRUE) + if (on_complete) + on_complete.Invoke(-1) + else + handle_error("Unknown status code: [http.status_code].", ip, retry) + if (!retry) + sleep(25) + query(ip, on_complete, TRUE) + else + if (on_complete) + on_complete.Invoke(-1) + +/** + * Logs an IPIntel error and, on a final retry failure, increments [/datum/controller/subsystem/ipintel/var/errors] + * and sets [/datum/controller/subsystem/ipintel/var/throttle] to temporarily suppress further API calls. + * + * The throttle duration grows linearly with the error count: + * each additional failure adds another 12-second window (10 ticks × 120 × error count). + * All errors are written via [/proc/log_subsystem_ipintel]. + * + * Arguments: + * * error - Base error message to log + * * ip - The IP address that triggered the error + * * retry - TRUE if this is a final failure (no further retry will be attempted) + */ +/datum/controller/subsystem/ipintel/proc/handle_error(error, ip, retry) + PRIVATE_PROC(TRUE) + if (retry) + errors++ + error += " Could not check [ip]. Disabling IPINTEL for [errors] minute\s" + throttle = world.timeofday + (10 * 120 * errors) + else + error += " Attempting retry on [ip]." + log_subsystem_ipintel(error) + diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm index 93bc0d39443..4d6a3b23be6 100644 --- a/code/datums/helper_datums/getrev.dm +++ b/code/datums/helper_datums/getrev.dm @@ -15,7 +15,6 @@ GLOBAL_DATUM_INIT(revdata, /datum/getrev, new()) var/date var/showinfo var/list/datum/tgs_revision_information/test_merge/test_merges - var/greeting_info /datum/getrev/New() var/list/head_branch = file2list(".git/HEAD", "\n") @@ -56,6 +55,9 @@ GLOBAL_DATUM_INIT(revdata, /datum/getrev, new()) to_chat(src, "Current Map: [SSatlas.current_map.full_name]") + if(GLOB.revdata.test_merges.len) + to_chat(src, GLOB.revdata.testmerge_overview()) + /datum/getrev/proc/testmerge_overview() if (!test_merges.len) return @@ -69,24 +71,6 @@ GLOBAL_DATUM_INIT(revdata, /datum/getrev, new()) return out.Join() -/datum/getrev/proc/generate_greeting_info() - if (!test_merges.len) - greeting_info = {"
- There are currently no test merges loaded onto the server. -
"} - return - - var/list/out = list("

There are currently [test_merges.len] PRs being tested live.

", - {""} - ) - - for (var/TM in test_merges) - out += testmerge_long_oveview(TM) - - out += "
" - - greeting_info = out.Join() - /datum/getrev/proc/testmerge_initialize() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) @@ -98,8 +82,6 @@ GLOBAL_DATUM_INIT(revdata, /datum/getrev, new()) LOG_DEBUG("GETREV: No TGS API found.") test_merges = list() - generate_greeting_info() - /datum/getrev/proc/testmerge_short_overview(datum/tgs_revision_information/test_merge/tm) . = list() diff --git a/code/datums/helper_datums/synthsprites.dm b/code/datums/helper_datums/synthsprites.dm index f78af8967de..94086fdb98d 100644 --- a/code/datums/helper_datums/synthsprites.dm +++ b/code/datums/helper_datums/synthsprites.dm @@ -53,12 +53,12 @@ paiicon is the pai icon sprite name LOG_DEBUG("Synthsprites: SQL Disabled - Falling back to JSON") loadsynths_from_json() return - if(!establish_db_connection(GLOB.dbcon)) + if(!SSdbcore.Connect()) LOG_DEBUG("Synthsprites: SQL ERROR - Failed to connect. - Falling back to JSON") loadsynths_from_json() return - var/DBQuery/customsynthsprites = GLOB.dbcon.NewQuery("SELECT synthname, synthckey, synthicon, aichassisicon, aiholoicon, paiicon FROM ss13_customsynths ORDER BY synthckey ASC") + var/datum/db_query/customsynthsprites = SSdbcore.NewQuery("SELECT synthname, synthckey, synthicon, aichassisicon, aiholoicon, paiicon FROM ss13_customsynths ORDER BY synthckey ASC") customsynthsprites.Execute() while(customsynthsprites.NextRow()) CHECK_TICK @@ -71,3 +71,4 @@ paiicon is the pai icon sprite name synth.aiholoicon = customsynthsprites.item[5] synth.paiicon = customsynthsprites.item[6] robot_custom_icons[synth.synthname] = synth + qdel(customsynthsprites) diff --git a/code/game/antagonist/antagonist_add.dm b/code/game/antagonist/antagonist_add.dm index 67b7f4d38de..0dac51f3e3d 100644 --- a/code/game/antagonist/antagonist_add.dm +++ b/code/game/antagonist/antagonist_add.dm @@ -112,7 +112,7 @@ new_log.SetSuccessCallback(CALLBACK(src, .proc/set_db_log_id)) new_log.SetFailCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) - new_log.ExecuteNoSleep() + new_log.ExecuteNoSleep(TRUE) //Allow to execute before roundstarts for roundstart antags return /datum/antagonist/proc/set_db_log_id(var/datum/db_query/new_log) @@ -137,4 +137,4 @@ ) update_query.SetSuccessCallback(CALLBACK(src, .proc/set_db_log_id)) update_query.SetFailCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) - update_query.ExecuteNoSleep() + update_query.ExecuteNoSleep(TRUE) //Shouldnt happen but still, in case it happens we want it to run before roundstart diff --git a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm index 1729f1b0448..11ce9dccfcc 100644 --- a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm +++ b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm @@ -111,7 +111,7 @@ if (!establish_db_connection(GLOB.dbcon)) to_chat(src, SPAN_NOTICE("Unable to connect to the database.")) return - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT title, message FROM ss13_ccia_general_notice_list WHERE deleted_at IS NULL") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT title, message FROM ss13_ccia_general_notice_list WHERE deleted_at IS NULL") query.Execute() var/list/template_names = list() @@ -121,6 +121,8 @@ template_names += query.item[1] templates[query.item[1]] = query.item[2] + qdel(query) + // Catch empty list if (!templates.len) to_chat(src, SPAN_NOTICE("There are no templates in the database.")) diff --git a/code/game/jobs/whitelist.dm b/code/game/jobs/whitelist.dm index bb650bc4599..88bf05299b5 100644 --- a/code/game/jobs/whitelist.dm +++ b/code/game/jobs/whitelist.dm @@ -75,13 +75,15 @@ var/list/whitelist_jobconfig = list() log_world("ERROR: Database connection failed while loading alien whitelists. Reverting to legacy system.") GLOB.config.sql_whitelists = 0 else - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT status_name, flag FROM ss13_whitelist_statuses") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT status_name, flag FROM ss13_whitelist_statuses") query.Execute() while (query.NextRow()) if (query.item[1] in GLOB.whitelisted_species) GLOB.whitelisted_species[query.item[1]] = text2num(query.item[2]) + qdel(query) + return var/text = file2text("config/alienwhitelist.txt") diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm index f521f51b099..d37ff72603b 100644 --- a/code/game/machinery/requests_console.dm +++ b/code/game/machinery/requests_console.dm @@ -266,13 +266,15 @@ GLOBAL_LIST_INIT_TYPED(allConsoles, /obj/machinery/requests_console, list()) if (!SQLquery) SQLquery = "SELECT id, name, department FROM ss13_forms ORDER BY id" - var/DBQuery/query = GLOB.dbcon.NewQuery(SQLquery) + var/datum/db_query/query = SSdbcore.NewQuery(SQLquery) query.Execute() var/list/forms = list() while (query.NextRow()) forms += list(list("id" = query.item[1], "name" = query.item[2], "department" = query.item[3])) + qdel(query) + if (!forms.len) data["sql_error"] = 1 @@ -397,7 +399,7 @@ GLOBAL_LIST_INIT_TYPED(allConsoles, /obj/machinery/requests_console, list()) alert("Connection to the database lost. Aborting.") if(!printid) alert("Invalid query. Try again.") - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT id, name, data FROM ss13_forms WHERE id=[printid]") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id, name, data FROM ss13_forms WHERE id=[printid]") query.Execute() while(query.NextRow()) @@ -415,6 +417,8 @@ GLOBAL_LIST_INIT_TYPED(allConsoles, /obj/machinery/requests_console, list()) paperstock-- + qdel(query) + // Get extra information about the form. if(href_list["whatis"]) var/whatisid = sanitizeSQL(href_list["whatis"]) @@ -423,7 +427,7 @@ GLOBAL_LIST_INIT_TYPED(allConsoles, /obj/machinery/requests_console, list()) alert("Connection to the database lost. Aborting.") if(!whatisid) alert("Invalid query. Try again.") - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT id, name, department, info FROM ss13_forms WHERE id=[whatisid]") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id, name, department, info FROM ss13_forms WHERE id=:id",list("id"=whatisid)) query.Execute() var/dat = "
Stellar Corporate Conglomerate Form
" while(query.NextRow()) @@ -438,6 +442,7 @@ GLOBAL_LIST_INIT_TYPED(allConsoles, /obj/machinery/requests_console, list()) dat += "[info]" dat += "
" usr << browse(HTML_SKELETON(dat), "window=Information;size=560x240") + qdel(query) // Toggle the paper bin lid. if(href_list["setLid"]) diff --git a/code/game/objects/items/devices/uplink.dm b/code/game/objects/items/devices/uplink.dm index 467a37c1aa5..4489a6f292d 100644 --- a/code/game/objects/items/devices/uplink.dm +++ b/code/game/objects/items/devices/uplink.dm @@ -244,8 +244,8 @@ Then check if it's true, if true return. This will stop the normal menu appearin .["contracts_view"] = 1 query_details["status"] = "open" - var/DBQuery/index_query = GLOB.dbcon.NewQuery("SELECT count(*) as Total_Contracts FROM ss13_syndie_contracts WHERE deleted_at IS NULL AND status = :status:") - index_query.Execute(query_details) + var/datum/db_query/index_query = SSdbcore.NewQuery("SELECT count(*) as Total_Contracts FROM ss13_syndie_contracts WHERE deleted_at IS NULL AND status = :status", query_details) + index_query.Execute() var/pages = 0 @@ -271,8 +271,8 @@ Then check if it's true, if true return. This will stop the normal menu appearin query_details["offset"] = (.["contracts_current_page"] - 1) * 10 - var/DBQuery/list_query = GLOB.dbcon.NewQuery("SELECT contract_id, contractee_name, title FROM ss13_syndie_contracts WHERE deleted_at IS NULL AND status = :status: LIMIT 10 OFFSET :offset:") - list_query.Execute(query_details) + var/datum/db_query/list_query = SSdbcore.NewQuery("SELECT contract_id, contractee_name, title FROM ss13_syndie_contracts WHERE deleted_at IS NULL AND status = :status LIMIT 10 OFFSET :offset",query_details) + list_query.Execute() var/list/contracts = list() while (list_query.NextRow()) @@ -283,6 +283,8 @@ Then check if it's true, if true return. This will stop the normal menu appearin .["contracts"] = contracts .["contracts_found"] = 1 + qdel(list_query) + qdel(index_query) if(tgui_menu == 31) .["contracts_found"] = 0 @@ -299,8 +301,8 @@ Then check if it's true, if true return. This will stop the normal menu appearin var/query_details[0] query_details["contract_id"] = exploit_id - var/DBQuery/select_query = GLOB.dbcon.NewQuery("SELECT contract_id, contractee_name, status, title, description, reward_other FROM ss13_syndie_contracts WHERE contract_id = :contract_id:") - select_query.Execute(query_details) + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT contract_id, contractee_name, status, title, description, reward_other FROM ss13_syndie_contracts WHERE contract_id = :contract_id",query_details) + select_query.Execute() if (select_query.NextRow()) .["contracts_found"] = 1 @@ -324,6 +326,7 @@ Then check if it's true, if true return. This will stop the normal menu appearin contract["reward_other"] = select_query.item[6] .["contract"] = contract + qdel(select_query) // I placed this here because of how relevant it is. // You place this in your uplinkable item to check if an uplink is active or not. diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index f828e2a737b..eeae6cfde06 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -180,7 +180,7 @@ GLOBAL_PROTECT(jobban_keylist) */ /proc/jobban_loaddatabase() // No database. Weee. - if (!establish_db_connection(GLOB.dbcon)) + if (!SSdbcore.Connect()) log_world("ERROR: Database connection failed. Reverting to the legacy ban system.") log_misc("Database connection failed. Reverting to the legacy ban system.") GLOB.config.ban_legacy_system = 1 @@ -188,7 +188,7 @@ GLOBAL_PROTECT(jobban_keylist) return // All jobbans in one query. Because we don't actually care. - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT id, ckey, job, reason FROM ss13_ban WHERE isnull(unbanned) AND ((bantype = 'JOB_PERMABAN') OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now()))") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id, ckey, job, reason FROM ss13_ban WHERE isnull(unbanned) AND ((bantype = 'JOB_PERMABAN') OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now()))") query.Execute() while (query.NextRow()) @@ -207,6 +207,7 @@ GLOBAL_PROTECT(jobban_keylist) else // Woups. What happened here...? log_and_message_admins("JOBBANS: Duplicate jobban entry in MySQL for [ckey]. Ban ID: #[query.item[1]]") + qdel(query) /** * Saves the current bans into the data/job_full.ban file. diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index a56e6a2934d..56fa97e03b3 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -1,15 +1,34 @@ +/** + * # IPIntel datum + * + * Holds the result of an IPIntel lookup for a given IP address. + * Created by [/datum/controller/subsystem/ipintel/proc/get_ip_intel] and delivered to its `on_complete` callback. + */ /datum/ipintel + /// The IP address that was looked up var/ip + /// The raw intel score returned by the API (0.0–1.0), or -1 on error var/intel = 0 + /// Whether this result came from cache (memory or database) rather than a live API call var/cache = FALSE + /// How many minutes ago the cached entry was written var/cacheminutesago = 0 + /// SQL timestamp of when the cache entry was written var/cachedate = "" + /// world.realtime equivalent of the cache write time, used for in-memory cache expiry var/cacherealtime = 0 /datum/ipintel/New() cachedate = SQLtime() cacherealtime = world.realtime +/** + * Returns TRUE if this cached result is still within its configured validity window. + * + * Good results (below [/datum/configuration/var/ipintel_rating_bad]) are kept for + * [/datum/configuration/var/ipintel_save_good] hours; bad results are kept for + * [/datum/configuration/var/ipintel_save_bad] hours. Returns FALSE for error results (intel < 0). + */ /datum/ipintel/proc/is_valid() . = FALSE if (intel < 0) @@ -20,110 +39,3 @@ else if (world.realtime < cacherealtime+(GLOB.config.ipintel_save_bad*60*60*10)) return TRUE - -/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE) - var/datum/ipintel/res = new() - res.ip = ip - . = res - if (!ip || !GLOB.config.ipintel_email || !SSipintel.enabled) - return - if (!bypasscache) - var/datum/ipintel/cachedintel = SSipintel.cache[ip] - if (cachedintel && cachedintel.is_valid()) - cachedintel.cache = TRUE - return cachedintel - - if(establish_db_connection(GLOB.dbcon)) - var/DBQuery/query_get_ip_intel = GLOB.dbcon.NewQuery({" - SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) - FROM ss13_ipintel - WHERE - ip = INET6_ATON('[ip]') - AND (( - intel < [GLOB.config.ipintel_rating_bad] - AND - date + INTERVAL [GLOB.config.ipintel_save_good] HOUR > NOW() - ) OR ( - intel >= [GLOB.config.ipintel_rating_bad] - AND - date + INTERVAL [GLOB.config.ipintel_save_bad] HOUR > NOW() - )) - "}) - if(!query_get_ip_intel.Execute()) - return - if (query_get_ip_intel.NextRow()) - res.cache = TRUE - res.cachedate = query_get_ip_intel.item[1] - res.intel = text2num(query_get_ip_intel.item[2]) - res.cacheminutesago = text2num(query_get_ip_intel.item[3]) - res.cacherealtime = world.realtime - (text2num(query_get_ip_intel.item[3])*10*60) - SSipintel.cache[ip] = res - return - res.intel = ip_intel_query(ip) - if (updatecache && res.intel >= 0) - SSipintel.cache[ip] = res - if(establish_db_connection(GLOB.dbcon)) - var/DBQuery/query_add_ip_intel = GLOB.dbcon.NewQuery("INSERT INTO ss13_ipintel (ip, intel) VALUES (INET6_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()") - query_add_ip_intel.Execute() - - - -/proc/ip_intel_query(ip, var/retryed=0) - . = -1 //default - if (!ip) - return - if (SSipintel.throttle > world.timeofday) - return - if (!SSipintel.enabled) - return - - var/list/http[] = world.Export("http://[GLOB.config.ipintel_domain]/check.php?ip=[ip]&contact=[GLOB.config.ipintel_email]&format=json&flags=f") - - if (http) - var/status = text2num(http["STATUS"]) - - if (status == 200) - var/response = json_decode(file2text(http["CONTENT"])) - if (response) - if (response["status"] == "success") - var/intelnum = text2num(response["result"]) - if (isnum(intelnum)) - return text2num(response["result"]) - else - ipintel_handle_error("Bad intel from server: [response["result"]].", ip, retryed) - if (!retryed) - sleep(25) - return .(ip, 1) - else - ipintel_handle_error("Bad response from server: [response["status"]].", ip, retryed) - if (!retryed) - sleep(25) - return .(ip, 1) - - else if (status == 429) - ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip, 1) - return - else - ipintel_handle_error("Unknown status code: [status].", ip, retryed) - if (!retryed) - sleep(25) - return .(ip, 1) - else - ipintel_handle_error("Unable to connect to API.", ip, retryed) - if (!retryed) - sleep(25) - return .(ip, 1) - - -/proc/ipintel_handle_error(error, ip, retryed) - if (retryed) - SSipintel.errors++ - error += " Could not check [ip]. Disabling IPINTEL for [SSipintel.errors] minute\s" - SSipintel.throttle = world.timeofday + (10 * 120 * SSipintel.errors) - else - error += " Attempting retry on [ip]." - log_ipintel(error) - -/proc/log_ipintel(text) - log_game("IPINTEL: [text]") - LOG_DEBUG("IPINTEL: [text]") diff --git a/code/modules/admin/ticket.dm b/code/modules/admin/ticket.dm index d81533f601f..825d6261fd1 100644 --- a/code/modules/admin/ticket.dm +++ b/code/modules/admin/ticket.dm @@ -162,25 +162,28 @@ GLOBAL_LIST_EMPTY(ticket_panels) if (status != TICKET_CLOSED) return - if (!establish_db_connection(GLOB.dbcon)) + if (!SSdbcore.Connect()) return - var/DBQuery/Q = GLOB.dbcon.NewQuery({"INSERT INTO ss13_tickets + var/datum/db_query/query = SSdbcore.NewQuery({"INSERT INTO ss13_tickets (game_id, message_count, admin_count, admin_list, opened_by, taken_by, closed_by, response_delay, opened_at, closed_at) - VALUES - (:g_id:, :m_count:, :a_count:, :a_list:, :opened_by:, :taken_by:, :closed_by:, :delay:, :opened_at:, :closed_at:)"}) - Q.Execute(list( - "g_id" = GLOB.round_id, - "m_count" = length(msgs), - "a_count" = length(assigned_admins), - "a_list" = json_encode(assigned_admins), - "opened_by" = owner, - "taken_by" = length(assigned_admins) ? assigned_admins[1] : null, - "closed_by" = closed_by, - "delay" = response_time || -1, - "opened_at" = SQLtime(opened_rt), - "closed_at" = SQLtime(closed_rt) - )) + VALUES (:g_id, :m_count, :a_count, :a_list, :opened_by, :taken_by, :closed_by, :delay, :opened_at, :closed_at)"}, + list( + "g_id" = GLOB.round_id, + "m_count" = length(msgs), + "a_count" = length(assigned_admins), + "a_list" = json_encode(assigned_admins), + "opened_by" = owner, + "taken_by" = length(assigned_admins) ? assigned_admins[1] : null, + "closed_by" = closed_by, + "delay" = response_time || -1, + "opened_at" = SQLtime(opened_rt), + "closed_at" = SQLtime(closed_rt) + ), + TRUE + ) + query.SetSuccessCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + query.Execute() /datum/ticket/proc/append_message(m_from, m_to, msg) msgs += new /datum/ticket_msg(m_from, m_to, msg) diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 3ee9da10cb1..e8c058e2929 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -620,7 +620,7 @@ Traitors and the like can also be revived with the previous role mostly intact. if (!establish_db_connection(GLOB.dbcon)) to_chat(src, SPAN_NOTICE("Unable to connect to the database.")) return - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT title, message FROM ss13_ccia_general_notice_list WHERE deleted_at IS NULL") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT title, message FROM ss13_ccia_general_notice_list WHERE deleted_at IS NULL") query.Execute() var/list/template_names = list() @@ -630,6 +630,8 @@ Traitors and the like can also be revived with the previous role mostly intact. template_names += query.item[1] templates[query.item[1]] = query.item[2] + qdel(query) + // Catch empty list if (!templates.len) to_chat(src, SPAN_NOTICE("There are no templates in the database.")) diff --git a/code/modules/admin/verbs/warning.dm b/code/modules/admin/verbs/warning.dm index b770497f5d0..58cd80c0a35 100644 --- a/code/modules/admin/verbs/warning.dm +++ b/code/modules/admin/verbs/warning.dm @@ -243,34 +243,47 @@ * Called by /datum/preferences/proc/gather_notifications() in preferences.dm */ /client/proc/warnings_gather() - var/count = 0 - var/count_expire = 0 - - if (!establish_db_connection(GLOB.dbcon)) + if (!SSdbcore.Connect()) return var/list/client_details = list("ckey" = ckey, "computer_id" = computer_id, "address" = address) - var/DBQuery/expire_query = GLOB.dbcon.NewQuery("SELECT id FROM ss13_warnings WHERE (acknowledged = 1 AND expired = 0 AND DATE_SUB(CURDATE(),INTERVAL 3 MONTH) > time) AND (ckey = :ckey: OR computerid = :computer_id: OR ip = :address:)") - expire_query.Execute(client_details) - while (expire_query.NextRow()) - var/warning_id = text2num(expire_query.item[1]) - var/DBQuery/update_query = GLOB.dbcon.NewQuery("UPDATE ss13_warnings SET expired = 1 WHERE id = :warning_id:") - update_query.Execute(list("warning_id" = warning_id)) - count_expire++ + var/datum/db_query/expire_query = SSdbcore.NewQuery( + "SELECT id FROM ss13_warnings WHERE (acknowledged = 1 AND expired = 0 AND DATE_SUB(CURDATE(),INTERVAL 3 MONTH) > time) AND (ckey = :ckey OR computerid = :computer_id OR ip = :address)", + client_details) + expire_query.SetSuccessCallback(CALLBACK(src, PROC_REF(_warnings_expire_cb))) + expire_query.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + expire_query.ExecuteNoSleep(TRUE) - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT id FROM ss13_warnings WHERE (visible = 1 AND acknowledged = 0 AND expired = 0) AND (ckey = :ckey: OR computerid = :computer_id: OR ip = :address:)") - query.Execute(client_details) + var/datum/db_query/count_query = SSdbcore.NewQuery( + "SELECT id FROM ss13_warnings WHERE (visible = 1 AND acknowledged = 0 AND expired = 0) AND (ckey = :ckey OR computerid = :computer_id OR ip = :address)", + client_details) + count_query.SetSuccessCallback(CALLBACK(src, PROC_REF(_warnings_count_cb))) + count_query.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + count_query.ExecuteNoSleep(TRUE) + +/client/proc/_warnings_expire_cb(datum/db_query/query) + var/count_expire = 0 + while (query.NextRow()) + var/warning_id = text2num(query.item[1]) + var/datum/db_query/update_query = SSdbcore.NewQuery( + "UPDATE ss13_warnings SET expired = 1 WHERE id = :warning_id", + list("warning_id" = warning_id)) + update_query.SetSuccessCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + update_query.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + update_query.ExecuteNoSleep(TRUE) + count_expire++ + qdel(query) + if (count_expire) + prefs?.new_notification("info", "[count_expire] of your warnings have expired.") + +/client/proc/_warnings_count_cb(datum/db_query/query) + var/count = 0 while (query.NextRow()) count++ - - var/list/data = list("unread" = "", "expired" = "") + qdel(query) if (count) - data["unread"] = "You have [count] unread warning\s! Click here to review and acknowledge them!" - if (count_expire) - data["expired"] = "[count_expire] of your warnings have expired." - - return data + prefs?.new_notification("danger", "You have [count] unread warning\s! Click here to review and acknowledge them!", 1) /** * A proc used to gather if someone has Unacknowledged Warnings diff --git a/code/modules/cciaa/cciaa_items.dm b/code/modules/cciaa/cciaa_items.dm index a5b7d68df36..37ede897137 100644 --- a/code/modules/cciaa/cciaa_items.dm +++ b/code/modules/cciaa/cciaa_items.dm @@ -59,15 +59,16 @@ //If nothing has been done with the device yet if(!selected_report && !interviewee_id) - if(GLOB.config.sql_ccia_logs) + if(GLOB.config.sql_ccia_logs && SSdbcore.Connect()) //Get the active cases from the database and display them var/list/reports = list() - var/DBQuery/report_query = GLOB.dbcon.NewQuery("SELECT id, report_date, title, public_topic, internal_topic, game_id, status FROM ss13_ccia_reports WHERE status IN ('in progress', 'approved') AND deleted_at IS NULL") + var/datum/db_query/report_query = SSdbcore.NewQuery("SELECT id, report_date, title, public_topic, internal_topic, game_id, status FROM ss13_ccia_reports WHERE status IN ('in progress', 'approved') AND deleted_at IS NULL") report_query.Execute() while(report_query.NextRow()) CHECK_TICK var/datum/ccia_report/R = new(report_query.item[1], report_query.item[2], report_query.item[3], report_query.item[4], report_query.item[5], report_query.item[6], report_query.item[7]) reports["[report_query.item[1]] - [report_query.item[2]] - [report_query.item[3]]"] = R + qdel(report_query) var/selection = input(usr, "Select Report","Report Name") as null|anything in reports if(!selection) @@ -152,32 +153,33 @@ P.forceMove(get_turf(src.loc)) //If we have sql ccia logs enabled, then persist it here - if(GLOB.config.sql_ccia_logs && establish_db_connection(GLOB.dbcon)) + if(GLOB.config.sql_ccia_logs && SSdbcore.Connect()) //This query is split up into multiple parts due to the length limitations of byond. //To avoid this the text and the antag_involvement_text are saved separately - var/DBQuery/save_log = GLOB.dbcon.NewQuery("INSERT INTO ss13_ccia_reports_transcripts (id, report_id, character_id, interviewer, antag_involvement, text) VALUES (NULL, :report_id:, :character_id:, :interviewer:, :antag_involvement:, :text:)") - save_log.Execute(list("report_id" = selected_report.id, "character_id" = interviewee_id, "interviewer" = usr.name, "antag_involvement" = antag_involvement, "text" = P.info)) + var/datum/db_query/save_log = SSdbcore.NewQuery("INSERT INTO ss13_ccia_reports_transcripts (id, report_id, character_id, interviewer, antag_involvement, text) VALUES (NULL, :report_id, :character_id, :interviewer, :antag_involvement, :text)",list("report_id" = selected_report.id, "character_id" = interviewee_id, "interviewer" = usr.name, "antag_involvement" = antag_involvement, "text" = P.info),TRUE) + save_log.warn_execute() //Run the query to get the inserted id - var/transcript_id = null - var/DBQuery/tid = GLOB.dbcon.NewQuery("SELECT LAST_INSERT_ID() AS log_id") - tid.Execute() - if (tid.NextRow()) - transcript_id = text2num(tid.item[1]) + var/transcript_id = save_log.last_insert_id - if(tid) - var/DBQuery/add_text = GLOB.dbcon.NewQuery("UPDATE ss13_ccia_reports_transcripts SET text = :text: WHERE id = :id:") - add_text.Execute(list("id" = transcript_id, "text" = P.info)) - var/DBQuery/add_antag_involvement_text = GLOB.dbcon.NewQuery("UPDATE ss13_ccia_reports_transcripts SET antag_involvement_text = :antag_involvement_text: WHERE id = :id:") - add_antag_involvement_text.Execute(list("id" = transcript_id, "antag_involvement_text" = antag_involvement_text)) + if(transcript_id) + var/datum/db_query/add_text = SSdbcore.NewQuery("UPDATE ss13_ccia_reports_transcripts SET text = :text WHERE id = :id",list("id" = transcript_id, "text" = P.info),TRUE) + add_text.warn_execute() + qdel(add_text) + var/datum/db_query/add_antag_involvement_text = SSdbcore.NewQuery("UPDATE ss13_ccia_reports_transcripts SET antag_involvement_text = :antag_involvement_text WHERE id = :id",list("id" = transcript_id, "antag_involvement_text" = antag_involvement_text),TRUE) + add_antag_involvement_text.warn_execute() + qdel(add_antag_involvement_text) else message_cciaa("Transcript could not be saved correctly. TiD Missing") //Check if we need to update the status to review required if(antag_involvement && selected_report.status == "in progress") to_chat(usr, SPAN_NOTICE("The device beeps and flashes \"Liaison Review Required. Interviewee claimed antag involvement.\".")) - var/DBQuery/update_db = GLOB.dbcon.NewQuery("UPDATE ss13_ccia_reports SET status = 'review required' WHERE id = :id:") - update_db.Execute(list("id" = selected_report.id)) + var/datum/db_query/update_db = SSdbcore.NewQuery("UPDATE ss13_ccia_reports SET status = 'review required' WHERE id = :id",list("id" = selected_report.id),TRUE) + update_db.warn_execute() + qdel(update_db) + + qdel(save_log) sLogFile = null selected_report = null diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 79802fb4a6e..59fc92e424b 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -36,6 +36,7 @@ var/related_accounts_ip = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id var/whitelist_status = 0 //Used to determine what whitelists the player has access to. Uses bitflag values! + var/whitelist_status_loaded = FALSE //Set to TRUE once log_client_to_db() has written whitelist_status from the DB. Prevents false whitelist failures during login before the async query completes. var/need_saves_migrated = "Requires database" //Used to determine whether or not the ckey needs their saves migrated over to the database. Default is 0 upon successful connection. var/account_age = -1 // Age on the BYOND account in days. var/account_join_date = null // Date of the BYOND account creation in ISO 8601 format. diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 4158a27a974..2eabc2f53d1 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -145,13 +145,13 @@ GLOBAL_LIST_INIT(localhost_addresses, list( var/request_id = text2num(href_list["linkingrequest"]) - if (!establish_db_connection(GLOB.dbcon)) + if (!SSdbcore.Connect()) to_chat(src, SPAN_WARNING("Action failed! Database link could not be established!")) return - var/DBQuery/check_query = GLOB.dbcon.NewQuery("SELECT player_ckey, status FROM ss13_player_linking WHERE id = :id:") - check_query.Execute(list("id" = request_id)) + var/datum/db_query/check_query = SSdbcore.NewQuery("SELECT player_ckey, status FROM ss13_player_linking WHERE id = :id",list("id" = request_id)) + check_query.Execute() if (!check_query.NextRow()) to_chat(src, SPAN_WARNING("No request found!")) @@ -161,18 +161,20 @@ GLOBAL_LIST_INIT(localhost_addresses, list( to_chat(src, SPAN_WARNING("Request authentication failed!")) return + qdel(check_query) + var/query_contents = "" var/list/query_details = list("new_status", "id") var/feedback_message = "" switch (href_list["linkingaction"]) if ("accept") - query_contents = "UPDATE ss13_player_linking SET status = :new_status:, updated_at = NOW() WHERE id = :id:" + query_contents = "UPDATE ss13_player_linking SET status = :new_status, updated_at = NOW() WHERE id = :id" query_details["new_status"] = "confirmed" query_details["id"] = request_id feedback_message = SPAN_DANGER("Account successfully linked!") if ("deny") - query_contents = "UPDATE ss13_player_linking SET status = :new_status:, deleted_at = NOW() WHERE id = :id:" + query_contents = "UPDATE ss13_player_linking SET status = :new_status, deleted_at = NOW() WHERE id = :id" query_details["new_status"] = "rejected" query_details["id"] = request_id @@ -181,8 +183,9 @@ GLOBAL_LIST_INIT(localhost_addresses, list( to_chat(src, SPAN_WARNING("Invalid command sent.")) return - var/DBQuery/update_query = GLOB.dbcon.NewQuery(query_contents) - update_query.Execute(query_details) + var/datum/db_query/update_query = SSdbcore.NewQuery(query_contents,query_details) + update_query.Execute() + qdel(update_query) if (href_list["linkingaction"] == "accept" && alert("To complete the process, you have to visit the website. Do you want to do so now?",,"Yes","No") == "Yes") process_webint_link("interface/user/link") @@ -542,21 +545,35 @@ GLOBAL_LIST_INIT(localhost_addresses, list( ..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening return QDEL_HINT_HARDDEL_NOW -// here because it's similar to below -// Returns null if no DB connection can be established, or -1 if the requested key was not found in the database +/** + * Ensures whitelist_status is populated from the database. + * + * Idempotent: returns immediately if already loaded. + * Called by log_client_to_db() as part of the full player record fetch, + * and by the loadout sanitization to block until the status is available. + */ +/client/proc/load_whitelist_status() + if (whitelist_status_loaded) + return -/proc/get_player_age(key) - if(!establish_db_connection(GLOB.dbcon)) - return null + if (IsGuestKey(key) || !GLOB.config.sql_whitelists) + whitelist_status_loaded = TRUE + return - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT datediff(Now(),firstseen) as age FROM ss13_player WHERE ckey = :ckey:") - query.Execute(list("ckey"=ckey(key))) + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT whitelist_status FROM ss13_player WHERE ckey = :ckey", + list("ckey" = ckey(key)) + ) + if (!query.Execute()) + qdel(query) + whitelist_status_loaded = TRUE + return - if(query.NextRow()) - return text2num(query.item[1]) - else - return -1 + if (query.NextRow()) + whitelist_status = text2num(query.item[1]) + qdel(query) + whitelist_status_loaded = TRUE /client/proc/log_client_to_db() set waitfor = FALSE @@ -564,16 +581,17 @@ GLOBAL_LIST_INIT(localhost_addresses, list( if (IsGuestKey(src.key)) return - if(!establish_db_connection(GLOB.dbcon)) - return - - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT datediff(Now(),firstseen) as age, whitelist_status, account_join_date, DATEDIFF(NOW(), account_join_date), ckey_is_external FROM ss13_player WHERE ckey = :ckey:") - - if(!query.Execute(list("ckey"=ckey(key)))) + // --- Main player record lookup --- + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT datediff(Now(),firstseen), whitelist_status, account_join_date, DATEDIFF(NOW(), account_join_date), ckey_is_external FROM ss13_player WHERE ckey = :ckey", + list("ckey" = ckey(key)) + ) + if (!query.Execute()) + qdel(query) return var/found = 0 - player_age = 0 // New players won't have an entry so knowing we have a connection we set this to zero to be updated if their is a record. + player_age = 0 // New players won't have an entry; zero signals we have a connection but no record yet. if (query.NextRow()) found = 1 @@ -583,44 +601,85 @@ GLOBAL_LIST_INIT(localhost_addresses, list( account_age = text2num(query.item[4]) ckey_is_external = !!text2num(query.item[5]) if (!account_age) - account_join_date = sanitizeSQL(findJoinDate()) + account_join_date = findJoinDate() if (!account_join_date) account_age = -1 else - var/DBQuery/query_datediff = GLOB.dbcon.NewQuery("SELECT DATEDIFF(NOW(), [account_join_date])") + var/datum/db_query/query_datediff = SSdbcore.NewQuery( + "SELECT DATEDIFF(NOW(), :join_date)", + list("join_date" = account_join_date) + ) if (!query_datediff.Execute()) + qdel(query) + qdel(query_datediff) return if (query_datediff.NextRow()) account_age = text2num(query_datediff.item[1]) + qdel(query_datediff) + qdel(query) + whitelist_status_loaded = TRUE - var/DBQuery/query_ip = GLOB.dbcon.NewQuery("SELECT ckey FROM ss13_player WHERE ip = '[address]'") + // --- Related account lookups --- + var/datum/db_query/query_ip = SSdbcore.NewQuery( + "SELECT ckey FROM ss13_player WHERE ip = :ip", + list("ip" = address) + ) query_ip.Execute() related_accounts_ip = "" - while(query_ip.NextRow()) - related_accounts_ip += "[query_ip.item[1]], " - break + if (query_ip.NextRow()) + related_accounts_ip = "[query_ip.item[1]], " + qdel(query_ip) - var/DBQuery/query_cid = GLOB.dbcon.NewQuery("SELECT ckey FROM ss13_player WHERE computerid = '[computer_id]'") + var/datum/db_query/query_cid = SSdbcore.NewQuery( + "SELECT ckey FROM ss13_player WHERE computerid = :computerid", + list("computerid" = computer_id) + ) query_cid.Execute() related_accounts_cid = "" - while(query_cid.NextRow()) - related_accounts_cid += "[query_cid.item[1]], " - break + if (query_cid.NextRow()) + related_accounts_cid = "[query_cid.item[1]], " + qdel(query_cid) + // --- Write --- var/admin_rank = "Player" - if(src.holder) + if (src.holder) admin_rank = src.holder.rank - if(found) - //Player already identified previously, we need to just update the 'lastseen', 'ip', 'computer_id', 'byond_version' and 'byond_build' variables - var/DBQuery/query_update = GLOB.dbcon.NewQuery("UPDATE ss13_player SET lastseen = Now(), ip = :ip:, computerid = :computerid:, lastadminrank = :lastadminrank:, account_join_date = :account_join_date:, byond_version = :byond_version:, byond_build = :byond_build: WHERE ckey = :ckey:") - query_update.Execute(list("ckey"=ckey(key),"ip"=src.address,"computerid"=src.computer_id,"lastadminrank"=admin_rank,"account_join_date"=account_join_date,"byond_version"=byond_version,"byond_build"=byond_build)) + if (found) + var/datum/db_query/query_update = SSdbcore.NewQuery( + "UPDATE ss13_player SET lastseen = Now(), ip = :ip, computerid = :computerid, lastadminrank = :lastadminrank, account_join_date = :account_join_date, byond_version = :byond_version, byond_build = :byond_build WHERE ckey = :ckey", + list( + "ckey" = ckey(key), + "ip" = src.address, + "computerid" = src.computer_id, + "lastadminrank" = admin_rank, + "account_join_date" = account_join_date, + "byond_version" = byond_version, + "byond_build" = byond_build, + ) + ) + query_update.SetSuccessCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) + query_update.SetFailCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) + query_update.Execute() else if (!GLOB.config.access_deny_new_players) - //New player!! Need to insert all the stuff - var/DBQuery/query_insert = GLOB.dbcon.NewQuery("INSERT INTO ss13_player (ckey, ckey_is_external, firstseen, lastseen, ip, computerid, lastadminrank, account_join_date, byond_version, byond_build) VALUES (:ckey:, :ckey_is_external:, Now(), Now(), :ip:, :computerid:, :lastadminrank:, :account_join_date:, :byond_version:, :byond_build:)") - query_insert.Execute(list("ckey"=ckey(key), "ckey_is_external"=src.ckey_is_external,"ip"=src.address,"computerid"=src.computer_id,"lastadminrank"=admin_rank,"account_join_date"=account_join_date,"byond_version"=byond_version,"byond_build"=byond_build)) + var/datum/db_query/query_insert = SSdbcore.NewQuery( + "INSERT INTO ss13_player (ckey, ckey_is_external, firstseen, lastseen, ip, computerid, lastadminrank, account_join_date, byond_version, byond_build) VALUES (:ckey, :ckey_is_external, Now(), Now(), :ip, :computerid, :lastadminrank, :account_join_date, :byond_version, :byond_build)", + list( + "ckey" = ckey(key), + "ckey_is_external" = src.ckey_is_external, + "ip" = src.address, + "computerid" = src.computer_id, + "lastadminrank" = admin_rank, + "account_join_date" = account_join_date, + "byond_version" = byond_version, + "byond_build" = byond_build, + ) + ) + query_insert.SetSuccessCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) + query_insert.SetFailCallback(CALLBACK(GLOBAL_PROC, /proc/qdel)) + query_insert.Execute() else - // Flag as -1 to know we have to kiiick them. + // Flag as -1 to signal this player must be kicked (new players denied). player_age = -1 if (!account_join_date) @@ -746,18 +805,20 @@ GLOBAL_LIST_INIT(localhost_addresses, list( if (!GLOB.config.webint_url || !GLOB.config.sql_enabled) return - if (!establish_db_connection(GLOB.dbcon)) + if (!SSdbcore.Connect()) return var/list/requests = list() var/list/query_details = list("ckey" = ckey) - var/DBQuery/select_query = GLOB.dbcon.NewQuery("SELECT id, forum_id, forum_username, datediff(Now(), created_at) as request_age FROM ss13_player_linking WHERE status = 'new' AND player_ckey = :ckey: AND deleted_at IS NULL") - select_query.Execute(query_details) + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id, forum_id, forum_username, datediff(Now(), created_at) as request_age FROM ss13_player_linking WHERE status = 'new' AND player_ckey = :ckey AND deleted_at IS NULL",query_details) + select_query.Execute() while (select_query.NextRow()) requests.Add(list(list("id" = text2num(select_query.item[1]), "forum_id" = text2num(select_query.item[2]), "forum_username" = select_query.item[3], "request_age" = select_query.item[4]))) + qdel(select_query) + if (!requests.len) return @@ -783,17 +844,18 @@ GLOBAL_LIST_INIT(localhost_addresses, list( if (!GLOB.config.webint_url || !GLOB.config.sql_enabled) return - if (!establish_db_connection(GLOB.dbcon)) + if (!SSdbcore.Connect()) return - var/DBQuery/select_query = GLOB.dbcon.NewQuery("SELECT COUNT(*) AS request_count FROM ss13_player_linking WHERE status = 'new' AND player_ckey = :ckey: AND deleted_at IS NULL") - select_query.Execute(list("ckey" = ckey)) + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT COUNT(*) AS request_count FROM ss13_player_linking WHERE status = 'new' AND player_ckey = :ckey AND deleted_at IS NULL", list("ckey" = ckey)) + select_query.SetSuccessCallback(CALLBACK(src, PROC_REF(_gather_linking_requests_cb))) + select_query.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + select_query.ExecuteNoSleep(TRUE) - if (select_query.NextRow()) - if (text2num(select_query.item[1]) > 0) - return "You have [select_query.item[1]] account linking requests pending review. Click here to see them!" - - return null +/client/proc/_gather_linking_requests_cb(datum/db_query/query) + if (query.NextRow() && text2num(query.item[1]) > 0) + prefs?.new_notification("info", "You have [query.item[1]] account linking requests pending review. Click here to see them!", callback_src = src, callback_proc = "check_linking_requests") + qdel(query) /client/proc/process_webint_link(var/route, var/attributes) if (!route) @@ -838,34 +900,46 @@ GLOBAL_LIST_INIT(localhost_addresses, list( send_link(src, linkURL) return + /client/proc/check_ip_intel() - set waitfor = 0 //we sleep when getting the intel, no need to hold up the client connection while we sleep + set waitfor = 0 if (GLOB.config.ipintel_email) - var/datum/ipintel/res = get_ip_intel(address) - if (GLOB.config.ipintel_rating_kick && res.intel >= GLOB.config.ipintel_rating_kick) - if (!holder) - message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a proxy/VPN. They are being kicked because of this.") - log_admin("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a proxy/VPN. They are being kicked because of this.") - to_chat(src, SPAN_DANGER("Usage of proxies is not permitted by the rules. You are being kicked because of this.")) - del(src) - else - message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.") - else if (res.intel >= GLOB.config.ipintel_rating_bad) + SSipintel.get_ip_intel(address, on_complete = CALLBACK(src, /client/proc/on_ip_intel_result)) + +/client/proc/on_ip_intel_result(datum/ipintel/res) + if (GLOB.config.ipintel_rating_kick && res.intel >= GLOB.config.ipintel_rating_kick) + if (!holder) + message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a proxy/VPN. They are being kicked because of this.") + log_admin("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a proxy/VPN. They are being kicked because of this.") + to_chat(src, SPAN_DANGER("Usage of proxies is not permitted by the rules. You are being kicked because of this.")) + del(src) + else message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.") - ip_intel = res.intel + else if (res.intel >= GLOB.config.ipintel_rating_bad) + message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.") + ip_intel = res.intel /client/proc/findJoinDate() - var/list/http = world.Export("http://byond.com/members/[ckey]?format=text") - if(!http) + var/datum/http_request/req = new() + req.prepare(RUSTG_HTTP_METHOD_GET, "https://byond.com/members/[ckey]?format=text") + req.begin_async() + + // Poll RUSTG directly rather than routing through SShttp — this proc can be called + // before SShttp is firing (e.g. during MC init), so we cannot rely on SShttp's fire + // loop to dispatch the callback. + while (!req.is_complete()) + sleep(world.tick_lag) + + var/datum/http_response/http = req.into_response() + if (http.errored || http.status_code != 200) LOG_DEBUG("ACCESS CONTROL: Failed to connect to byond age check for [ckey]") return - var/F = file2text(http["CONTENT"]) - if(F) - var/regex/R = regex("joined = \"(\\d{4}-\\d{2}-\\d{2})\"") - if(R.Find(F)) - . = R.group[1] - else - CRASH("Age check regex failed for [src.ckey]") + + var/regex/R = regex("joined = \"(\\d{4}-\\d{2}-\\d{2})\"") + if (R.Find(http.body)) + return R.group[1] + else + CRASH("Age check regex failed for [src.ckey]") /client/Click(atom/object, atom/location, control, params) SEND_SIGNAL(src, COMSIG_CLIENT_CLICK, object, location, control, params, usr) diff --git a/code/modules/client/preference_setup/loadout/loadout.dm b/code/modules/client/preference_setup/loadout/loadout.dm index f0dd4f4e48c..3cc9bb8d735 100644 --- a/code/modules/client/preference_setup/loadout/loadout.dm +++ b/code/modules/client/preference_setup/loadout/loadout.dm @@ -104,6 +104,8 @@ GLOBAL_LIST_INIT(gear_datums, list()) var/list/whitelist_cache = list() if(preference_mob) + if(preference_mob.client) + preference_mob.client.load_whitelist_status() for(var/species in GLOB.all_species) var/datum/species/S = GLOB.all_species[species] if(is_alien_whitelisted(preference_mob, S)) diff --git a/code/modules/client/preferences_notification.dm b/code/modules/client/preferences_notification.dm index 9e9d42ffc94..d9ac155b217 100644 --- a/code/modules/client/preferences_notification.dm +++ b/code/modules/client/preferences_notification.dm @@ -139,113 +139,87 @@ new_notification("info", "You have unread updates in the changelog.") if (GLOB.config.sql_enabled) - - var/list/warnings = user.warnings_gather() - if (warnings["unread"]) - new_notification("danger", warnings["unread"], 1) - if (warnings["expired"]) - new_notification("info", warnings["expired"]) - - var/linking = user.gather_linking_requests() - if (linking) - new_notification("info", linking, callback_src = user, callback_proc = "check_linking_requests") - - var/cciaa_actions = count_ccia_actions(user) - if (cciaa_actions) - new_notification("info", cciaa_actions) - + user.warnings_gather() + user.gather_linking_requests() + count_ccia_actions(user) add_active_notifications(user) /datum/preferences/proc/add_active_notifications(var/client/user) - if(!user) - return null + if (!user) + return - if (!establish_db_connection(GLOB.dbcon)) - log_world("ERROR: Error initiatlizing database connection while getting notifications.") - return null + if (!SSdbcore.Connect()) + return - var/DBQuery/query = GLOB.dbcon.NewQuery({"SELECT - message, type, id - FROM ss13_player_notifications - WHERE acked_at IS NULL AND ckey = :ckey: - "}) - query.Execute(list("ckey" = user.ckey)) + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT message, type, id FROM ss13_player_notifications WHERE acked_at IS NULL AND ckey = :ckey", + list("ckey" = user.ckey)) + query.SetSuccessCallback(CALLBACK(src, PROC_REF(_add_active_notifications_cb), user)) + query.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + query.ExecuteNoSleep(TRUE) - var/chat_notification=0 - var/panel_notification=0 - var/notification_count=0 - - while(query.NextRow()) - var/autoack=0 - //Lets loop through the results +/datum/preferences/proc/_add_active_notifications_cb(var/client/user, datum/db_query/query) + var/chat_notification = 0 + var/panel_notification = 0 + var/notification_count = 0 + while (query.NextRow()) + var/autoack = 0 switch(query.item[2]) if("player_greeting") - panel_notification=1 + panel_notification = 1 notification_count++ if("player_greeting_chat") - chat_notification=1 - panel_notification=1 + chat_notification = 1 + panel_notification = 1 notification_count++ if("admin") SSdiscord.send_to_admins("Server Notification for [user.ckey]: [query.item[1]]") SSdiscord.post_webhook_event(WEBHOOK_ADMIN, list("title"="Server Notification for: [user.ckey]", "message"="Server Notification Triggered for [user.ckey]: [query.item[1]]")) - //Immediately ack the notification - autoack=1 + autoack = 1 if("ccia") SSdiscord.send_to_cciaa("Server Notification for [user.ckey]: [query.item[1]]") SSdiscord.post_webhook_event(WEBHOOK_CCIAA_EMERGENCY_MESSAGE, list("title"="Server Notification for: [user.ckey]", "message"="Server Notification Triggered for [user.ckey]: [query.item[1]]")) - //Immeidately ack the notification - autoack=1 - if(autoack) - var/DBQuery/ackquery = GLOB.dbcon.NewQuery({"UPDATE ss13_player_notifications - SET acked_by = 'autoack-server', acked_at = NOW() - WHERE id = :id: - "}) - ackquery.Execute(list("id" = query.item[3])) - if(panel_notification) - new_notification("warning","You have [notification_count] unread notifications! Click here to review and acknowledge them!") - if(chat_notification) - to_chat(user,SPAN_WARNING("You have unacknowledged notifications.
Click here to review and acknowledge them!")) + autoack = 1 + if (autoack) + var/datum/db_query/ackquery = SSdbcore.NewQuery( + "UPDATE ss13_player_notifications SET acked_by = 'autoack-server', acked_at = NOW() WHERE id = :id", + list("id" = query.item[3])) + ackquery.SetSuccessCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + ackquery.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + ackquery.ExecuteNoSleep(TRUE) + qdel(query) + if (panel_notification) + new_notification("warning", "You have [notification_count] unread notifications! Click here to review and acknowledge them!") + if (chat_notification) + to_chat(user, SPAN_WARNING("You have unacknowledged notifications.
Click here to review and acknowledge them!")) /* * Helper proc for getting a count of active CCIA actions against the player's characters. */ /datum/preferences/proc/count_ccia_actions(var/client/user) if (!user) - return null + return - if (!establish_db_connection(GLOB.dbcon)) - log_world("ERROR: Error initiatlizing database connection while counting CCIA actions.") - return null + if (!SSdbcore.Connect()) + return - var/DBQuery/prep_query = GLOB.dbcon.NewQuery("SELECT id FROM ss13_characters WHERE ckey = :ckey:") - prep_query.Execute(list("ckey" = user.ckey)) - var/list/chars = list() - - while (prep_query.NextRow()) - chars += text2num(prep_query.item[1]) - - if (!chars.len) - return null - - var/DBQuery/query = GLOB.dbcon.NewQuery({"SELECT - COUNT(act_chr.action_id) AS action_count - FROM ss13_ccia_action_char act_chr - JOIN ss13_characters chr ON act_chr.char_id = chr.id - JOIN ss13_ccia_actions act ON act_chr.action_id = act.id - WHERE - act_chr.char_id IN :char_id: AND - (act.expires_at IS NULL OR act.expires_at >= CURRENT_DATE()) AND - act.deleted_at IS NULL;"}) - query.Execute(list("char_id" = chars)) + var/datum/db_query/actions_query = SSdbcore.NewQuery( + {"SELECT COUNT(act_chr.action_id) AS action_count + FROM ss13_ccia_action_char act_chr + JOIN ss13_characters chr ON act_chr.char_id = chr.id + JOIN ss13_ccia_actions act ON act_chr.action_id = act.id + WHERE + chr.ckey = :ckey AND + (act.expires_at IS NULL OR act.expires_at >= CURRENT_DATE()) AND + act.deleted_at IS NULL;"}, + list("ckey" = user.ckey)) + actions_query.SetSuccessCallback(CALLBACK(src, PROC_REF(_count_ccia_actions_cb))) + actions_query.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + actions_query.ExecuteNoSleep(TRUE) +/datum/preferences/proc/_count_ccia_actions_cb(datum/db_query/query) if (query.NextRow()) var/action_count = text2num(query.item[1]) - - if (action_count == 0) - return null - - var/string = "There are [action_count] active CCIA actions currently active against your character(s)." - return string - - return null + if (action_count > 0) + new_notification("info", "There are [action_count] active CCIA actions currently active against your character(s).") + qdel(query) diff --git a/code/modules/events/ccia_general_notice.dm b/code/modules/events/ccia_general_notice.dm index fdf2c7930ec..b84ddf4094b 100644 --- a/code/modules/events/ccia_general_notice.dm +++ b/code/modules/events/ccia_general_notice.dm @@ -7,16 +7,17 @@ /datum/event/ccia_general_notice/start() ..() - if (!establish_db_connection(GLOB.dbcon)) - LOG_DEBUG("CCIA Autoamtic General Notice - Could not establish database connection") + if (!SSdbcore.Connect()) + LOG_DEBUG("CCIA Automatic General Notice - Could not establish database connection") return - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT SQL_NO_CACHE title, message FROM ss13_ccia_general_notice_list WHERE deleted_at IS NULL AND automatic = 1 ORDER BY RAND() LIMIT 1;") + var/datum/db_query/query = SSdbcore.NewQuery("SELECT SQL_NO_CACHE title, message FROM ss13_ccia_general_notice_list WHERE deleted_at IS NULL AND automatic = 1 ORDER BY RAND() LIMIT 1;") query.Execute() if (query.NextRow()) reporttitle = query.item[1] reportbody = query.item[2] reportbody += "\n\n- CCIAAMS, [commstation_name()]" announce = 1 + qdel(query) /datum/event/ccia_general_notice/announce() if(announce) diff --git a/code/modules/mob/abstract/new_player/menu.dm b/code/modules/mob/abstract/new_player/menu.dm index 8f41463be03..77043d0eb48 100644 --- a/code/modules/mob/abstract/new_player/menu.dm +++ b/code/modules/mob/abstract/new_player/menu.dm @@ -407,15 +407,18 @@ ABSTRACT_TYPE(/atom/movable/screen/new_player/selection) /atom/movable/screen/new_player/selection/polls/Initialize() . = ..() - if(establish_db_connection(GLOB.dbcon)) + if(SSdbcore.Connect()) var/mob/M = hud.mymob var/isadmin = M && M.client && M.client.holder - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT id FROM ss13_poll_question WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM ss13_poll_vote WHERE ckey = \"[M.ckey]\") AND id NOT IN (SELECT pollid FROM ss13_poll_textreply WHERE ckey = \"[M.ckey]\")") - query.Execute() - var/newpoll = query.NextRow() + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id FROM ss13_poll_question WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM ss13_poll_vote WHERE ckey = :ckey) AND id NOT IN (SELECT pollid FROM ss13_poll_textreply WHERE ckey = :ckey) LIMIT 0,1", list("ckey" = M.ckey)) + query.SetSuccessCallback(CALLBACK(src, PROC_REF(_polls_check_cb))) + query.SetFailCallback(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel))) + query.ExecuteNoSleep(TRUE) - if(newpoll) - new_polls = TRUE +/atom/movable/screen/new_player/selection/polls/proc/_polls_check_cb(datum/db_query/query) + if (query.NextRow()) + new_polls = TRUE + qdel(query) /atom/movable/screen/new_player/selection/polls/Click() var/mob/abstract/new_player/player = usr diff --git a/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm b/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm index bdcac6840f0..f6cf335f022 100644 --- a/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm +++ b/code/modules/mob/living/carbon/human/species/station/ipc/ipc.dm @@ -303,15 +303,15 @@ if (!target || !player) return - if (establish_db_connection(GLOB.dbcon) && target.character_id) + if (SSdbcore.Connect() && target.character_id) var/status = FALSE var/sql_status = FALSE if (target.internal_organs_by_name[BP_IPCTAG]) status = TRUE var/list/query_details = list("char_id" = target.character_id) - var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT tag_status FROM ss13_characters_ipc_tags WHERE char_id = :char_id:") - query.Execute(query_details) + var/datum/db_query/query = SSdbcore.NewQuery("SELECT tag_status FROM ss13_characters_ipc_tags WHERE char_id = :char_id",query_details) + query.Execute() if (query.NextRow()) sql_status = text2num(query.item[1]) @@ -319,8 +319,10 @@ return query_details["status"] = status - var/DBQuery/update_query = GLOB.dbcon.NewQuery("UPDATE ss13_characters_ipc_tags SET tag_status = :status: WHERE char_id = :char_id:") - update_query.Execute(query_details) + var/datum/db_query/update_query = SSdbcore.NewQuery("UPDATE ss13_characters_ipc_tags SET tag_status = :status: WHERE char_id = :char_id",query_details) + update_query.Execute() + qdel(update_query) + qdel(query) /datum/species/machine/get_light_color(mob/living/carbon/human/H) if (!istype(H)) diff --git a/code/modules/tgui_panel/telemetry.dm b/code/modules/tgui_panel/telemetry.dm index d3e8e281eb6..2cad8e19fdd 100644 --- a/code/modules/tgui_panel/telemetry.dm +++ b/code/modules/tgui_panel/telemetry.dm @@ -65,5 +65,27 @@ if (!ckey) return + // Convert assoc-list connections to positional format for apply_ban_mirror, + // which reads extra_info[i][1] to extract ckeys for DB storage. + var/list/extra_info = list() + for(var/list/conn in telemetry_connections) + extra_info += list(list(conn["ckey"], conn["address"], conn["computer_id"])) + + var/ban_id = 0 + for(var/list/conn in telemetry_connections) + var/list/bdata = world.IsBanned(conn["ckey"], conn["address"], conn["computer_id"], 1, real_bans_only = TRUE, log_connection = FALSE) + if(bdata && bdata.len && !isnull(bdata["id"])) + ban_id = bdata["id"] + break + + if(ban_id) + if(!client.holder) + log_and_message_admins("[ckey] from [client.address]-[client.computer_id] was caught bandodging. Mirror applied for ban #[ban_id], kicking shortly.") + apply_ban_mirror(ckey, client.address, client.computer_id, ban_id, 2, extra_info) + spawn(20) + del(client) + else + log_and_message_admins("[ckey] is a staff but was caught bandodging! Ban ID: #[ban_id].") + #undef TGUI_TELEMETRY_MAX_CONNECTIONS #undef TGUI_TELEMETRY_RESPONSE_WINDOW diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index 05ebeb692dd..4271f2617c4 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -89,6 +89,9 @@ if(type == "audio/setAdminMusicVolume") client.admin_music_volume = payload["volume"] return TRUE + if(type == "telemetry") + analyze_telemetry(payload) + return TRUE /** * public * diff --git a/code/modules/web_interface/webint_procs.dm b/code/modules/web_interface/webint_procs.dm index 5df37007e43..0bd3a905f56 100644 --- a/code/modules/web_interface/webint_procs.dm +++ b/code/modules/web_interface/webint_procs.dm @@ -82,13 +82,15 @@ alert("An error occured while attempting to connect to the database!") return 0 - var/DBQuery/insert_query = GLOB.dbcon.NewQuery("INSERT INTO ss13_web_sso (ckey, token, ip, created_at) VALUES (:ckey:, :token:, :ip:, NOW())") - insert_query.Execute(list("ckey" = user.ckey, "token" = token, "ip" = user.address)) + var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO ss13_web_sso (ckey, token, ip, created_at) VALUES (:ckey, :token, :ip, NOW())",list("ckey" = user.ckey, "token" = token, "ip" = user.address)) - if (insert_query.ErrorMsg()) + if (!insert_query.Execute()) alert("An error occured while trying to upload the session data!") + qdel(insert_query) return 0 + qdel(insert_query) + if (alert("This will take you to the webpage and log you in. Do you wish to proceed?",,"Yes","No") == "No") return 0 diff --git a/config/example/logging.json b/config/example/logging.json index afa00af4801..b3c0a5a36d9 100644 --- a/config/example/logging.json +++ b/config/example/logging.json @@ -47,5 +47,6 @@ "log_subsystems_zas":0, "log_subsystems_zas_debug":0, "log_subsystems_http":1, + "log_subsystems_ipintel":1, "log_subsystems_odyssey":1 } diff --git a/config/example/logging_files.json b/config/example/logging_files.json index 49410734f0c..9d368134bc4 100644 --- a/config/example/logging_files.json +++ b/config/example/logging_files.json @@ -46,5 +46,6 @@ "world_modules_ruins_log":"modules/ruins.log", "world_subsystems_zas":"subsystems/zas.log", "world_subsystems_zas_debug":"subsystems/zas.log", - "world_subsystems_http":"subsystems/http.log" + "world_subsystems_http":"subsystems/http.log", + "world_subsystems_ipintel":"subsystems/ipintel.log" } diff --git a/html/changelogs/arrow768-2026-03-26-refactor-ipintel.yml b/html/changelogs/arrow768-2026-03-26-refactor-ipintel.yml new file mode 100644 index 00000000000..2e5639dc19f --- /dev/null +++ b/html/changelogs/arrow768-2026-03-26-refactor-ipintel.yml @@ -0,0 +1,10 @@ +author: arrow768 +delete-after: True +changes: + - refactor: "Refactors IPIntel (XKeyScore) to use the SShttp subsystem for async HTTP requests instead of world.Export." + - refactor: "Moves IPIntel logic into the SSipintel subsystem as proper subsystem procs." + - code_imp: "IPIntel database queries now use SSdbcore with parameterized queries instead of GLOB.dbcon string interpolation." + - config: "Adds a dedicated IPIntel subsystem log (subsystems/ipintel.log) controlled by log_subsystems_ipintel in logging.json." + - code_imp: "Refactors BYOND account age lookup (findJoinDate) to use async RUSTG HTTP instead of world.Export." + - code_imp: "Refactors client login DB logging (log_client_to_db) to use SSdbcore with parameterized queries." + - code_imp: "Removes unused get_player_age proc." diff --git a/html/changelogs/arrow768-minor-dbcore-stuff.yml b/html/changelogs/arrow768-minor-dbcore-stuff.yml new file mode 100644 index 00000000000..061880e8bf5 --- /dev/null +++ b/html/changelogs/arrow768-minor-dbcore-stuff.yml @@ -0,0 +1,71 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# - (fixes bugs) +# wip +# - (work in progress) +# qol +# - (quality of life) +# soundadd +# - (adds a sound) +# sounddel +# - (removes a sound) +# rscadd +# - (adds a feature) +# rscdel +# - (removes a feature) +# imageadd +# - (adds an image or sprite) +# imagedel +# - (removes an image or sprite) +# spellcheck +# - (fixes spelling or grammar) +# experiment +# - (experimental change) +# balance +# - (balance changes) +# code_imp +# - (misc internal code change) +# refactor +# - (refactors code) +# config +# - (makes a change to the config files) +# admin +# - (makes changes to administrator tools) +# server +# - (miscellaneous changes to server) +################################# + +# Your name. +author: arrow768 + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: +- server: "Changed the synthsprites to use the new DBCore" +- server: "Changed the MalfAI to use the new DBCore" +- server: "Changed the Alien Whitelists to use the new DBCore" +- server: "Changed the Requests Console to use the new DBCore" +- server: "Changed the Contracts Uplink to use the new DBCore" +- server: "Changed the Admin Ranks to use the new DBCore" +- server: "Changed the Job Bans to use the new DBCore" +- server: "Changed the Tickets to use the new DBCore" +- server: "Changed the Create Command Report to use the new DBCore" +- server: "Changed the WebInterface interconnect to use the new DBCore" +- server: "Changed the CCIA Recorder to use the new DBCore" +- server: "Changed the IPCTags to use the new DBCore" +- server: "Changed the Main Menu Poll-Check to use the new DBCore" +- server: "Changed the Client-Procs to use the new DBCore"