diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index e30c9a73d21..67d9a70bb19 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -306,7 +306,7 @@ #define TRIGGER_GUARD_NORMAL 1 // Macro to get the current elapsed round time, rather than total world runtime -#define ROUND_TIME (SSticker.round_start_time ? (world.time - SSticker.round_start_time) : 0) +#define ROUND_TIME (SSticker.time_game_started ? (world.time - SSticker.time_game_started) : 0) // Macro that returns true if it's too early in a round to freely ghost out #define TOO_EARLY_TO_GHOST (ROUND_TIME < GLOB.configuration.general.cryo_penalty_period MINUTES) diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index 3d7d14e59ce..14add1b8308 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -15,10 +15,6 @@ GLOBAL_VAR(church_name) return name -// AA TODO: Remove this. Its always gonna be NAS Trurl -/proc/command_name() - return "NAS Trurl" - GLOBAL_VAR(religion_name) /proc/religion_name() if(GLOB.religion_name) @@ -139,7 +135,7 @@ GLOBAL_VAR(syndicate_code_response) //Code response for traitors. var/safety[] = list(1,2,3)//Tells the proc which options to remove later on. var/nouns[] = list("love","hate","anger","peace","pride","sympathy","bravery","loyalty","honesty","integrity","compassion","charity","success","courage","deceit","skill","beauty","brilliance","pain","misery","beliefs","dreams","justice","truth","faith","liberty","knowledge","thought","information","culture","trust","dedication","progress","education","hospitality","leisure","trouble","friendships", "relaxation") var/drinks[] = list("vodka and tonic","gin fizz","bahama mama","manhattan","black Russian","whiskey soda","long island tea","margarita","Irish coffee"," manly dwarf","Irish cream","doctor's delight","Beepksy Smash","tequila sunrise","brave bull","gargle blaster","bloody mary","whiskey cola","white Russian","vodka martini","martini","Cuba libre","kahlua","vodka","wine","moonshine") - var/locations[] = GLOB.teleportlocs.len ? GLOB.teleportlocs : drinks//if null, defaults to drinks instead. + var/locations[] = length(SSmapping.teleportlocs) ? SSmapping.teleportlocs : drinks//if null, defaults to drinks instead. var/names[] = list() for(var/datum/data/record/t in GLOB.data_core.general)//Picks from crew manifest. diff --git a/code/_globalvars/mapping.dm b/code/_globalvars/mapping.dm index caf4a2a58bb..2e447d0484a 100644 --- a/code/_globalvars/mapping.dm +++ b/code/_globalvars/mapping.dm @@ -54,6 +54,3 @@ GLOBAL_LIST_EMPTY(lava_ruins_templates) GLOBAL_LIST_EMPTY(shelter_templates) GLOBAL_LIST_EMPTY(shuttle_templates) -// Teleport locations -GLOBAL_LIST_EMPTY(teleportlocs) -GLOBAL_LIST_EMPTY(ghostteleportlocs) diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index 92f02a404a1..d16aae208d2 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -3,13 +3,374 @@ SUBSYSTEM_DEF(ipintel) wait = 1 flags = SS_NO_FIRE init_order = INIT_ORDER_XKEYSCORE // 10 - var/enabled = 0 //disable at round start to avoid checking reconnects + // Are we enabled? Auto disable at world init to avoid checking reconnects + var/enabled = FALSE var/throttle = 0 var/errors = 0 var/list/cache = list() -/datum/controller/subsystem/ipintel/Initialize(timeofday, zlevel) - enabled = 1 - . = ..() +/datum/controller/subsystem/ipintel/Initialize(timeofday) + enabled = TRUE + return ..() + +// Represents an IP intel holder datum +/datum/ipintel + /// The IP being checked + var/ip + /// The current rating, 0-1 float. + var/intel = 0 + /// Whether this was loaded from the cache or not + var/cache = FALSE + /// How many minutes ago it was cached + var/cacheminutesago = 0 + /// The date it was cached + var/cachedate = "" + /// The real time it was cached + var/cacherealtime = 0 + +/datum/ipintel/New() + cachedate = SQLtime() + cacherealtime = world.realtime + +/datum/ipintel/proc/is_valid() + . = FALSE + if(intel < 0) + return + if(intel <= GLOB.configuration.ipintel.bad_rating) + if(world.realtime < cacherealtime + (GLOB.configuration.ipintel.hours_save_good HOURS)) + return TRUE + else + if(world.realtime < cacherealtime + (GLOB.configuration.ipintel.hours_save_bad HOURS)) + return TRUE + + + +/** + * Get IP intel + * + * Performs a lookup of the rating for an IP provided + * + * Arguments: + * * ip - The IP to lookup + * * bypasscache - Do we want to bypass the DB cache? + * * updatecache - Do we want to update the DB cache? + */ +/datum/controller/subsystem/ipintel/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE) + var/datum/ipintel/res = new() + res.ip = ip + . = res + if(!ip || !GLOB.configuration.ipintel.contact_email || !GLOB.configuration.ipintel.enabled || !enabled) + return + if(!bypasscache) + var/datum/ipintel/cachedintel = cache[ip] + if(cachedintel && cachedintel.is_valid()) + cachedintel.cache = TRUE + return cachedintel + + if(SSdbcore.IsConnected()) + var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({" + SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) + FROM ipintel + WHERE + ip = INET_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.configuration.ipintel.bad_rating, + "save_good" = GLOB.configuration.ipintel.hours_save_good, + "save_bad" = GLOB.configuration.ipintel.hours_save_bad, + )) + if(!query_get_ip_intel.warn_execute()) + qdel(query_get_ip_intel) + 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) + return + qdel(query_get_ip_intel) + res.intel = ip_intel_query(ip) + if(updatecache && res.intel >= 0) + cache[ip] = res + if(SSdbcore.IsConnected()) + var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery({" + INSERT INTO ipintel (ip, intel) VALUES (INET_ATON(:ip), :intel) + ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()"}, + list( + "ip" = ip, + "intel" = res.intel + ) + ) + query_add_ip_intel.warn_execute() + qdel(query_add_ip_intel) + + + +/** + * Performs the remote IPintel lookup + * + * + * + * Arguments: + * * ip - The IP to lookup + * * retried - Was this attempt retried? + */ +/datum/controller/subsystem/ipintel/proc/ip_intel_query(ip, retried = FALSE) + . = -1 //default + if(!ip) + return + if(throttle > world.timeofday) + return + if(!enabled) + return + + // Do not refactor this to use SShttp, because that requires the subsystem to be firing for requests to be made, and this will be triggered before the MC has finished loading + var/list/http[] = world.Export("http://[GLOB.configuration.ipintel.ipintel_domain]/check.php?ip=[ip]&contact=[GLOB.configuration.ipintel.contact_email]&format=json&flags=b") + + 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, retried) + if(!retried) + sleep(25) + return .(ip, 1) + else + ipintel_handle_error("Bad response from server: [response["status"]].", ip, retried) + if(!retried) + 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, retried) + if(!retried) + sleep(25) + return .(ip, 1) + else + ipintel_handle_error("Unable to connect to API.", ip, retried) + if(!retried) + sleep(25) + return .(ip, 1) + + + +/** + * Error handler + * + * Handles an IP intel error, also throttling the susbystem if required + * + * Arguments: + * * error - The error description + * * ip - The IP that was tried + * * retried - Was this on a retried attempt + */ +/datum/controller/subsystem/ipintel/proc/ipintel_handle_error(error, ip, retried) + if(retried) + errors++ + error += " Could not check [ip]. Disabling IPINTEL for [errors] minute[(errors == 1 ? "" : "s")]" + throttle = world.timeofday + (2 * errors MINUTES) + else + error += " Attempting retry on [ip]." + log_ipintel(error) + + + +/** + * Logs an IPintel error + * + * Pretty self explanatory. Logs errors regarding ipintel. + * + * Arguments: + * * text - Argument 1 + */ +/datum/controller/subsystem/ipintel/proc/log_ipintel(text) + log_game("IPINTEL: [text]") + log_debug("IPINTEL: [text]") + + + +/** + * IPIntel Ban Checker + * + * Checks if a user is banned due to IPintel. It will check configuration, DB, whitelist checks, and more + * + * Arguments: + * * t_ckey - The ckey to check + * * t_ip - The IP to check + */ +/datum/controller/subsystem/ipintel/proc/ipintel_is_banned(t_ckey, t_ip) + if(!GLOB.configuration.ipintel.contact_email) + return FALSE + if(!GLOB.configuration.ipintel.enabled) + return FALSE + if(!GLOB.configuration.ipintel.whitelist_mode) + return FALSE + if(!SSdbcore.IsConnected()) + return FALSE + if(!ipintel_badip_check(t_ip)) + return FALSE + if(vpn_whitelist_check(t_ckey)) + return FALSE + return TRUE + + + +/** + * IP Rating Checker + * + * Checks if a provided IP passes the config threshold for denial + * + * Arguments: + * * target_ip - The IP to check + */ +/datum/controller/subsystem/ipintel/proc/ipintel_badip_check(target_ip) + var/rating_bad = GLOB.configuration.ipintel.bad_rating + if(!rating_bad) + log_debug("ipintel_badip_check reports misconfigured rating_bad directive") + return FALSE + var/valid_hours = GLOB.configuration.ipintel.hours_save_bad + if(!valid_hours) + log_debug("ipintel_badip_check reports misconfigured ipintel_save_bad directive") + return FALSE + var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({" + SELECT * FROM ipintel WHERE ip = INET_ATON(:target_ip) + AND intel >= :rating_bad AND (date + INTERVAL :valid_hours HOUR) > NOW()"}, + list( + "target_ip" = target_ip, + "rating_bad" = rating_bad, + "valid_hours" = valid_hours + ) + ) + if(!query_get_ip_intel.warn_execute()) + log_debug("ipintel_badip_check reports failed query execution") + qdel(query_get_ip_intel) + return FALSE + if(!query_get_ip_intel.NextRow()) + qdel(query_get_ip_intel) + return FALSE + qdel(query_get_ip_intel) + return TRUE + + + +/** + * VPN whitelist checker + * + * Checks if a ckey is whitelisted to be using a VPN against the DB + * + * Arguments: + * * target_ckey - The ckey to check + */ +/datum/controller/subsystem/ipintel/proc/vpn_whitelist_check(target_ckey) + if(!GLOB.configuration.ipintel.whitelist_mode) + return FALSE + var/datum/db_query/query_whitelist_check = SSdbcore.NewQuery("SELECT * FROM vpn_whitelist WHERE ckey=:ckey", list( + "ckey" = target_ckey + )) + if(!query_whitelist_check.warn_execute()) + qdel(query_whitelist_check) + return FALSE + if(query_whitelist_check.NextRow()) + qdel(query_whitelist_check) + return TRUE // At least one row in the whitelist names their ckey. That means they are whitelisted. + qdel(query_whitelist_check) + return FALSE + + + +/** + * VPN whitelist adder + * + * Adds a ckey to the VPN whitelist. Asks the admin to also provide a link to their request. + * + * Arguments: + * * target_ckey - The ckey to whitelist + */ +/datum/controller/subsystem/ipintel/proc/vpn_whitelist_add(target_ckey) + var/reason_string = input(usr, "Enter link to the URL of their whitelist request on the forum.","Reason required") as message|null + if(!reason_string) + return FALSE + var/datum/db_query/query_whitelist_add = SSdbcore.NewQuery("INSERT INTO vpn_whitelist (ckey,reason) VALUES (:targetckey, :reason)", list( + "targetckey" = target_ckey, + "reason" = reason_string + )) + if(!query_whitelist_add.warn_execute()) + qdel(query_whitelist_add) + return FALSE + qdel(query_whitelist_add) + return TRUE + + + +/** + * VPN whitelist remover + * + * Removes a ckey from the VPN whitelist. Pretty simple. + * + * Arguments: + * * target_ckey - The ckey to remove + */ +/datum/controller/subsystem/ipintel/proc/vpn_whitelist_remove(target_ckey) + var/datum/db_query/query_whitelist_remove = SSdbcore.NewQuery("DELETE FROM vpn_whitelist WHERE ckey=:targetckey", list( + "targetckey" = target_ckey + )) + if(!query_whitelist_remove.warn_execute()) + qdel(query_whitelist_remove) + return FALSE + qdel(query_whitelist_remove) + return TRUE + + + +/** + * VPN whitelist panel + * + * Doesnt actually open a panel, this is just a verb to handle the rest of the whitelist operations + * + * Arguments: + * * target_ckey - The ckey to add/remove + */ +/datum/controller/subsystem/ipintel/proc/vpn_whitelist_panel(target_ckey as text) + if(!check_rights(R_ADMIN)) + return + if(!target_ckey) + return + var/is_already_whitelisted = vpn_whitelist_check(target_ckey) + if(is_already_whitelisted) + var/confirm = alert("[target_ckey] is already whitelisted. Remove them?", "Confirm Removal", "No", "Yes") + if(!confirm || confirm != "Yes") + to_chat(usr, "VPN whitelist alteration cancelled.") + return + else if(vpn_whitelist_remove(target_ckey)) + to_chat(usr, "[target_ckey] was removed from the VPN whitelist.") + else + to_chat(usr, "VPN whitelist unchanged.") + else + if(vpn_whitelist_add(target_ckey)) + to_chat(usr, "[target_ckey] was added to the VPN whitelist.") + else + to_chat(usr, "VPN whitelist unchanged.") diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 044913acb89..014b247ba9e 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -6,6 +6,10 @@ SUBSYSTEM_DEF(mapping) var/datum/map/map_datum /// What map will be used next round var/datum/map/next_map + /// List of all areas that can be accessed via IC means + var/list/teleportlocs + /// List of all areas that can be accessed via IC and OOC means + var/list/ghostteleportlocs // This has to be here because world/New() uses [station_name()], which looks this datum up /datum/controller/subsystem/mapping/PreInit() @@ -62,26 +66,28 @@ SUBSYSTEM_DEF(mapping) log_startup_progress("Successfully populated lavaland in [stop_watch(lavaland_setup_timer)]s.") // Now we make a list of areas for teleport locs - // AA TODO: Make these locs into lists on the SS itself, not globs + teleportlocs = list() for(var/area/AR in world) if(AR.no_teleportlocs) continue - if(GLOB.teleportlocs[AR.name]) + if(teleportlocs[AR.name]) continue var/turf/picked = safepick(get_area_turfs(AR.type)) if(picked && is_station_level(picked.z)) - GLOB.teleportlocs[AR.name] = AR + teleportlocs[AR.name] = AR - GLOB.teleportlocs = sortAssoc(GLOB.teleportlocs) + teleportlocs = sortAssoc(teleportlocs) + + ghostteleportlocs = list() for(var/area/AR in world) - if(GLOB.ghostteleportlocs[AR.name]) + if(ghostteleportlocs[AR.name]) continue var/list/turfs = get_area_turfs(AR.type) if(turfs.len) - GLOB.ghostteleportlocs[AR.name] = AR + ghostteleportlocs[AR.name] = AR - GLOB.ghostteleportlocs = sortAssoc(GLOB.ghostteleportlocs) + ghostteleportlocs = sortAssoc(ghostteleportlocs) // World name if(GLOB.configuration.general.server_name) diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 320f3671000..14a952cb36f 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -7,8 +7,10 @@ SUBSYSTEM_DEF(ticker) runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME offline_implications = "The game is no longer aware of when the round ends. Immediate server restart recommended." - /// Time the world started, relative to world.time + /// Time the game should start, relative to world.time var/round_start_time = 0 + /// Time that the round started + var/time_game_started = 0 /// Default timeout for if world.Reboot() doesnt have a time specified var/const/restart_timeout = 75 SECONDS /// Current status of the game. See code\__DEFINES\game.dm @@ -266,7 +268,7 @@ SUBSYSTEM_DEF(ticker) SSdiscord.send2discord_simple_noadmins("**\[Info]** Round has started") auto_toggle_ooc(FALSE) // Turn it off - round_start_time = world.time + time_game_started = world.time // Sets the auto shuttle vote to happen after the config duration next_autotransfer = world.time + GLOB.configuration.vote.autotransfer_initial_time diff --git a/code/datums/spells/area_teleport.dm b/code/datums/spells/area_teleport.dm index b931bd9840f..0fdce753476 100644 --- a/code/datums/spells/area_teleport.dm +++ b/code/datums/spells/area_teleport.dm @@ -25,14 +25,14 @@ var/A = null if(!randomise_selection) - A = input("Area to teleport to", "Teleport", A) as null|anything in GLOB.teleportlocs + A = input("Area to teleport to", "Teleport", A) as null|anything in SSmapping.teleportlocs else - A = pick(GLOB.teleportlocs) + A = pick(SSmapping.teleportlocs) if(!A) return - var/area/thearea = GLOB.teleportlocs[A] + var/area/thearea = SSmapping.teleportlocs[A] if(thearea.tele_proof && !istype(thearea, /area/wizard_station)) to_chat(usr, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.") diff --git a/code/defines/procs/announce.dm b/code/defines/procs/announce.dm index b62f0f8763b..e8d3abb7335 100644 --- a/code/defines/procs/announce.dm +++ b/code/defines/procs/announce.dm @@ -32,8 +32,8 @@ GLOBAL_DATUM_INIT(event_announcement, /datum/announcement/priority/command/event /datum/announcement/priority/command/New(do_log = 1, new_sound = sound('sound/misc/notice2.ogg'), do_newscast = 0) ..(do_log, new_sound, do_newscast) admin_announcement = 1 - title = "[command_name()] Update" - announcement_type = "[command_name()] Update" + title = "NAS Trurl Update" + announcement_type = "NAS Trurl Update" /datum/announcement/priority/command/event/New(do_log = 1, new_sound = sound('sound/misc/notice2.ogg'), do_newscast = 0) ..(do_log, new_sound, do_newscast) diff --git a/code/game/gamemodes/blob/blob_report.dm b/code/game/gamemodes/blob/blob_report.dm index ad3cedafb85..6a64c19ac29 100644 --- a/code/game/gamemodes/blob/blob_report.dm +++ b/code/game/gamemodes/blob/blob_report.dm @@ -11,7 +11,7 @@ if(is_station_level(bomb.z)) bomb.r_code = nukecode - interceptname = "Classified [command_name()] Update" + interceptname = "Classified NAS Trurl Update" intercepttext += "Nanotrasen Update: Biohazard Alert.
"
- message_text += "