diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql
index 750add2fd86..1845880ba52 100644
--- a/SQL/paradise_schema.sql
+++ b/SQL/paradise_schema.sql
@@ -549,3 +549,15 @@ CREATE TABLE `ipintel` (
PRIMARY KEY ( `ip` )
) ENGINE = INNODB;
/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `vpn_whitelist`
+--
+DROP TABLE IF EXISTS `vpn_whitelist`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `vpn_whitelist` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `reason` text
+ PRIMARY KEY (`ckey`)
+) ENGINE=INNODB;
diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql
index 1a663563cab..75dc19ff8cb 100644
--- a/SQL/paradise_schema_prefixed.sql
+++ b/SQL/paradise_schema_prefixed.sql
@@ -547,4 +547,16 @@ CREATE TABLE `SS13_ipintel` (
`intel` REAL NOT NULL DEFAULT '0',
PRIMARY KEY ( `ip` )
) ENGINE = INNODB;
-/*!40101 SET character_set_client = @saved_cs_client */;
\ No newline at end of file
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `SS13_vpn_whitelist`
+--
+DROP TABLE IF EXISTS `SS13_vpn_whitelist`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `SS13_vpn_whitelist` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `reason` text,
+ PRIMARY KEY (`ckey`)
+) ENGINE=INNODB;
\ No newline at end of file
diff --git a/SQL/updates/5-6.sql b/SQL/updates/5-6.sql
index f5f04d0f441..67021b90e8b 100644
--- a/SQL/updates/5-6.sql
+++ b/SQL/updates/5-6.sql
@@ -1,8 +1,16 @@
#Updating the SQL from version 5 to version 6. -Kyep
+
#Creating a table to track the results of VPN/proxy lookups for IPs
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;
\ No newline at end of file
+) ENGINE = INNODB;
+
+#Creating a table to track which ckeys are whitelisted for use of VPNs
+CREATE TABLE `vpn_whitelist` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `reason` text,
+ PRIMARY KEY (`ckey`)
+) ENGINE=INNODB;
\ No newline at end of file
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index a424fb1ac55..b6f1fb4bc30 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -134,6 +134,7 @@
var/ipintel_save_bad = 1
var/ipintel_domain = "check.getipintel.net"
var/ipintel_maxplaytime = 0
+ var/ipintel_whitelist = 0
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
@@ -323,6 +324,8 @@
config.ipintel_save_bad = text2num(value)
if("ipintel_maxplaytime")
config.ipintel_maxplaytime = text2num(value)
+ if("ipintel_whitelist")
+ config.ipintel_whitelist = 1
if("log_ooc")
config.log_ooc = 1
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 4fcf95dbfd2..ca5b91eaa1c 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -273,6 +273,15 @@ var/global/nologevent = 0
show_note(key)
+/datum/admins/proc/vpn_whitelist()
+ set category = "Admin"
+ set name = "VPN Ckey Whitelist"
+ if(!check_rights(R_ADMIN))
+ return
+ var/key = stripped_input(usr, "Enter ckey to add/remove, or leave blank to cancel:", "VPN Whitelist add/remove", max_length=32)
+ if(key)
+ vpn_whitelist_panel(key)
+
/datum/admins/proc/access_news_network() //MARKER
set category = "Event"
set name = "Access Newscaster Network"
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 4a455cd8e5d..06c2361b90a 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -53,6 +53,7 @@ var/list/admin_verbs_admin = list(
/datum/admins/proc/PlayerNotes,
/client/proc/cmd_mentor_say,
/datum/admins/proc/show_player_notes,
+ /datum/admins/proc/vpn_whitelist,
/client/proc/free_slot, /*frees slot for chosen job*/
/client/proc/toggleattacklogs,
/client/proc/toggleadminlogs,
diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm
index 138e9b4cc0f..83853aa17a4 100644
--- a/code/modules/admin/ipintel.dm
+++ b/code/modules/admin/ipintel.dm
@@ -132,7 +132,52 @@
log_game("IPINTEL: [text]")
log_debug("IPINTEL: [text]")
+/proc/vpn_whitelist_check(target_ckey)
+ var/target_sql_ckey = ckey(target_ckey)
+ var/DBQuery/query_whitelist_check = dbcon.NewQuery("SELECT * FROM [format_table_name("vpn_whitelist")] WHERE ckey = '[target_sql_ckey]'")
+ if(!query_whitelist_check.Execute())
+ var/err = query_whitelist_check.ErrorMsg()
+ log_debug("SQL ERROR on proc/vpn_whitelist_check for ckey '[target_sql_ckey]'. Error : \[[err]\]\n")
+ return FALSE
+ if(query_whitelist_check.NextRow())
+ return TRUE // At least one row in the whitelist names their ckey. That means they are whitelisted.
+ return FALSE
+/proc/vpn_whitelist_add(target_ckey)
+ var/target_sql_ckey = ckey(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
+ reason_string = sanitizeSQL(reason_string)
+ var/DBQuery/query_whitelist_add = dbcon.NewQuery("INSERT INTO [format_table_name("vpn_whitelist")] (ckey,reason) VALUES ('[target_sql_ckey]','[reason_string]')")
+ if(!query_whitelist_add.Execute())
+ var/err = query_whitelist_add.ErrorMsg()
+ log_debug("SQL ERROR on proc/vpn_whitelist_add for ckey '[target_sql_ckey]'. Error : \[[err]\]\n")
+ return FALSE
+ return TRUE
+/proc/vpn_whitelist_remove(target_ckey)
+ var/target_sql_ckey = ckey(target_ckey)
+ var/DBQuery/query_whitelist_remove = dbcon.NewQuery("DELETE FROM [format_table_name("vpn_whitelist")] WHERE ckey = '[target_sql_ckey]'")
+ if(!query_whitelist_remove.Execute())
+ var/err = query_whitelist_remove.ErrorMsg()
+ log_debug("SQL ERROR on proc/vpn_whitelist_remove for ckey '[target_sql_ckey]'. Error : \[[err]\]\n")
+ return FALSE
+ return TRUE
-
+/proc/vpn_whitelist_panel(var/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)
+ 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.")
\ No newline at end of file
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index 8f57b49092e..5cc9296dd6a 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -72,6 +72,7 @@
control_freak = CONTROL_FREAK_ALL | CONTROL_FREAK_SKIN | CONTROL_FREAK_MACROS
var/ip_intel = "Disabled"
+ var/iprestricted = FALSE
var/datum/click_intercept/click_intercept = null
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index 0fdf29f1097..c11e68ff1b1 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -545,13 +545,24 @@
return
if(is_connecting_from_localhost())
- log_debug("IPIntel: skip check for player [key_name_admin(src)] connecting from localhost.")
+ log_debug("check_ip_intel: skip check for player [key_name_admin(src)] connecting from localhost.")
return
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
+ if(ip_intel >= config.ipintel_rating_bad)
+ if(config.ipintel_whitelist)
+ if(vpn_whitelist_check(ckey))
+ log_debug("IPIntel: Player [ckey] IP [address] rated [ip_intel*100]% passes check due to whitelisted status.")
+ else
+ message_admins("IPIntel: [key_name_admin(src)] IP [address] was restricted due to likely proxy/VPN.")
+ iprestricted = TRUE
+ vpn_warning()
+ else
+ message_admins("IPIntel: [key_name_admin(src)] IP [address] rated [ip_intel*100]% likely to be a Proxy/VPN.")
+
+/client/proc/vpn_warning()
+ to_chat(src, "You are using a proxy/VPN. That is not allowed. Either turn it off, or request whitelisting here: [config.banappeals]")
#undef TOPIC_SPAM_DELAY
#undef UPLOAD_LIMIT
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 427bcfcb659..fe78eaef3b2 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -165,6 +165,9 @@
if(!tos_consent)
to_chat(usr, "You must consent to the terms of service before you can join!")
return 0
+ if(client.iprestricted)
+ client.vpn_warning()
+ return 0
ready = !ready
new_player_panel_proc()
@@ -182,7 +185,11 @@
return 0
if(alert(src,"Are you sure you wish to observe? You cannot normally join the round after doing this!","Player Setup","Yes","No") == "Yes")
- if(!client) return 1
+ if(!client)
+ return 1
+ if(client.iprestricted)
+ client.vpn_warning()
+ return 0
var/mob/dead/observer/observer = new()
src << browse(null, "window=playersetup")
spawning = 1
@@ -220,7 +227,9 @@
if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
to_chat(usr, "The round is either not ready, or has already finished...")
return
-
+ if(client.iprestricted)
+ client.vpn_warning()
+ return 0
if(client.prefs.species in GLOB.whitelisted_species)
if(!is_alien_whitelisted(src, client.prefs.species) && config.usealienwhitelist)
diff --git a/config/example/config.txt b/config/example/config.txt
index ca81181c027..8a08ef53e0c 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -191,11 +191,13 @@ GUEST_BAN
## 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 72
## 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 6
+#IPINTEL_SAVE_BAD 24
## 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
## Ignore players with this many hours of playtime. Requires USE_EXP_TRACKING
#IPINTEL_MAXPLAYTIME 10
+## Require players (except the previous ones with playtime, if enabled) to be whitelisted to use proxies/VPNs.
+#IPINTEL_WHITELIST
## Comment to disable checking for the cid randomizer dll. (disabled if database isn't enabled or connected)
CHECK_RANDOMIZER