From 2366eb1a20950f50aa1e888a58f7d8b81a5b797c Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Sat, 23 Apr 2016 09:53:29 -0700 Subject: [PATCH 1/5] PUTS NIKNAK OUT OF A JOB! --- SQL/tgstation_schema.sql | 11 +++- SQL/tgstation_schema_prefixed.sql | 9 +++ code/controllers/configuration.dm | 15 +++++ code/modules/admin/IsBanned.dm | 22 ++++++++ code/modules/admin/ipintel.dm | 91 +++++++++++++++++++++++++++++++ config/config.txt | 12 ++++ tgstation.dme | 1 + 7 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 code/modules/admin/ipintel.dm diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 34d83537256..7fb896d93a6 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -364,5 +364,14 @@ CREATE TABLE `notes` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; - +DROP TABLE IF EXISTS `ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel` ( +`ip` INT UNSIGNED NOT NULL , +`date` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`intel` REAL NOT NULL DEFAULT '0', +PRIMARY KEY ( `ip` ) +) ENGINE = INNODB; +/*!40101 SET character_set_client = @saved_cs_client */; -- Dump completed on 2013-03-24 18:02:35 diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index fe40d2f72cf..c5bbd407125 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -359,5 +359,14 @@ CREATE TABLE `SS13_notes` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `SS13_ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `SS13_ipintel` ( +`ip` INT UNSIGNED NOT NULL , +`date` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`intel` REAL NOT NULL DEFAULT '0', +PRIMARY KEY ( `ip` ) +) ENGINE = INNODB; -- Dump completed on 2013-03-24 18:02:35 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 67e4e70422e..f141715dbbd 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -70,6 +70,12 @@ var/forbid_singulo_possession = 0 var/useircbot = 0 + //IP Intel vars + var/ipintel_email + var/ipintel_rating_max = 1 + var/ipintel_save_good = 3 + var/ipintel_save_bad = 1 + var/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt var/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt var/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database @@ -389,6 +395,15 @@ config.notify_new_player_age = text2num(value) if("irc_first_connection_alert") config.irc_first_connection_alert = 1 + if("ipintel_email") + if (value != "ch@nge.me") + config.ipintel_email = value + if("ipintel_rating_max") + config.ipintel_rating_max = text2num(value) + if("ipintel_save_good") + config.ipintel_save_good = text2num(value) + if("ipintel_save_bad") + config.ipintel_save_bad = text2num(value) if("aggressive_changelog") config.aggressive_changelog = 1 if("log_runtimes") diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 0d1c1c2370b..a1515f0c86b 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -99,6 +99,28 @@ log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]") return . + if (config.ipintel_email) + world.log << "checking [ckey] [address]" + var/datum/ipintel/res = get_ip_intel(address) + world.log << "got res: [res.ip], [res.intel], [res.cache], [res.cachedate], [res.cacheminutesago]" + world.log << "checking [res.intel] vs [config.ipintel_rating_max]" + if (res.intel > config.ipintel_rating_max) + world.log << "match" + if (admin) + world.log << "admin" + log_admin("The admin [key] has been allowed to bypass an IP intel ban. [address] was rated [res.intel*100]% likely to be a bad ip.") + message_admins("The admin [key] has been allowed to bypass an IP intel ban. [address] was rated [res.intel*100]% likely to be a bad ip.") + addclientmessage(ckey,"You have been allowed to bypass an IP intel ban. Your IP [address] was rated [res.intel*100]% likely to be a bad ip.") + else + world.log << "not admin, banning" + if (!res.cache) + world.log << "first ban" + log_admin("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") + message_admins("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") + + . = list("reason"="IP_INTEL", "desc"="\nYour IP [address] was rated [res.intel*100]% likely to be a bad IP (spammer/proxy). The highest allowed to connect is [config.ipintel_rating_max*100]%.\nThis rating was retrieved [res.cacheminutesago] minutes ago on [res.cachedate] and refreshes in [(config.ipintel_save_bad*60)-res.cacheminutesago] minutes.") + log_access("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") + return . = ..() //default pager ban stuff if (.) diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm new file mode 100644 index 00000000000..cab6d96a996 --- /dev/null +++ b/code/modules/admin/ipintel.dm @@ -0,0 +1,91 @@ +/datum/ipintel + var/ip + var/intel = 0 + var/cache = FALSE + var/cacheminutesago = 0 + var/cachedate = "" +/datum/ipintel/New() + cachedate = SQLtime() + +proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE) + var/datum/ipintel/res = new() + res.ip = ip + . = res + if (!ip || !config.ipintel_email) + return + if (!bypasscache && establish_db_connection()) + var/DBQuery/query = dbcon.NewQuery("SELECT date,intel,TIMESTAMPDIFF(MINUTE,date,NOW()) FROM [format_table_name("ipintel")] WHERE ip = INET_ATON('[ip]') AND ((intel <= [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_good] HOUR > NOW()) OR (intel > [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_bad] HOUR > NOW()))") + query.Execute() + if (query.NextRow()) + res.cache = TRUE + res.cachedate = query.item[1] + res.intel = query.item[2] + res.cacheminutesago = query.item[3] + return + res.intel = ip_intel_query(ip) + if (updatecache && res.intel >= 0 && establish_db_connection()) + //if you're wondering, we don't add or update the date as its a TIMESTAMP field, and as such, automatically updates at any insert or update + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel)") + query.Execute() + return + + +var/ip_intel_throttle = 0 +var/ip_intel_errors = 0 +/proc/ip_intel_query(ip, var/retry=0) + . = -1 //default + if (!ip) + return + if (ip_intel_throttle > world.timeofday) + return + + var/http[] = world.Export("http://check.getipintel.net/check.php?ip=[ip]&contact=[config.ipintel_email]&format=json") + + if(http) + var/status = text2num(http["STATUS"]) + + if(status == 200) + var/response = json_decode(file2text(http["CONTENT"])) + if(response) + if(response["status"] == "success") + return text2num(response["result"]) + else + ipintel_handle_error("Bad response from server: [response["status"]].", ip, retry) + if (!retry) + sleep(25) + return .(ip, 1) + + else if(status == 429) + ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip) + return + + else + ipintel_handle_error("Unknown status code: [status].", ip, retry) + if (!retry) + sleep(25) + return .(ip, 1) + + else + ipintel_handle_error("Unable to connect to API.", ip, retry) + if (!retry) + sleep(25) + return .(ip, 1) + + + +proc/ipintel_handle_error(error, ip, retry) + if (retry) + ip_intel_errors++ + error += " Could not check [ip]. Disabling IPINTEL for [ip_intel_errors] minute[( ip_intel_errors == 1 ? "" : "s" )]" + ip_intel_throttle = world.timeofday + (10 * 60 * ip_intel_errors) + else + error += " Attempting retry on [ip]." + log_ipintel(error) + +proc/log_ipintel(text) + log_game("IPINTEL: [text]") + + + + + diff --git a/config/config.txt b/config/config.txt index 5cbfcd0c5b0..f371a024b59 100644 --- a/config/config.txt +++ b/config/config.txt @@ -111,6 +111,18 @@ HOSTEDBY Yournamehere ## Uncomment this to stop people connecting to your server without a registered ckey. (i.e. guest-* are all blocked from connecting) GUEST_BAN +### IPINTEL: +### This allows you to block likely proxies by checking ips against getipintel.net +## Maximum rating: (0.90 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything higher then this number is rejected +#IPINTEL_RATING_MAX 0.90 +## 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) +#IPINTEL_SAVE_GOOD 6 +## 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 1 + + ## Uncomment to allow web client connections #ALLOW_WEBCLIENT diff --git a/tgstation.dme b/tgstation.dme index 94ba8781c4a..b7142475ca6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -833,6 +833,7 @@ #include "code\modules\admin\create_poll.dm" #include "code\modules\admin\create_turf.dm" #include "code\modules\admin\holder2.dm" +#include "code\modules\admin\ipintel.dm" #include "code\modules\admin\IsBanned.dm" #include "code\modules\admin\NewBan.dm" #include "code\modules\admin\player_panel.dm" From d67b9cbe0c9ff2407b95213568a625f266643d4f Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Sat, 23 Apr 2016 10:03:10 -0700 Subject: [PATCH 2/5] Removes debugging messages --- code/modules/admin/IsBanned.dm | 7 ------- 1 file changed, 7 deletions(-) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index a1515f0c86b..fb320a66c02 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -100,21 +100,14 @@ return . if (config.ipintel_email) - world.log << "checking [ckey] [address]" var/datum/ipintel/res = get_ip_intel(address) - world.log << "got res: [res.ip], [res.intel], [res.cache], [res.cachedate], [res.cacheminutesago]" - world.log << "checking [res.intel] vs [config.ipintel_rating_max]" if (res.intel > config.ipintel_rating_max) - world.log << "match" if (admin) - world.log << "admin" log_admin("The admin [key] has been allowed to bypass an IP intel ban. [address] was rated [res.intel*100]% likely to be a bad ip.") message_admins("The admin [key] has been allowed to bypass an IP intel ban. [address] was rated [res.intel*100]% likely to be a bad ip.") addclientmessage(ckey,"You have been allowed to bypass an IP intel ban. Your IP [address] was rated [res.intel*100]% likely to be a bad ip.") else - world.log << "not admin, banning" if (!res.cache) - world.log << "first ban" log_admin("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") message_admins("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") From 2523a0d604cd5bea6a7f4bde2bef4475583bd932 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Sat, 23 Apr 2016 15:31:36 -0700 Subject: [PATCH 3/5] whitespace and code formatting changes. --- code/modules/admin/ipintel.dm | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index cab6d96a996..58a69267a54 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -7,7 +7,7 @@ /datum/ipintel/New() cachedate = SQLtime() -proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE) +/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE) var/datum/ipintel/res = new() res.ip = ip . = res @@ -41,13 +41,13 @@ var/ip_intel_errors = 0 var/http[] = world.Export("http://check.getipintel.net/check.php?ip=[ip]&contact=[config.ipintel_email]&format=json") - if(http) + if (http) var/status = text2num(http["STATUS"]) - if(status == 200) + if (status == 200) var/response = json_decode(file2text(http["CONTENT"])) - if(response) - if(response["status"] == "success") + if (response) + if (response["status"] == "success") return text2num(response["result"]) else ipintel_handle_error("Bad response from server: [response["status"]].", ip, retry) @@ -55,16 +55,14 @@ var/ip_intel_errors = 0 sleep(25) return .(ip, 1) - else if(status == 429) - ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip) + 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, retry) if (!retry) sleep(25) return .(ip, 1) - else ipintel_handle_error("Unable to connect to API.", ip, retry) if (!retry) @@ -72,8 +70,7 @@ var/ip_intel_errors = 0 return .(ip, 1) - -proc/ipintel_handle_error(error, ip, retry) +/proc/ipintel_handle_error(error, ip, retry) if (retry) ip_intel_errors++ error += " Could not check [ip]. Disabling IPINTEL for [ip_intel_errors] minute[( ip_intel_errors == 1 ? "" : "s" )]" @@ -82,7 +79,7 @@ proc/ipintel_handle_error(error, ip, retry) error += " Attempting retry on [ip]." log_ipintel(error) -proc/log_ipintel(text) +/proc/log_ipintel(text) log_game("IPINTEL: [text]") From c37ae784e7a68ee502da57f7295c055bb4020900 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Wed, 11 May 2016 23:40:37 -0700 Subject: [PATCH 4/5] Adds local cache, doesn't check reconnects, fixes bugs --- SQL/database_changelog.txt | 13 ++++++ SQL/tgstation_schema.sql | 2 +- SQL/tgstation_schema_prefixed.sql | 4 +- code/controllers/subsystem/ipintel.dm | 18 +++++++++ code/modules/admin/IsBanned.dm | 2 +- code/modules/admin/ipintel.dm | 58 ++++++++++++++++++--------- tgstation.dme | 1 + 7 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 code/controllers/subsystem/ipintel.dm diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt index e142f20cfab..9835d792f6e 100644 --- a/SQL/database_changelog.txt +++ b/SQL/database_changelog.txt @@ -1,3 +1,16 @@ +16th April 2016 + +Added ipintel table, only required if ip intel is enabled in the config + +CREATE TABLE `ipintel` ( +`ip` INT UNSIGNED NOT NULL , +`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`intel` REAL NOT NULL DEFAULT '0', +PRIMARY KEY ( `ip` ) +) ENGINE = INNODB; + +--------------------------------------------------- + 21 September 2015, by Jordie0608 Modified table 'poll_question', adding columns 'createdby_ckey', 'createdby_ip' and 'for_trialmin' to bring it inline with the schema used by the tg servers. diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 7fb896d93a6..72d08af90f7 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -369,7 +369,7 @@ DROP TABLE IF EXISTS `ipintel`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `ipintel` ( `ip` INT UNSIGNED NOT NULL , -`date` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , `intel` REAL NOT NULL DEFAULT '0', PRIMARY KEY ( `ip` ) ) ENGINE = INNODB; diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index c5bbd407125..ab49b337cf3 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -364,9 +364,9 @@ DROP TABLE IF EXISTS `SS13_ipintel`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `SS13_ipintel` ( `ip` INT UNSIGNED NOT NULL , -`date` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , +`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL , `intel` REAL NOT NULL DEFAULT '0', PRIMARY KEY ( `ip` ) ) ENGINE = INNODB; - +/*!40101 SET character_set_client = @saved_cs_client */; -- Dump completed on 2013-03-24 18:02:35 diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm new file mode 100644 index 00000000000..ecfb6c928f5 --- /dev/null +++ b/code/controllers/subsystem/ipintel.dm @@ -0,0 +1,18 @@ +var/datum/subsystem/ipintel/SSipintel + +/datum/subsystem/ipintel + name = "IP Intel" + priority = -3 + var/enabled = 0 //disable at round start to avoid checking reconnects + var/throttle = 0 + var/errors = 0 + + var/list/cache = list() + +/datum/subsystem/ipintel/New() + NEW_SS_GLOBAL(SSipintel) + +/datum/subsystem/ipintel/Initialize(timeofday, zlevel) + enabled = 1 + . = ..() + diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index fb320a66c02..6b7f75a81df 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -99,7 +99,7 @@ log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]") return . - if (config.ipintel_email) + if (config.ipintel_email && SSipintel.enabled) var/datum/ipintel/res = get_ip_intel(address) if (res.intel > config.ipintel_rating_max) if (admin) diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index 58a69267a54..8b6a1566866 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -4,39 +4,61 @@ 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.ipintel_rating_max) + if (world.realtime < cacherealtime+(config.ipintel_save_good*60*60*10)) + return TRUE + else + if (world.realtime < cacherealtime+(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 || !config.ipintel_email) + if (!ip || !config.ipintel_email || !SSipintel.enabled) return - if (!bypasscache && establish_db_connection()) - var/DBQuery/query = dbcon.NewQuery("SELECT date,intel,TIMESTAMPDIFF(MINUTE,date,NOW()) FROM [format_table_name("ipintel")] WHERE ip = INET_ATON('[ip]') AND ((intel <= [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_good] HOUR > NOW()) OR (intel > [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_bad] HOUR > NOW()))") - query.Execute() - if (query.NextRow()) - res.cache = TRUE - res.cachedate = query.item[1] - res.intel = query.item[2] - res.cacheminutesago = query.item[3] - return + if (!bypasscache) + var/datum/ipintel/cachedintel = SSipintel.cache[ip] + if (cachedintel && cachedintel.is_valid()) + cachedintel.cache = TRUE + return cachedintel + + if (establish_db_connection()) + var/DBQuery/query = dbcon.NewQuery("SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()), UNIX_TIMESTAMP(date) FROM [format_table_name("ipintel")] WHERE ip = INET_ATON('[ip]') AND ((intel <= [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_good] HOUR > NOW()) OR (intel > [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_bad] HOUR > NOW()))") + query.Execute() + if (query.NextRow()) + res.cache = TRUE + res.cachedate = query.item[1] + res.intel = query.item[2] + res.cacheminutesago = query.item[3] + res.cacherealtime = query.item[4]*10 + SSipintel.cache[ip] = res + return res.intel = ip_intel_query(ip) if (updatecache && res.intel >= 0 && establish_db_connection()) - //if you're wondering, we don't add or update the date as its a TIMESTAMP field, and as such, automatically updates at any insert or update - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel)") + SSipintel.cache[ip] = res + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NULL") query.Execute() return -var/ip_intel_throttle = 0 -var/ip_intel_errors = 0 + /proc/ip_intel_query(ip, var/retry=0) . = -1 //default if (!ip) return - if (ip_intel_throttle > world.timeofday) + if (SSipintel.throttle > world.timeofday) return var/http[] = world.Export("http://check.getipintel.net/check.php?ip=[ip]&contact=[config.ipintel_email]&format=json") @@ -72,9 +94,9 @@ var/ip_intel_errors = 0 /proc/ipintel_handle_error(error, ip, retry) if (retry) - ip_intel_errors++ - error += " Could not check [ip]. Disabling IPINTEL for [ip_intel_errors] minute[( ip_intel_errors == 1 ? "" : "s" )]" - ip_intel_throttle = world.timeofday + (10 * 60 * ip_intel_errors) + SSipintel.errors++ + error += " Could not check [ip]. Disabling IPINTEL for [SSipintel.errors] minute[( SSipintel.errors == 1 ? "" : "s" )]" + SSipintel.throttle = world.timeofday + (10 * 60 * SSipintel.errors) else error += " Attempting retry on [ip]." log_ipintel(error) diff --git a/tgstation.dme b/tgstation.dme index b7142475ca6..301e7c4f1bd 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -136,6 +136,7 @@ #include "code\controllers\subsystem\fastprocess.dm" #include "code\controllers\subsystem\garbage.dm" #include "code\controllers\subsystem\icon_smooth.dm" +#include "code\controllers\subsystem\ipintel.dm" #include "code\controllers\subsystem\jobs.dm" #include "code\controllers\subsystem\lighting.dm" #include "code\controllers\subsystem\machines.dm" From d220416ed3fcbcdc122a31309c90e9257bd5b6a1 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Fri, 17 Jun 2016 03:54:42 -0700 Subject: [PATCH 5/5] Makes ipintel only warn, fixes some cache things. --- code/controllers/configuration.dm | 11 +++-- code/controllers/subsystem/ipintel.dm | 5 ++- code/modules/admin/IsBanned.dm | 16 ------- code/modules/admin/ipintel.dm | 62 +++++++++++++++++++-------- code/modules/client/client_defines.dm | 1 + code/modules/client/client_procs.dm | 11 +++++ config/config.txt | 15 ++++--- 7 files changed, 73 insertions(+), 48 deletions(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index f141715dbbd..f8aec416e19 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -72,9 +72,10 @@ //IP Intel vars var/ipintel_email - var/ipintel_rating_max = 1 - var/ipintel_save_good = 3 + var/ipintel_rating_bad = 1 + var/ipintel_save_good = 12 var/ipintel_save_bad = 1 + var/ipintel_domain = "check.getipintel.net" var/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt var/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt @@ -398,8 +399,10 @@ if("ipintel_email") if (value != "ch@nge.me") config.ipintel_email = value - if("ipintel_rating_max") - config.ipintel_rating_max = text2num(value) + if("ipintel_rating_bad") + config.ipintel_rating_bad = text2num(value) + if("ipintel_domain") + config.ipintel_domain = value if("ipintel_save_good") config.ipintel_save_good = text2num(value) if("ipintel_save_bad") diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index ecfb6c928f5..b1b99d59392 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -1,8 +1,9 @@ var/datum/subsystem/ipintel/SSipintel /datum/subsystem/ipintel - name = "IP Intel" - priority = -3 + name = "XKeyScore" + init_order = -10 + flags = SS_NO_FIRE var/enabled = 0 //disable at round start to avoid checking reconnects var/throttle = 0 var/errors = 0 diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 6b7f75a81df..75eaeab65fb 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -99,22 +99,6 @@ log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]") return . - if (config.ipintel_email && SSipintel.enabled) - var/datum/ipintel/res = get_ip_intel(address) - if (res.intel > config.ipintel_rating_max) - if (admin) - log_admin("The admin [key] has been allowed to bypass an IP intel ban. [address] was rated [res.intel*100]% likely to be a bad ip.") - message_admins("The admin [key] has been allowed to bypass an IP intel ban. [address] was rated [res.intel*100]% likely to be a bad ip.") - addclientmessage(ckey,"You have been allowed to bypass an IP intel ban. Your IP [address] was rated [res.intel*100]% likely to be a bad ip.") - else - if (!res.cache) - log_admin("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") - message_admins("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") - - . = list("reason"="IP_INTEL", "desc"="\nYour IP [address] was rated [res.intel*100]% likely to be a bad IP (spammer/proxy). The highest allowed to connect is [config.ipintel_rating_max*100]%.\nThis rating was retrieved [res.cacheminutesago] minutes ago on [res.cachedate] and refreshes in [(config.ipintel_save_bad*60)-res.cacheminutesago] minutes.") - log_access("Failed Login: [key] [computer_id] [address] - IP intel rated [res.intel*100]% likely to be a bad ip.") - return - . = ..() //default pager ban stuff if (.) //byond will not trigger isbanned() for "global" host bans, diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index 8b6a1566866..d176de32bb5 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -14,14 +14,13 @@ . = FALSE if (intel < 0) return - if (intel <= config.ipintel_rating_max) + if (intel <= config.ipintel_rating_bad) if (world.realtime < cacherealtime+(config.ipintel_save_good*60*60*10)) return TRUE else if (world.realtime < cacherealtime+(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 @@ -35,33 +34,50 @@ return cachedintel if (establish_db_connection()) - var/DBQuery/query = dbcon.NewQuery("SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()), UNIX_TIMESTAMP(date) FROM [format_table_name("ipintel")] WHERE ip = INET_ATON('[ip]') AND ((intel <= [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_good] HOUR > NOW()) OR (intel > [config.ipintel_rating_max] AND date + INTERVAL [config.ipintel_save_bad] HOUR > NOW()))") + var/DBQuery/query = dbcon.NewQuery({" + SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) + FROM [format_table_name("ipintel")] + WHERE + ip = INET_ATON('[ip]') + AND (( + intel < [config.ipintel_rating_bad] + AND + date + INTERVAL [config.ipintel_save_good] HOUR > NOW() + ) OR ( + intel >= [config.ipintel_rating_bad] + AND + date + INTERVAL [config.ipintel_save_bad] HOUR > NOW() + )) + "}) query.Execute() if (query.NextRow()) res.cache = TRUE res.cachedate = query.item[1] res.intel = query.item[2] res.cacheminutesago = query.item[3] - res.cacherealtime = query.item[4]*10 + res.cacherealtime = world.realtime - (query.item[3]*10*60) SSipintel.cache[ip] = res return res.intel = ip_intel_query(ip) - if (updatecache && res.intel >= 0 && establish_db_connection()) + if (updatecache && res.intel >= 0) SSipintel.cache[ip] = res - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NULL") - query.Execute() + if (establish_db_connection()) + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()") + query.Execute() return -/proc/ip_intel_query(ip, var/retry=0) +/proc/ip_intel_query(ip, var/retryed=0) . = -1 //default if (!ip) return if (SSipintel.throttle > world.timeofday) return + if (!SSipintel.enabled) + return - var/http[] = world.Export("http://check.getipintel.net/check.php?ip=[ip]&contact=[config.ipintel_email]&format=json") + var/list/http[] = world.Export("http://[config.ipintel_domain]/check.php?ip=[ip]&contact=[config.ipintel_email]&format=json&flags=f") if (http) var/status = text2num(http["STATUS"]) @@ -70,10 +86,17 @@ var/response = json_decode(file2text(http["CONTENT"])) if (response) if (response["status"] == "success") - return text2num(response["result"]) + 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, retry) - if (!retry) + ipintel_handle_error("Bad response from server: [response["status"]].", ip, retryed) + if (!retryed) sleep(25) return .(ip, 1) @@ -81,28 +104,29 @@ ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip, 1) return else - ipintel_handle_error("Unknown status code: [status].", ip, retry) - if (!retry) + 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, retry) - if (!retry) + ipintel_handle_error("Unable to connect to API.", ip, retryed) + if (!retryed) sleep(25) return .(ip, 1) -/proc/ipintel_handle_error(error, ip, retry) - if (retry) +/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 * 60 * SSipintel.errors) + 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_defines.dm b/code/modules/client/client_defines.dm index 78b19cb7198..7443a4a17ed 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -45,6 +45,7 @@ // Used by html_interface module. var/hi_last_pos + var/ip_intel = "Disabled" //datum that controls the displaying and hiding of tooltips var/datum/tooltip/tooltips diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index a888c025b7e..c9fa33716bf 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -210,6 +210,8 @@ var/next_external_rsc = 0 sync_client_with_db() + check_ip_intel() + send_resources() if(!void) @@ -322,6 +324,15 @@ var/next_external_rsc = 0 var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]','[sql_ckey]','[sql_ip]','[sql_computerid]');") query_accesslog.Execute() +/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.ipintel_email) + var/datum/ipintel/res = get_ip_intel(address) + if (res.intel >= 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/add_verbs_from_config() if(config.see_own_notes) verbs += /client/proc/self_notes diff --git a/config/config.txt b/config/config.txt index f371a024b59..d04cd64cd3d 100644 --- a/config/config.txt +++ b/config/config.txt @@ -112,16 +112,17 @@ HOSTEDBY Yournamehere GUEST_BAN ### IPINTEL: -### This allows you to block likely proxies by checking ips against getipintel.net -## Maximum rating: (0.90 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything higher then this number is rejected -#IPINTEL_RATING_MAX 0.90 +### This allows you to detect likely proxies by checking ips against getipintel.net +## Rating to warn at: (0.90 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.90 ## 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) -#IPINTEL_SAVE_GOOD 6 +## 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 1 - +#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 ## Uncomment to allow web client connections #ALLOW_WEBCLIENT