From 8f8195da225c55a736b095911a28b7988bf7d4f2 Mon Sep 17 00:00:00 2001 From: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:52:54 -0400 Subject: [PATCH] ip intel mk2 (#82683) Do not merge this without coordinating with your server's host. Slightly refactors the way we handle IP intel. You can still use the old data stored in the database. Adds the ability to automatically reject connections determined by config flags. We used to have IP intel to check for VPNs, although it was disabled due to being bad and unhelpful. This refactor should make it much more manageable for hosts and admins. This adds a new SQL table `ipintel_whitelist` Look at the schema! :cl: admin: The return of IPIntel /:cl: --------- Co-authored-by: MrStonedOne Co-authored-by: oranges --- SQL/database_changelog.md | 25 +- SQL/tgstation_schema.sql | 14 + code/__DEFINES/admin_verb.dm | 1 + code/__DEFINES/ipintel.dm | 15 + code/__DEFINES/subsystems.dm | 2 +- .../configuration/entries/general.dm | 31 +- code/controllers/subsystem/ipintel.dm | 294 +++++++++++++++++- code/modules/admin/ipintel.dm | 136 -------- code/modules/client/client_procs.dm | 9 - config/config.txt | 26 +- tgstation.dme | 2 +- 11 files changed, 379 insertions(+), 176 deletions(-) create mode 100644 code/__DEFINES/ipintel.dm delete mode 100644 code/modules/admin/ipintel.dm diff --git a/SQL/database_changelog.md b/SQL/database_changelog.md index 48090d7f212..65743042c51 100644 --- a/SQL/database_changelog.md +++ b/SQL/database_changelog.md @@ -2,21 +2,36 @@ Any time you make a change to the schema files, remember to increment the databa Make sure to also update `DB_MAJOR_VERSION` and `DB_MINOR_VERSION`, which can be found in `code/__DEFINES/subsystem.dm`. -The latest database version is 5.29 (5.26 for /tg/); The query to update the schema revision table is: +The latest database version is 5.30 (5.27 for /tg/); The query to update the schema revision table is: ```sql -INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 29); +INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 30); ``` or ```sql -INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 29); +INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 30); ``` In any query remember to add a prefix to the table names if you use one. - ----------------------------------------------------- -Version 5.29, 08 January 2024, by distributivgesetz +Version 5.30, 26 April 2024, by zephyrtfa +Add the ip intel table +```sql +DROP TABLE IF EXISTS `ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel` ( + `ip` int(10) unsigned NOT NULL, + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `intel` double NOT NULL DEFAULT '0', + PRIMARY KEY (`ip`), + KEY `idx_ipintel` (`ip`,`intel`,`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +``` +----------------------------------------------------- +Version 5.29, 08 January 2024, by Useroth Add a new table for age-checking purposes. Optional if you don't ever intend to use the age prompt. ```sql diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index e7167f766df..fba7edfcb7a 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -220,6 +220,20 @@ CREATE TABLE `ipintel` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `ipintel_whitelist` +-- + +DROP TABLE IF EXISTS `ipintel_whitelist`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel_whitelist` ( + `ckey` varchar(32) NOT NULL, + `admin_ckey` varchar(32) NOT NULL, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `legacy_population` -- diff --git a/code/__DEFINES/admin_verb.dm b/code/__DEFINES/admin_verb.dm index cbf856bf436..7e811c12196 100644 --- a/code/__DEFINES/admin_verb.dm +++ b/code/__DEFINES/admin_verb.dm @@ -87,6 +87,7 @@ _ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_categor #define ADMIN_CATEGORY_OBJECT "Object" #define ADMIN_CATEGORY_MAPPING "Mapping" #define ADMIN_CATEGORY_PROFILE "Profile" +#define ADMIN_CATEGORY_IPINTEL "IPIntel" // Visibility flags #define ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG "Map-Debug" diff --git a/code/__DEFINES/ipintel.dm b/code/__DEFINES/ipintel.dm new file mode 100644 index 00000000000..9fbc14ae40d --- /dev/null +++ b/code/__DEFINES/ipintel.dm @@ -0,0 +1,15 @@ +#define IPINTEL_RATE_LIMIT_MINUTE "minute" +#define IPINTEL_RATE_LIMIT_DAY "day" + +/// An internal error occurred and the query cannot be processed +#define IPINTEL_UNKNOWN_INTERNAL_ERROR "unknown_internal_error" +/// An error occurred with the query and the result is unknown +#define IPINTEL_UNKNOWN_QUERY_ERROR "unknown_query_error" +/// Cannot query as we are rate limited for the rest of the day +#define IPINTEL_RATE_LIMITED_DAY "rate_limited_day" +/// Cannot query as we are rate limited for the rest of the minute +#define IPINTEL_RATE_LIMITED_MINUTE "rate_limited_minute" +/// The IP address is a VPN or bad IP +#define IPINTEL_BAD_IP "bad_ip" +/// The IP address is not a VPN or bad IP +#define IPINTEL_GOOD_IP "good_ip" diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 402b104cabf..ca94ab48e96 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -20,7 +20,7 @@ * * make sure you add an update to the schema_version stable in the db changelog */ -#define DB_MINOR_VERSION 26 +#define DB_MINOR_VERSION 27 //! ## Timing subsystem diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index b3ff39ef1fe..73906998e01 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -451,6 +451,9 @@ /datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? +/datum/config_entry/string/ipintel_base + default = "check.getipintel.net" + /datum/config_entry/string/ipintel_email /datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) @@ -462,18 +465,26 @@ min_val = 0 max_val = 1 -/datum/config_entry/number/ipintel_save_good - default = 12 - integer = FALSE - min_val = 0 +/datum/config_entry/flag/ipintel_reject_rate_limited + default = TRUE -/datum/config_entry/number/ipintel_save_bad - default = 1 - integer = FALSE - min_val = 0 +/datum/config_entry/flag/ipintel_reject_bad + default = TRUE -/datum/config_entry/string/ipintel_domain - default = "check.getipintel.net" +/datum/config_entry/flag/ipintel_reject_unknown + default = FALSE + +/datum/config_entry/number/ipintel_rate_minute + default = 15 + +/datum/config_entry/number/ipintel_rate_day + default = 500 + +/datum/config_entry/number/ipintel_cache_length + default = 7 + +/datum/config_entry/number/ipintel_exempt_playtime_living + default = 0 /datum/config_entry/flag/aggressive_changelog diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index 83cbbc4c27e..3f1c738b09c 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -1,13 +1,295 @@ SUBSYSTEM_DEF(ipintel) name = "XKeyScore" init_order = INIT_ORDER_XKEYSCORE - flags = SS_NO_FIRE - var/enabled = FALSE //disable at round start to avoid checking reconnects - var/throttle = 0 - var/errors = 0 + flags = SS_INIT_NO_NEED|SS_NO_FIRE + /// The threshold for probability to be considered a VPN and/or bad IP + var/probability_threshold + /// The email used in conjuction with https://check.getipintel.net/check.php + var/contact_email + /// Maximum number of queries per minute + var/max_queries_per_minute + /// Maximum number of queries per day + var/max_queries_per_day + /// Query base + var/query_base + /// The length of time (days) to cache IP intel + var/ipintel_cache_length + /// The living playtime (minutes) for players to be exempt from IPIntel checks + var/exempt_living_playtime - var/list/cache = list() + /// Cache for previously queried IP addresses and those stored in the database + var/list/datum/ip_intel/cached_queries = list() + /// The store for rate limiting + var/list/rate_limits + +/// The ip intel for a given address +/datum/ip_intel + /// If this intel was just queried, the status of the query + var/query_status + var/result + var/address + var/date /datum/controller/subsystem/ipintel/Initialize() - enabled = TRUE + var/list/fail_messages = list() + + probability_threshold = CONFIG_GET(number/ipintel_rating_bad) + if(probability_threshold < 0 || probability_threshold > 1) + fail_messages += list("invalid probability threshold") + + contact_email = CONFIG_GET(string/ipintel_email) + if(isnull(contact_email) || !findtext(contact_email, "@")) + fail_messages += list("invalid contact email") + + var/max_queries_per_minute = CONFIG_GET(number/ipintel_rate_minute) + var/max_queries_per_day = CONFIG_GET(number/ipintel_rate_day) + if(max_queries_per_minute < 0 || max_queries_per_day < 0) + fail_messages += list("invalid rate limits") + + var/query_base = CONFIG_GET(string/ipintel_base) + if(isnull(query_base)) + fail_messages += list("invalid query base") + + var/ipintel_cache_length = CONFIG_GET(number/ipintel_cache_length) + if(ipintel_cache_length < 0) + fail_messages += list("invalid cache length") + + var/exempt_living_playtime = CONFIG_GET(number/ipintel_exempt_playtime_living) + if(exempt_living_playtime < 0) + fail_messages += list("invalid exempt living playtime") + + if(length(fail_messages)) + message_admins("IPIntel: Initialization failed check logs!") + logger.Log(LOG_CATEGORY_GAME_ACCESS, "IPIntel failed to initialize.", list( + "fail_messages" = fail_messages, + )) + return SS_INIT_FAILURE + return SS_INIT_SUCCESS + +/datum/controller/subsystem/ipintel/stat_entry(msg) + return "[..()] | D: [max_queries_per_day - rate_limits[IPINTEL_RATE_LIMIT_DAY]] | M: [max_queries_per_minute - rate_limits[IPINTEL_RATE_LIMIT_MINUTE]]" + +/datum/controller/subsystem/ipintel/proc/get_address_intel_state(address, probability_override) + var/datum/ip_intel/intel = query_address(address) + if(isnull(intel)) + stack_trace("query_address did not return an ip intel response") + return IPINTEL_UNKNOWN_INTERNAL_ERROR + + if(istext(intel)) + return intel + + if(!(intel.query_status in list("success", "cached"))) + return IPINTEL_UNKNOWN_QUERY_ERROR + var/check_probability = probability_override || probability_threshold + if(intel.result >= check_probability) + return IPINTEL_BAD_IP + return IPINTEL_GOOD_IP + +/datum/controller/subsystem/ipintel/proc/is_rate_limited() + var/static/minute_key + var/expected_minute_key = floor(REALTIMEOFDAY / 1 MINUTES) + + if(minute_key != expected_minute_key) + minute_key = expected_minute_key + rate_limits[IPINTEL_RATE_LIMIT_MINUTE] = 0 + + if(rate_limits[IPINTEL_RATE_LIMIT_MINUTE] >= max_queries_per_minute) + return IPINTEL_RATE_LIMITED_MINUTE + if(rate_limits[IPINTEL_RATE_LIMIT_DAY] >= max_queries_per_day) + return IPINTEL_RATE_LIMITED_DAY + return FALSE + +/datum/controller/subsystem/ipintel/proc/query_address(address, allow_cached = TRUE) + if(allow_cached && fetch_cached_ip_intel(address)) + return cached_queries[address] + var/is_rate_limited = is_rate_limited() + if(is_rate_limited) + return is_rate_limited + if(!initialized) + return IPINTEL_UNKNOWN_INTERNAL_ERROR + + rate_limits[IPINTEL_RATE_LIMIT_MINUTE] += 1 + rate_limits[IPINTEL_RATE_LIMIT_DAY] += 1 + + var/query_base = "https://[src.query_base]/check.php?ip=" + var/query = "[query_base][address]&contact=[contact_email]&flags=b&format=json" + + var/datum/http_request/request = new + request.prepare(RUSTG_HTTP_METHOD_GET, query) + request.execute_blocking() + var/datum/http_response/response = request.into_response() + var/list/data = response.body + + var/datum/ip_intel/intel = new + intel.query_status = data["status"] + if(intel.query_status != "success") + return intel + intel.result = data["result"] + intel.date = SQLtime() + intel.address = address + cached_queries[address] = intel + add_intel_to_database(intel) + return intel + +/datum/controller/subsystem/ipintel/proc/add_intel_to_database(datum/ip_intel/intel) + var/datum/db_query/query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("ipintel")] ( \ + ip, \ + intel, \ + ) VALUES ( \ + INET_ATON(:address) \ + :result, \ + )", list( + "address" = intel.address, + "result" = intel.result, + ) + ) + query.warn_execute() + query.sync() + qdel(query) + +/datum/controller/subsystem/ipintel/proc/fetch_cached_ip_intel(address) + var/date_restrictor + if(ipintel_cache_length > 0) + date_restrictor = " AND date > DATE_SUB(NOW(), INTERVAL [ipintel_cache_length] DAY)" + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT * FROM [format_table_name("ipintel")] WHERE ip = INET_ATON(:address)[date_restrictor]", list( + "address" = address + ) + ) + query.warn_execute() + query.sync() + if(query.status == DB_QUERY_BROKEN) + qdel(query) + return null + + query.NextRow() + var/list/data = query.item + qdel(query) + if(isnull(data)) + return null + + var/datum/ip_intel/intel = new + intel.query_status = "cached" + intel.result = data["intel"] + intel.date = data["date"] + intel.address = address + return TRUE + +/datum/controller/subsystem/ipintel/proc/is_exempt(client/player) + if(exempt_living_playtime > 0) + var/list/play_records = player.prefs.exp + if (!play_records.len) + player.set_exp_from_db() + play_records = player.prefs.exp + if(length(play_records) && play_records[EXP_TYPE_LIVING] > exempt_living_playtime) + return TRUE + return FALSE + +/datum/controller/subsystem/ipintel/proc/is_whitelisted(ckey) + var/datum/db_query/query = SSdbcore.NewQuery( + "SELECT * FROM [format_table_name("ipintel_whitelist")] WHERE ckey = :ckey", list( + "ckey" = ckey + ) + ) + query.warn_execute() + query.sync() + if(query.status == DB_QUERY_BROKEN) + qdel(query) + return FALSE + query.NextRow() + return !!query.item // if they have a row, they are whitelisted + +ADMIN_VERB(ipintel_allow, R_BAN, "Whitelist Player VPN", "Allow a player to connect even if they are using a VPN.", ADMIN_CATEGORY_IPINTEL, ckey as text) + if(SSipintel.is_whitelisted(ckey)) + to_chat(user, "Player is already whitelisted.") + return + + var/datum/db_query/query = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("ipintel_whitelist")] ( \ + ckey, \ + admin_ckey \ + ) VALUES ( \ + :ckey, \ + :admin_ckey \ + )", list( + "ckey" = ckey, + "admin_ckey" = user.ckey, + ) + ) + query.warn_execute() + query.sync() + qdel(query) + message_admins("IPINTEL: [key_name_admin(user)] has whitelisted '[ckey]'") + +ADMIN_VERB(ipintel_revoke, R_BAN, "Revoke Player VPN Whitelist", "Revoke a player's VPN whitelist.", ADMIN_CATEGORY_IPINTEL, ckey as text) + if(!SSipintel.is_whitelisted(ckey)) + to_chat(user, "Player is not whitelisted.") + return + var/datum/db_query/query = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("ipintel_whitelist")] WHERE ckey = :ckey", list( + "ckey" = ckey + ) + ) + query.warn_execute() + query.sync() + qdel(query) + message_admins("IPINTEL: [key_name_admin(user)] has revoked the VPN whitelist for '[ckey]'") + +/client/proc/check_ip_intel() + if(SSipintel.is_exempt(src) || SSipintel.is_whitelisted(ckey)) + return + + var/intel_state = SSipintel.get_address_intel_state(address) + var/reject_bad_intel = CONFIG_GET(flag/ipintel_reject_bad) + var/reject_unknown_intel = CONFIG_GET(flag/ipintel_reject_unknown) + var/reject_rate_limited = CONFIG_GET(flag/ipintel_reject_rate_limited) + + var/connection_rejected = FALSE + var/datum/ip_intel/intel = SSipintel.cached_queries[address] + switch(intel_state) + if(IPINTEL_BAD_IP) + log_access("IPINTEL: [ckey] was flagged as a VPN with [intel.result * 100]% likelihood.") + if(reject_bad_intel) + to_chat_immediate(src, span_boldnotice("Your connection has been detected as a VPN.")) + connection_rejected = TRUE + else + message_admins("IPINTEL: [key_name_admin(src)] has been flagged as a VPN with [intel.result * 100]% likelihood.") + + if(IPINTEL_RATE_LIMITED_DAY, IPINTEL_RATE_LIMITED_MINUTE) + log_access("IPINTEL: [ckey] was unable to be checked due to the rate limit.") + if(reject_rate_limited) + to_chat_immediate(src, span_boldnotice("New connections are not being allowed at this time.")) + connection_rejected = TRUE + else + message_admins("IPINTEL: [key_name_admin(src)] was unable to be checked due to rate limiting.") + + if(IPINTEL_UNKNOWN_INTERNAL_ERROR, IPINTEL_UNKNOWN_QUERY_ERROR) + log_access("IPINTEL: [ckey] unable to be checked due to an error.") + if(reject_unknown_intel) + to_chat_immediate(src, span_boldnotice("Your connection cannot be processed at this time.")) + connection_rejected = TRUE + else + message_admins("IPINTEL: [key_name_admin(src)] was unable to be checked due to an error.") + + if(!connection_rejected) + return + + var/list/contact_where = list() + var/forum_url = CONFIG_GET(string/forumurl) + if(forum_url) + contact_where += list("Forums") + var/appeal_url = CONFIG_GET(string/banappeals) + if(appeal_url) + contact_where += list("Ban Appeals") + + var/message_string = "Your connection has been rejected at this time. If you believe this is in error or have any questions please contact an admin" + if(length(contact_where)) + message_string += " at [english_list(contact_where)]" + else + message_string += " somehow." + message_string += "." + + to_chat_immediate(src, span_userdanger(message_string)) + qdel(src) diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm deleted file mode 100644 index 7ca4ccbc320..00000000000 --- a/code/modules/admin/ipintel.dm +++ /dev/null @@ -1,136 +0,0 @@ -/datum/ipintel - var/ip - var/intel = 0 - var/cache = FALSE - var/cacheminutesago = 0 - var/cachedate = "" - var/cacherealtime = 0 - -/datum/ipintel/New() - cachedate = SQLtime() - cacherealtime = world.realtime - -/datum/ipintel/proc/is_valid() - . = FALSE - if (intel < 0) - return - if (intel <= CONFIG_GET(number/ipintel_rating_bad)) - if (world.realtime < cacherealtime + (CONFIG_GET(number/ipintel_save_good) * 60 * 60 * 10)) - return TRUE - else - if (world.realtime < cacherealtime + (CONFIG_GET(number/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 || !CONFIG_GET(string/ipintel_email) || !SSipintel.enabled) - return - if (!bypasscache) - var/datum/ipintel/cachedintel = SSipintel.cache[ip] - if (cachedintel?.is_valid()) - cachedintel.cache = TRUE - return cachedintel - - if(SSdbcore.Connect()) - var/rating_bad = CONFIG_GET(number/ipintel_rating_bad) - var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({" - SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) - FROM [format_table_name("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" = rating_bad, "save_good" = CONFIG_GET(number/ipintel_save_good), "save_bad" = CONFIG_GET(number/ipintel_save_bad))) - if(!query_get_ip_intel.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) - SSipintel.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) - SSipintel.cache[ip] = res - if(SSdbcore.Connect()) - var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery( - "INSERT INTO [format_table_name("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.Execute() - qdel(query_add_ip_intel) - - -/proc/ip_intel_query(ip, retryed=0) - . = -1 //default - if (!ip) - return - if (SSipintel.throttle > world.timeofday) - return - if (!SSipintel.enabled) - return - - var/list/http[] = world.Export("http://[CONFIG_GET(string/ipintel_domain)]/check.php?ip=[ip]&contact=[CONFIG_GET(string/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(2.5 SECONDS) - return .(ip, 1) - else - ipintel_handle_error("Bad response from server: [response["status"]].", ip, retryed) - if (!retryed) - sleep(2.5 SECONDS) - 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(2.5 SECONDS) - return .(ip, 1) - else - ipintel_handle_error("Unable to connect to API.", ip, retryed) - if (!retryed) - sleep(2.5 SECONDS) - 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[( SSipintel.errors == 1 ? "" : "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]") - debug_admins("IPINTEL: [text]") diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index b3e18ce5b0c..ce4922b9d84 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -860,15 +860,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( qdel(query_get_notes) create_message("note", key, system_ckey, message, null, null, 0, 0, null, 0, 0) - -/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 - if (CONFIG_GET(string/ipintel_email)) - var/datum/ipintel/res = get_ip_intel(address) - if (res.intel >= CONFIG_GET(number/ipintel_rating_bad)) - message_admins(span_adminnotice("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.")) - ip_intel = res.intel - /client/Click(atom/object, atom/location, control, params) if(click_intercept_time) if(click_intercept_time >= world.time) diff --git a/config/config.txt b/config/config.txt index 5a550aea314..ebaf538e7b6 100644 --- a/config/config.txt +++ b/config/config.txt @@ -153,16 +153,26 @@ GUEST_BAN ## IPINTEL: ## This allows you to detect likely proxies by checking ips against getipintel.net -## Rating to warn at: (0.9 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything equal to or higher then this number triggers an admin warning -#IPINTEL_RATING_BAD 0.9 +## Rating to warn at: (0.8 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything equal to or higher then this number triggers an admin warning +#IPINTEL_RATING_BAD 0.8 ## Contact email, (required to use the service, leaving blank or default disables IPINTEL) #IPINTEL_EMAIL ch@nge.me -## How long to save good matches (ipintel rate limits to 15 per minute and 500 per day. so this shouldn't be too low, getipintel.net suggests 6 hours, time is in hours) (Your ip will get banned if you go over 500 a day too many times) -#IPINTEL_SAVE_GOOD 12 -## How long to save bad matches (these numbers can change as ips change hands, best not to save these for too long in case somebody gets a new ip used by a spammer/proxy before.) -#IPINTEL_SAVE_BAD 3 -## Domain name to query (leave commented out for the default, only needed if you pay getipintel.net for more querys) -#IPINTEL_DOMAIN check.getipintel.net +## Query base, if you pay for more queries this is what you want to change. +#IPINTEL_BASE check.getipintel.net +## Maximum number of queries in a minute +#IPINTEL_MAX_QUERY_MINUTE 15 +## Maximum number of queries in a day +#IPINTEL_MAX_QUERY_DAY 500 +## Whether clients which cannot be checked due to a rate limit will be denied +#IPINTEL_REJECT_RATE_LIMITED +## Whether clients which are flagged as a VPN will be denied +IPINTEL_REJECT_BAD +## Whether clients which cannot be checked due to an error of some form will be denied +#IPINTEL_REJECT_UNKNOWN +## How long to store results in the cache before they must be retrieved again. IN DAYS. +#IPINTEL_CACHE_LENGTH 7 +## How many minutes of living playtime to be automatically exempt from IPIntel. 0 for never. +#IPINTEL_EXEMPT_PLAYTIME_LIVING 0 ## Uncomment to allow web client connections #ALLOW_WEBCLIENT diff --git a/tgstation.dme b/tgstation.dme index e3b896d2934..01d225fc5bb 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -117,6 +117,7 @@ #include "code\__DEFINES\instruments.dm" #include "code\__DEFINES\interaction_flags.dm" #include "code\__DEFINES\inventory.dm" +#include "code\__DEFINES\ipintel.dm" #include "code\__DEFINES\is_helpers.dm" #include "code\__DEFINES\jobs.dm" #include "code\__DEFINES\keybinding.dm" @@ -2890,7 +2891,6 @@ #include "code\modules\admin\fun_balloon.dm" #include "code\modules\admin\greyscale_modify_menu.dm" #include "code\modules\admin\holder2.dm" -#include "code\modules\admin\ipintel.dm" #include "code\modules\admin\IsBanned.dm" #include "code\modules\admin\known_alts.dm" #include "code\modules\admin\outfit_editor.dm"