From 645ddf30d14ad41c44957499626abaa8d61e1f4e Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 15 Jan 2020 14:48:22 -0500 Subject: [PATCH] IP reputation checking Ported from https://github.com/VOREStation/VOREStation/pull/6451 Code done by Aronai Adds IP reputation checking to detect Tor, proxy, and VPN usage and block it if so configured. It's disabled by default, but if enabled the default settings are to block all VPN/Proxy/Tor to connect unless the player has been a player for 5 days on a 'normal' connection so that people who legitimately sometimes use a VPN for *reasons* can continue to do so. You can also have it check reputations and just log bad ones, without disconnecting the users. Whether or not it allows 'existing' players, the length of time they must have played, what's considered a 'bad' IP score, etc, are configurable. You **must** put an e-mail address if you use this, otherwise the service will likely ban you. This is the e-mail address they will send e-mails to if you're performing too many checks or they need to speak to you. Adds config options, here's a paste from the example config: ``` ## IP Reputation Checking # Enable/disable IP reputation checking (present/nonpresent) #IP_REPUTATION # Set the e-mail address problems can go to for IPR checks (e-mail address) IPR_EMAIL whatever@whatever.com # Above this value, reputation scores are considered 'bad' (number) IPR_BAD_SCORE 1 # If you want the people disconnected. Otherwise it just logs. (present/nonpresent) IPR_BLOCK_BAD_IPS # If players of a certain length of playtime are allowed anyway (REQUIRES DATABASE) (present/nonpresent) IPR_ALLOW_EXISTING # And what that age is (number) IPR_MINIMUM_AGE 5 ``` As you can see, it's off by default, so if you're a downstream this won't change anything for you unless you decide to turn it on. If you want the features, just copypaste the new config lines out of the example and uncomment IP_REPUTATION. Downstreams can replace the /client/proc/update_ip_reputation() proc with your own, if you'd like to substitute your own service! Just set the client's ip_reputation var at the end of your proc. --- code/controllers/configuration.dm | 25 ++++++++ code/modules/client/client defines.dm | 1 + code/modules/client/client procs.dm | 85 +++++++++++++++++++++++++++ config/example/config.txt | 21 ++++++- 4 files changed, 131 insertions(+), 1 deletion(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 306bf70951..9c1729d7e9 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -104,6 +104,13 @@ var/list/gamemode_cache = list() var/panic_bunker = 0 var/paranoia_logging = 0 + var/ip_reputation = FALSE //Should we query IPs to get scores? Generates HTTP traffic to an API service. + var/ipr_email //Left null because you MUST specify one otherwise you're making the internet worse. + var/ipr_block_bad_ips = FALSE //Should we block anyone who meets the minimum score below? Otherwise we just log it (If paranoia logging is on, visibly in chat). + var/ipr_bad_score = 1 //The API returns a value between 0 and 1 (inclusive), with 1 being 'definitely VPN/Tor/Proxy'. Values equal/above this var are considered bad. + var/ipr_allow_existing = FALSE //Should we allow known players to use VPNs/Proxies? If the player is already banned then obviously they still can't connect. + var/ipr_minimum_age = 5 //How many days before a player is considered 'fine' for the purposes of allowing them to use VPNs. + var/serverurl var/server var/banappeals @@ -806,6 +813,24 @@ var/list/gamemode_cache = list() if ("paranoia_logging") config.paranoia_logging = 1 + if("ip_reputation") + config.ip_reputation = 1 + + if("ipr_email") + config.ipr_email = value + + if("ipr_block_bad_ips") + config.ipr_block_bad_ips = 1 + + if("ipr_bad_score") + config.ipr_bad_score = text2num(value) + + if("ipr_allow_existing") + config.ipr_allow_existing = 1 + + if("ipr_minimum_age") + config.ipr_minimum_age = text2num(value) + if("random_submap_orientation") config.random_submap_orientation = 1 diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 5f49b931b5..d95f1b0cea 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -41,6 +41,7 @@ var/received_irc_pm = -99999 var/irc_admin //IRC admin that spoke with them last. var/mute_irc = 0 + var/ip_reputation = 0 //Do we think they're using a proxy/vpn? Only if IP Reputation checking is enabled in config. //////////////////////////////////// diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 6c20e43759..f4f9102d9d 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -271,6 +271,31 @@ qdel(src) return 0 + // IP Reputation Check + if(config.ip_reputation) + if(config.ipr_allow_existing && player_age >= config.ipr_minimum_age) + log_admin("Skipping IP reputation check on [key] with [address] because of player age") + else if(update_ip_reputation()) //It is set now + if(ip_reputation >= config.ipr_bad_score) //It's bad + + //Log it + if(config.paranoia_logging) //We don't block, but we want paranoia log messages + log_and_message_admins("[key] at [address] has bad IP reputation: [ip_reputation]. Will be kicked if enabled in config.") + else //We just log it + log_admin("[key] at [address] has bad IP reputation: [ip_reputation]. Will be kicked if enabled in config.") + + //Take action if required + if(config.ipr_block_bad_ips && config.ipr_allow_existing) //We allow players of an age, but you don't meet it + to_chat(src,"Sorry, we only allow VPN/Proxy/Tor usage for players who have spent at least [config.ipr_minimum_age] days on the server. If you are unable to use the internet without your VPN/Proxy/Tor, please contact an admin out-of-game to let them know so we can accomidate this.") + qdel(src) + return 0 + else if(config.ipr_block_bad_ips) //We don't allow players of any particular age + to_chat(src,"Sorry, we do not accept connections from users via VPN/Proxy/Tor connections.") + qdel(src) + return 0 + else + log_admin("Couldn't perform IP check on [key] with [address]") + if(sql_id) //Player already identified previously, we need to just update the 'lastseen', 'ip' and 'computer_id' variables var/DBQuery/query_update = dbcon.NewQuery("UPDATE erro_player SET lastseen = Now(), ip = '[sql_ip]', computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]' WHERE id = [sql_id]") @@ -395,3 +420,63 @@ client/verb/character_setup() if(var_name == NAMEOF(src, holder)) return FALSE return ..() + +//This is for getipintel.net. +//You're welcome to replace this proc with your own that does your own cool stuff. +//Just set the client's ip_reputation var and make sure it makes sense with your config settings (higher numbers are worse results) +/client/proc/update_ip_reputation() + var/request = "http://check.getipintel.net/check.php?ip=[address]&contact=[config.ipr_email]" + var/http[] = world.Export(request) + + /* Debug + world.log << "Requested this: [request]" + for(var/entry in http) + world.log << "[entry] : [http[entry]]" + */ + + if(!http || !islist(http)) //If we couldn't check, the service might be down, fail-safe. + log_admin("Couldn't connect to getipintel.net to check [address] for [key]") + return FALSE + + //429 is rate limit exceeded + if(text2num(http["STATUS"]) == 429) + log_and_message_admins("getipintel.net reports HTTP status 429. IP reputation checking is now disabled. If you see this, let a developer know.") + config.ip_reputation = FALSE + return FALSE + + var/content = file2text(http["CONTENT"]) //world.Export actually returns a file object in CONTENT + var/score = text2num(content) + if(isnull(score)) + return FALSE + + //Error handling + if(score < 0) + var/fatal = TRUE + var/ipr_error = "getipintel.net IP reputation check error while checking [address] for [key]: " + switch(score) + if(-1) + ipr_error += "No input provided" + if(-2) + fatal = FALSE + ipr_error += "Invalid IP provided" + if(-3) + fatal = FALSE + ipr_error += "Unroutable/private IP (spoofing?)" + if(-4) + fatal = FALSE + ipr_error += "Unable to reach database" + if(-5) + ipr_error += "Our IP is banned or otherwise forbidden" + if(-6) + ipr_error += "Missing contact info" + + log_and_message_admins(ipr_error) + if(fatal) + config.ip_reputation = FALSE + log_and_message_admins("With this error, IP reputation checking is disabled for this shift. Let a developer know.") + return FALSE + + //Went fine + else + ip_reputation = score + return TRUE diff --git a/config/example/config.txt b/config/example/config.txt index 92959a2592..4f220d8d6f 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -450,4 +450,23 @@ RADIATION_MATERIAL_RESISTANCE_DIVISOR 16 ## Mode of computing radiation resistance into effective radiation on a turf ## One and only one of the following options must be uncommented RADIATION_RESISTANCE_CALC_DIVIDE -# RADIATION_RESISTANCE_CALC_SUBTRACT \ No newline at end of file +# RADIATION_RESISTANCE_CALC_SUBTRACT + +## IP Reputation Checking +# Enable/disable IP reputation checking (present/nonpresent) +#IP_REPUTATION + +# Set the e-mail address problems can go to for IPR checks (e-mail address) +IPR_EMAIL whatever@whatever.com + +# Above this value, reputation scores are considered 'bad' (number) +IPR_BAD_SCORE 1 + +# If you want the people disconnected. Otherwise it just logs. (present/nonpresent) +IPR_BLOCK_BAD_IPS + +# If players of a certain length of playtime are allowed anyway (REQUIRES DATABASE) (present/nonpresent) +IPR_ALLOW_EXISTING + +# And what that age is (number) +IPR_MINIMUM_AGE 5