Implements IPIntel, Panic Bunker, and custom access control (#2436)

What it says on the tin.

IPIntel and BYOND account age relating panic bunker settings from TG. Also implements methods for potentially tagging VM users. Ontop of all of this, creates a spiffy UI for admins with R_SERVER to adjust the settings of the entire suite at runtime.
This commit is contained in:
skull132
2017-05-26 13:26:58 +03:00
committed by GitHub
parent c27866e4af
commit 7039840323
18 changed files with 492 additions and 72 deletions
+11 -5
View File
@@ -6,7 +6,7 @@
var/roundstart_hour = 0
//Returns the world time in english
proc/worldtime2text(time = world.time, timeshift = 1)
/proc/worldtime2text(time = world.time, timeshift = 1)
if(!roundstart_hour) roundstart_hour = rand(0, 23)
return timeshift ? time2text(time+(36000*roundstart_hour), "hh:mm") : time2text(time, "hh:mm")
@@ -15,14 +15,14 @@ proc/worldtime2text(time = world.time, timeshift = 1)
worldtime2text()
. = text2num(time2text(world.time + (36000 * roundstart_hour), "hh"))
proc/worlddate2text()
/proc/worlddate2text()
return num2text(game_year) + "-" + time2text(world.timeofday, "MM-DD")
proc/time_stamp()
/proc/time_stamp()
return time2text(world.timeofday, "hh:mm:ss")
/* Returns 1 if it is the selected month and day */
proc/isDay(var/month, var/day)
/proc/isDay(var/month, var/day)
if(isnum(month) && isnum(day))
var/MM = text2num(time2text(world.timeofday, "MM")) // get the current month
var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day
@@ -35,7 +35,7 @@ proc/isDay(var/month, var/day)
var/next_duration_update = 0
var/last_round_duration = 0
proc/round_duration()
/proc/round_duration()
if(last_round_duration && world.time < next_duration_update)
return last_round_duration
@@ -57,3 +57,9 @@ proc/round_duration()
if (world.timeofday < rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS!
return midnight_rollovers++
return midnight_rollovers
//returns timestamp in a sql and ISO 8601 friendly format
/proc/SQLtime(timevar)
if(!timevar)
timevar = world.realtime
return time2text(timevar, "YYYY-MM-DD hh:mm:ss")
+35
View File
@@ -265,6 +265,20 @@ var/list/gamemode_cache = list()
var/log_gelf_ip = ""
var/log_gelf_port = ""
//IP Intel vars
var/ipintel_email
var/ipintel_rating_bad = 1
var/ipintel_rating_kick = 0
var/ipintel_save_good = 12
var/ipintel_save_bad = 1
var/ipintel_domain = "check.getipintel.net"
// Access control/Panic bunker settings.
var/access_deny_new_players = 0
var/access_deny_new_accounts = -1
var/access_deny_vms = 0
var/access_warn_vms = 0
/datum/configuration/New()
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
for (var/T in L)
@@ -819,6 +833,27 @@ var/list/gamemode_cache = list()
if("log_gelf_port")
config.log_gelf_port = value
if("ipintel_email")
if (value != "ch@nge.me")
ipintel_email = value
if("ipintel_rating_bad")
ipintel_rating_bad = text2num(value)
if("ipintel_rating_kick")
ipintel_rating_kick = text2num(value)
if("ipintel_domain")
ipintel_domain = value
if("ipintel_save_good")
ipintel_save_good = text2num(value)
if("ipintel_save_bad")
ipintel_save_bad = text2num(value)
if("access_deny_new_accounts")
access_deny_new_accounts = text2num(value) >= 0 ? text2num(value) : -1
if("access_deny_vms")
access_deny_vms = text2num(value)
if("access_warn_vms")
access_warn_vms = text2num(value)
else
log_misc("Unknown setting in configuration: '[name]'")
+18
View File
@@ -0,0 +1,18 @@
var/datum/controller/subsystem/ipintel/SSipintel
/datum/controller/subsystem/ipintel
name = "XKeyScore"
init_order = SS_INIT_MISC_FIRST
flags = SS_NO_FIRE
var/enabled = 0 //disable at round start to avoid checking reconnects
var/throttle = 0
var/errors = 0
var/list/cache = list()
/datum/controller/subsystem/ipintel/New()
NEW_SS_GLOBAL(SSipintel)
/datum/controller/subsystem/ipintel/Initialize()
enabled = 1
. = ..()
+27 -1
View File
@@ -135,7 +135,33 @@
update_connection_data(C)
return
var/list/conn_info = json_decode(data)
var/list/data_object = json_decode(data)
if (!data_object || !data_object.len)
log_debug("CONN DATA: [C.ckey] has no connection data to showcase.")
return
if (data_object["vms"])
var/A = data_object["vms"]
var/count = 0
if (A & 1)
count++
if (A & 2)
count++
if (config.access_deny_vms && count >= config.access_deny_vms)
log_access("Failed Login: [C.ckey] [C.address] [C.computer_id] - Matching [count]/[config.access_deny_vms] VM identifiers. IDs: [A].", ckey = C.ckey)
message_admins("Failed Login: [C.ckey] [C.address] [C.computer_id] - Matching [count]/[config.access_deny_vms] VM identifiers. IDs: [A].")
spawn(20)
if (C)
del(C)
return
if (config.access_warn_vms && count >= config.access_warn_vms)
log_access("Notice: [key_name(C)] [C.address] [C.computer_id] - Matching [count] VM identifiers. IDs: [A].", ckey = C.ckey)
message_admins("Notice: [key_name(C)] [C.address] [C.computer_id] - Matching [count] VM identifiers. IDs: [A].")
var/list/conn_info = data_object["conn"]
if (!conn_info || !conn_info.len)
return
+2 -1
View File
@@ -169,7 +169,8 @@ var/list/admin_verbs_server = list(
/client/proc/admin_edit_motd,
/client/proc/toggle_recursive_explosions,
/client/proc/restart_controller,
/client/proc/cmd_ss_panic
/client/proc/cmd_ss_panic,
/client/proc/configure_access_control
)
var/list/admin_verbs_debug = list(
/client/proc/getruntimelog, // allows us to access runtime logs to somebody,
+129
View File
@@ -0,0 +1,129 @@
/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.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
. = res
if (!ip || !config.ipintel_email || !SSipintel.enabled)
return
if (!bypasscache)
var/datum/ipintel/cachedintel = SSipintel.cache[ip]
if (cachedintel && cachedintel.is_valid())
cachedintel.cache = TRUE
return cachedintel
if(establish_db_connection(dbcon))
var/DBQuery/query_get_ip_intel = dbcon.NewQuery({"
SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW())
FROM ss13_ipintel
WHERE
ip = INET6_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()
))
"})
if(!query_get_ip_intel.Execute())
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
return
res.intel = ip_intel_query(ip)
if (updatecache && res.intel >= 0)
SSipintel.cache[ip] = res
if(establish_db_connection(dbcon))
var/DBQuery/query_add_ip_intel = dbcon.NewQuery("INSERT INTO ss13_ipintel (ip, intel) VALUES (INET6_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()")
query_add_ip_intel.Execute()
/proc/ip_intel_query(ip, var/retryed=0)
. = -1 //default
if (!ip)
return
if (SSipintel.throttle > world.timeofday)
return
if (!SSipintel.enabled)
return
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"])
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(25)
return .(ip, 1)
else
ipintel_handle_error("Bad response from server: [response["status"]].", ip, retryed)
if (!retryed)
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, retryed)
if (!retryed)
sleep(25)
return .(ip, 1)
else
ipintel_handle_error("Unable to connect to API.", ip, retryed)
if (!retryed)
sleep(25)
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]")
log_debug("IPINTEL: [text]")
+4
View File
@@ -1606,6 +1606,10 @@
return
else if(href_list["access_control"])
access_control_topic(href_list["access_control"])
return
mob/living/proc/can_centcom_reply()
return 0
@@ -0,0 +1,99 @@
/client/proc/configure_access_control()
set category = "Server"
set name = "Configure Access Control"
if (!check_rights(R_SERVER))
return
var/datum/browser/config_window = new(usr, "access_control", "Access Control")
config_window.add_head_content("<title>Access Control</title>")
var/data = "These settings control who can access the server during this round.<br>"
data += "They must be reset every single time the server restarts.<br>"
data += "<b>If you do not know what these do, you shouldn't be touching them!</b><hr>"
data += "<h2>IP Intel Settings:</h2><br><ul>"
data += "<li>Current warning level: [config.ipintel_rating_bad ? "[config.ipintel_rating_bad]" : "<font color='red'>DISABLED</font>"]. <a href='?_src_=holder;access_control=intel_bad'>Edit</a></li>"
data += "<li>Current kick level: [config.ipintel_rating_kick ? "[config.ipintel_rating_kick]" : "<font color='red'>DISABLED</font>"]. <a href='?_src_=holder;access_control=intel_kick'>Edit</a></li>"
data += "</ul><hr>"
data += "<h2>Player Age Settings:</h2><br><ul>"
data += "<li>New players: [config.access_deny_new_players ? "<font color='green'>ALLOWED</font>" : "<font color='red'>DENIED</font>"]. <a href='?_src_=holder;access_control=new_players;'>Toggle</a></li>"
data += "<li>Account age restriction: [config.access_deny_new_accounts == -1 ? "<font color='red'>DISABLED</font>" : "[config.access_deny_new_accounts] DAYS"]. <a href='?_src_=holder;access_control=new_accounts;'>Edit</a></li>"
data += "</ul><hr>"
data += "<h2>VM Detection Settings:</h2><br><ul>"
data += "<li>VM identifier count to warn on: [config.access_warn_vms ? "[config.access_warn_vms]" : "<font color='red'>DISABLED</font>"]. <a href='?_src_=holder;access_control=vm_warn;'>Edit</a></li>"
data += "<li>VM identifier count to kick on: [config.access_deny_vms ? "[config.access_deny_vms]" : "<font color='red'>DISABLED</font>"]. <a href='?_src_=holder;access_control=vm_kick;'>Edit</a></li>"
data += "</ul>"
config_window.set_user(src.mob)
config_window.set_content(data)
config_window.open()
/datum/admins/proc/access_control_topic(control)
if (!control)
to_chat(usr, "<span class='warning'>No control option sent. Cancelling.</span>")
return
if (!check_rights(R_SERVER))
log_and_message_admins("has tried editing access control without the permissions to do so.")
return
switch(control)
if ("intel_bad")
var/num = input("Please set the new threshold for warning based on IPintel (0 to disable).", "New Threshold", config.ipintel_rating_kick) as num
if (num < 0 || num > 1)
to_chat(usr, "<span class='warning'>Invalid number. Cancelling.</span>")
return
config.ipintel_rating_bad = num
if (num)
log_and_message_admins("has set the IPIntel warn threshold to [num].")
else
log_and_message_admins("has disabled warn based on IPIntel.")
if ("intel_kick")
var/num = input("Please set the new threshold for kicking based on IPintel (0 to disable).", "New Threshold", config.ipintel_rating_kick) as num
if (num < 0 || num > 1)
to_chat(usr, "<span class='warning'>Invalid number. Cancelling.</span>")
return
config.ipintel_rating_kick = num
if (num)
log_and_message_admins("has set the IPIntel kick threshold to [num].")
else
log_and_message_admins("has disabled kicking based on IPIntel.")
if ("new_players")
config.access_deny_new_players = !config.access_deny_new_players
log_and_message_admins("has [config.access_deny_new_players ? "ENABLED" : "DISABLED"] the kicking of new players.")
if ("new_accounts")
var/num = input("Please set the new threshold for denying access based on BYOND account age. (-1 to disable.)", "New Threshold", config.access_deny_new_accounts) as num
if (num < 0 && num != -1)
to_chat(usr, "<span class='warning'>Invalid number. Cancelling.</span>")
return
config.access_deny_new_accounts = num
if (num != -1)
log_and_message_admins("has set the access barrier for new BYOND accounts to [num] days.")
else
log_and_message_admins("has disabled kicking based on BYOND account age.")
if ("vm_warn")
var/num = input("Please set the new threshold for warning on login based on positive VM identifiers. (0 to disable.)", "New Threshold") in list(0, 1, 2)
config.access_warn_vms = num
if (num)
log_and_message_admins("has set players with [config.access_warn_vms] positive identifiers out of 2 for VM usage to be warned.")
else
log_and_message_admins("has disabled warnings based on potential VM usage.")
if ("vm_kick")
var/num = input("Please set the new threshold for warning on login based on positive VM identifiers. (0 to disable.)", "New Threshold") in list(0, 1, 2)
config.access_deny_vms = num
if (num)
log_and_message_admins("has set players with [config.access_deny_vms] positive identifiers out of 2 for VM usage to be warned.")
else
log_and_message_admins("has disabled warnings based on potential VM usage.")
else
to_chat(usr, "<span class='danger'>Unknown control message sent. Cancelling.</span>")
owner.configure_access_control()
@@ -9,6 +9,19 @@ var/list/VVlocked = list("vars", "holder", "client", "virus", "viruses", "cuffed
var/list/VVicon_edit_lock = list("icon", "icon_state", "overlays", "underlays")
var/list/VVckey_edit = list("key", "ckey")
// The paranoia box. Specify a var name => bitflags required to edit it.
// Allows the securing of specific variables for, for example, config. That alter
// how players could join! Definitely not something you want folks to touch if they
// don't have the perms.
var/list/VVdynamic_lock = list(
"access_deny_new_players" = R_SERVER,
"access_deny_new_accounts" = R_SERVER,
"access_deny_vms" = R_SERVER,
"access_warn_vms" = R_SERVER,
"ipintel_rating_bad" = R_SERVER,
"ipintel_rating_kick" = R_SERVER
)
/*
/client/proc/cmd_modify_object_variables(obj/O as obj|mob|turf|area in world) // Acceptable 'in world', as VV would be incredibly hampered otherwise
set category = "Debug"
@@ -171,6 +184,8 @@ var/list/VVckey_edit = list("key", "ckey")
if(!check_rights(R_SPAWN|R_DEBUG|R_DEV)) return
if(variable in VVicon_edit_lock)
if(!check_rights(R_FUN|R_DEBUG|R_DEV)) return
if(VVdynamic_lock[variable])
if(!check_rights(VVdynamic_lock[variable])) return
if(isnull(variable))
usr << "Unable to determine variable type."
@@ -364,6 +379,8 @@ var/list/VVckey_edit = list("key", "ckey")
if(!check_rights(R_SPAWN|R_DEBUG|R_DEV)) return
if(param_var_name in VVicon_edit_lock)
if(!check_rights(R_FUN|R_DEBUG|R_DEV)) return
if(VVdynamic_lock[variable])
if(!check_rights(VVdynamic_lock[variable])) return
variable = param_var_name
@@ -426,6 +443,8 @@ var/list/VVckey_edit = list("key", "ckey")
if(!check_rights(R_SPAWN|R_DEBUG|R_DEV)) return
if(variable in VVicon_edit_lock)
if(!check_rights(R_FUN|R_DEBUG|R_DEV)) return
if(VVdynamic_lock[variable])
if(!check_rights(VVdynamic_lock[variable])) return
if(!autodetect_class)
+2 -1
View File
@@ -197,6 +197,7 @@ proc/cmd_admin_mute(mob/M as mob, mute_type, automute = 0)
if(M.client.prefs.muted & mute_type)
muteunmute = "unmuted"
M.client.prefs.muted &= ~mute_type
M.client.spam_alert = 0
else
muteunmute = "muted"
M.client.prefs.muted |= mute_type
@@ -623,7 +624,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
if (!holder)
src << "Only administrators may use this command."
return
for(var/datum/job/job in SSjobs.occupations)
src << "[job.title]: [job.total_positions]"
feedback_add_details("admin_verb","LFS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+3 -2
View File
@@ -178,7 +178,8 @@ var/list/asset_datums = list()
"bootstrap.min.css" = 'html/bootstrap/css/bootstrap.min.css',
"bootstrap.min.js" = 'html/bootstrap/js/bootstrap.min.js',
"jquery-2.0.0.min.js" = 'html/jquery/jquery-2.0.0.min.js',
"ie-truth.min.js" = 'html/iestats/ie-truth.min.js'
"ie-truth.min.js" = 'html/iestats/ie-truth.min.js',
"conninfo.min.js" = 'html/iestats/conninfo.min.js'
)
/datum/asset/simple/paper
@@ -221,7 +222,7 @@ var/list/asset_datums = list()
var/name = "pill[i].png"
register_asset(name, icon('icons/obj/chemical.dmi', "pill[i]"))
assets += name
for (var/sprite in bottle_sprites)
var/name = "[sprite].png"
register_asset(name, icon('icons/obj/chemical.dmi', sprite))
+4 -1
View File
@@ -1,6 +1,6 @@
/client
parent_type = /datum
////////////////
//ADMIN THINGS//
////////////////
@@ -41,6 +41,7 @@
var/info_sent = 0
// comment out the line below when debugging locally to enable the options & messages menu
//control_freak = 1
var/ip_intel = "Disabled"
var/received_discord_pm = -99999
var/discord_admin //IRC- no more IRC, K? Discord admin that spoke with them last.
@@ -55,6 +56,8 @@
var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id
var/whitelist_status = 0 //Used to determine what whitelists the player has access to. Uses bitflag values!
var/need_saves_migrated = "Requires database" //Used to determine whether or not the ckey needs their saves migrated over to the database. Default is 0 upon successful connection.
var/account_age = -1 // Age on the BYOND account in days.
var/account_join_date = null // Date of the BYOND account creation in ISO 8601 format.
preload_rsc = 0 // This is 0 so we can set it to an URL once the player logs in and have them download the resources from a different server.
+89 -37
View File
@@ -222,6 +222,7 @@
src << "<span class='danger'>You have tripped the macro filter. An auto-mute was applied.</span>"
last_message_time = world.time
spam_alert = 4
return 1
else
spam_alert = max(0, spam_alert--)
@@ -292,6 +293,35 @@
log_client_to_db()
// New player, and we don't want any.
if (!holder)
if (config.access_deny_new_players && !player_age)
log_access("Failed Login: [key] [computer_id] [address] - New player attempting connection during panic bunker.", ckey = ckey)
message_admins("Failed Login: [key] [computer_id] [address] - New player attempting connection during panic bunker.")
to_chat(src, "<span class='danger'>Apologies, but the server is currently not accepting connections from never before seen players.</span>")
del(src)
return 0
// Check if the account is too young.
if (config.access_deny_new_accounts != -1 && account_age != -1 && account_age <= config.access_deny_new_accounts)
log_access("Failed Login: [key] [computer_id] [address] - Account too young to play. [account_age] days.", ckey = ckey)
message_admins("Failed Login: [key] [computer_id] [address] - Account too young to play. [account_age] days.")
to_chat(src, "<span class='danger'>Apologies, but the server is currently not accepting connections from BYOND accounts this young.</span>")
del(src)
return 0
if (byond_version < config.client_error_version)
src << "<span class='danger'><b>Your version of BYOND is too old!</b></span>"
src << config.client_error_message
src << "Your version: [byond_version]."
src << "Required version: [config.client_error_version] or later."
src << "Visit http://www.byond.com/download/ to get the latest version of BYOND."
if (holder)
src << "Admins get a free pass. However, <b>please</b> update your BYOND as soon as possible. Certain things may cause crashes if you play with your present version."
else
del(src)
return 0
//preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum)
prefs = preferences_datums[ckey]
if(!prefs)
@@ -307,19 +337,6 @@
prefs.sanitize_preferences()
if (byond_version < config.client_error_version)
src << "<span class='danger'><b>Your version of BYOND is too old!</b></span>"
src << config.client_error_message
src << "Your version: [byond_version]."
src << "Required version: [config.client_error_version] or later."
src << "Visit http://www.byond.com/download/ to get the latest version of BYOND."
if (holder)
src << "Admins get a free pass. However, <b>please</b> update your BYOND as soon as possible. Certain things may cause crashes if you play with your present version."
else
del(src)
return 0
if(holder)
add_admin_verbs()
@@ -340,6 +357,8 @@
// Check code/modules/admin/verbs/antag-ooc.dm for definition
add_aooc_if_necessary()
check_ip_intel()
//////////////
//DISCONNECT//
//////////////
@@ -372,26 +391,38 @@
return -1
/client/proc/log_client_to_db()
if ( IsGuestKey(src.key) )
if (IsGuestKey(src.key))
return
establish_db_connection(dbcon)
if(!dbcon.IsConnected())
if(!establish_db_connection(dbcon))
return
var/sql_ckey = sql_sanitize_text(src.ckey)
var/DBQuery/query = dbcon.NewQuery("SELECT id, datediff(Now(),firstseen) as age, whitelist_status, migration_status FROM ss13_player WHERE ckey = '[sql_ckey]'")
query.Execute()
var/sql_id = 0
var/DBQuery/query = dbcon.NewQuery("SELECT datediff(Now(),firstseen) as age, whitelist_status, account_join_date, DATEDIFF(NOW(), account_join_date) FROM ss13_player WHERE ckey = '[sql_ckey]'")
if(!query.Execute())
return
var/found = 0
player_age = 0 // New players won't have an entry so knowing we have a connection we set this to zero to be updated if their is a record.
while(query.NextRow())
sql_id = query.item[1]
player_age = text2num(query.item[2])
whitelist_status = text2num(query.item[3])
need_saves_migrated = text2num(query.item[4])
break
if (query.NextRow())
found = 1
player_age = text2num(query.item[1])
whitelist_status = text2num(query.item[2])
account_join_date = query.item[3]
account_age = text2num(query.item[4])
if (!account_age)
account_join_date = sanitizeSQL(findJoinDate())
if (!account_join_date)
account_age = -1
else
var/DBQuery/query_datediff = dbcon.NewQuery("SELECT DATEDIFF(NOW(), [account_join_date])")
if (!query_datediff.Execute())
return
if (query_datediff.NextRow())
account_age = text2num(query_datediff.item[1])
var/DBQuery/query_ip = dbcon.NewQuery("SELECT ckey FROM ss13_player WHERE ip = '[address]'")
query_ip.Execute()
@@ -407,13 +438,6 @@
related_accounts_cid += "[query_cid.item[1]], "
break
//Just the standard check to see if it's actually a number
if(sql_id)
if(istext(sql_id))
sql_id = text2num(sql_id)
if(!isnum(sql_id))
return
var/admin_rank = "Player"
if(src.holder)
admin_rank = src.holder.rank
@@ -422,16 +446,18 @@
var/sql_computerid = sql_sanitize_text(src.computer_id)
var/sql_admin_rank = sql_sanitize_text(admin_rank)
if(sql_id)
if(found)
//Player already identified previously, we need to just update the 'lastseen', 'ip' and 'computer_id' variables
var/DBQuery/query_update = dbcon.NewQuery("UPDATE ss13_player SET lastseen = Now(), ip = '[sql_ip]', computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]' WHERE id = [sql_id]")
var/DBQuery/query_update = dbcon.NewQuery("UPDATE ss13_player SET lastseen = Now(), ip = '[sql_ip]', computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]', account_join_date = [account_join_date ? "'[account_join_date]'" : "NULL"] WHERE ckey = '[sql_ckey]'")
query_update.Execute()
else
//New player!! Need to insert all the stuff
var/DBQuery/query_insert = dbcon.NewQuery("INSERT INTO ss13_player (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, '[sql_ckey]', Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]')")
var/DBQuery/query_insert = dbcon.NewQuery("INSERT INTO ss13_player (ckey, firstseen, lastseen, ip, computerid, lastadminrank, account_join_date) VALUES ('[sql_ckey]', Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]', [account_join_date ? "'[account_join_date]'" : "NULL"])")
query_insert.Execute()
if (!account_join_date)
account_join_date = "Error"
//Logging player access
var/serverip = "[world.internet_address]:[world.port]"
var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `ss13_connection_log`(`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]','[sql_ckey]','[sql_ip]','[sql_computerid]');")
@@ -585,6 +611,32 @@
server_greeting.display_to_client(src)
/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 (config.ipintel_rating_kick && res.intel >= config.ipintel_rating_kick)
message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a proxy/VPN. They are being kicked because of this.")
log_admin("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a proxy/VPN. They are being kicked because of this.")
to_chat(src, "<span class='danger'>Usage of proxies is not permitted by the rules. You are being kicked because of this.</span>")
del(src)
else 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/findJoinDate()
var/list/http = world.Export("http://byond.com/members/[ckey]?format=text")
if(!http)
log_debug("ACCESS CONTROL: Failed to connect to byond age check for [ckey]")
return
var/F = file2text(http["CONTENT"])
if(F)
var/regex/R = regex("joined = \"(\\d{4}-\\d{2}-\\d{2})\"")
if(R.Find(F))
. = R.group[1]
else
CRASH("Age check regex failed for [src.ckey]")
// Byond seemingly calls stat, each tick.
// Calling things each tick can get expensive real quick.
// So we slow this down a little.