Files
wnsrc/gamemodes/helix/plugins/bastion/modules/sv_antialt.lua
lifestorm 73479cff9e Upload
2024-08-04 22:55:00 +03:00

507 lines
17 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local pcall = pcall
local util = util
local ix = ix
local string = string
local net = net
local ipairs = ipairs
local IsValid = IsValid
local os = os
local L = L
local math = math
local table = table
local pairs = pairs
local print = print
local CHTTP = CHTTP
local player = player
local CAMI = CAMI
if (!CHTTP) then
pcall(require, "chttp")
end
local PLUGIN = PLUGIN
PLUGIN.TYPE_UNKNOWN = 1
PLUGIN.TYPE_KNOWN = 2
local COOKIE_KEY = "qvFT4QSSST8K4tZF"
local fileChecks = {
{"hl2_misc_001.vpk", "hl2"},
{"ep2_pak_008.vpk", "ep2"},
{"cstrike_pak_003.vpk", "css", "cstrike"},
{"bin/client_panorama.dll", "csgo"},
{"detail.vbsp", "gmod", "garrysmod"}
}
util.AddNetworkString("RecieveDupe")
ix.util.Include("sv_antialt_db.lua")
ix.lang.AddTable("english", {
altSignupProxy = "Vous essayez de rejoindre Willard Networks en tant que nouvel utilisateur tout en utilisant un proxy ou un VPN. Pour lutter contre les autres comptes, nous ne l'autorisons pas.\n\nVeuillez désactiver votre proxy/VPN et rejoindre le serveur. Après avoir rejoint le serveur avec succès une fois, vous pouvez réactiver votre proxy/VPN pour de futures visites.\n\nSi vous n'utilisez pas de proxy ou de VPN, veuillez contacter un membre de l'équipe du STAFF sur nos forums ou discord"
})
ix.log.AddType("altNewClient", function(client)
return string.format("[AltChecker] %s rejoint pour la première fois.", client:SteamName())
end)
ix.log.AddType("altKnownNewCookie", function(client)
return string.format("[AltChecker] %s rejoint avec une installation inconnue.", client:SteamName())
end)
ix.log.AddType("altKnownCookieMatched", function(client, matched, total)
return string.format("[AltChecker] %s rejoint sans cookie, mais l'installation correspond au cookie existant. %d/%d", client:SteamName(), matched, total)
end)
hook.Add("PlayerInitialSpawn", "bastionAntiAlt", function(client)
if (client:IsBot()) then return end
client.ixAltData = {
checks = 4 + #fileChecks,
altLogging = 0,
error = false,
received = false,
checkComplete = false,
newAltFound = false,
mode = 0,
localCookie = "",
cookies = false,
ip = false,
timestamps = false,
otherCookies = {},
otherIPs = {},
otherTimestamps = {},
otherTimestampsNZ = {},
otherTimeMatches = {},
discordAlert = {
cookies = {},
timestamps = {},
ips = {},
altIDs = {}
}
}
-- Lookup user data
PLUGIN:AltLoadUserData(client)
if (PLUGIN.API_KEY) then
-- Lookup user IP
PLUGIN:AltLookupIP(client)
else
client.ixAltData.checkes = client.ixAltData.checks - 1
end
-- Check for IP matches
PLUGIN:AltLookupIPMatches(client)
-- Request cookie and install timestamps
PLUGIN:RequestClientData(client)
end)
function PLUGIN:RequestClientData(client)
net.Start("RecieveDupe")
net.WriteUInt(1, 3)
net.WriteString(COOKIE_KEY)
for _, v in ipairs(fileChecks) do
net.WriteString(v[1])
net.WriteString(v[3] or v[2])
end
net.Send(client)
end
net.Receive("RecieveDupe", function(len, client)
if (!IsValid(client) or !client.ixAltData or client.ixAltData.received) then return end
local data = client.ixAltData
data.received = true
-- set cookie
data.localCookie = net.ReadString()
-- set file timestamps
data.timestamps = {}
for i = 1, #fileChecks do
data.timestamps[i] = net.ReadUInt(32)
end
-- Check for cookie matches
if (data.localCookie != "") then
PLUGIN:AltLookupCookieMatches(client, data)
else
PLUGIN:AltPreFinalChecking(client)
end
-- Check for install timestamp matches
data.otherTimestamps = {}
data.otherTimestampsNZ = {}
for i = 1, #fileChecks do
PLUGIN:AltLookupTimestampMatches(client, data, fileChecks[i][2], data.timestamps[i])
end
end)
function PLUGIN:AltPreFinalChecking(client)
if (!IsValid(client)) then return end
local data = client.ixAltData
-- if we errored, don't do anything
-- check will be reexecuted when the client rejoins
if (data.error) then return end
-- check if all queries finished
data.checks = data.checks - 1
if (data.checks != 0) then return end
-- cookie matches (STRONG)
if (#data.otherCookies > 0) then
for _, v in ipairs(data.otherCookies) do
self:AltFound(client, v[2], "cookie", "cookie")
data.discordAlert.cookies[#data.discordAlert.cookies + 1] = (v[2] or "").." ("..(v[1] or "")..")"
data.discordAlert.altIDs[v[2]] = true
end
end
-- IP (WEAK)
if (#data.otherIPs > 0) then
for _, v in ipairs(data.otherIPs) do
data.discordAlert.ips[#data.discordAlert.ips + 1] = v[2]
data.discordAlert.altIDs[v[2]] = true
end
end
-- time matches (STRONG-MEDIUM)
self:AggregateTimestampMatches(data)
-- If no local cookie and player is known, check if a known cookie was time-matched
if (data.localCookie == "" and data.mode == self.TYPE_KNOWN) then
self:FindMatchingCookie(client, data)
end
if (#data.otherTimeMatches > 0) then
-- go looking for the other clients that own our matched timestamps
self:AltLookupCookieForTimestamps(client, data, #fileChecks)
else
-- else we don't need to wait for the lookup above
data.checkComplete = true
self:AltFinalChecking(client)
end
end
function PLUGIN:AltFinalChecking(client)
if (!IsValid(client)) then return end
local data = client.ixAltData
if (!data.checkComplete or data.altLogging != 0) then return end
self:DiscordAlert(client)
-- update IP list
local steamID = client:SteamID64()
local ip = self.GetIPAddress(client)
local query = mysql:Select("bastion_antialt_userips")
query:Where("steamid", steamID)
query:Where("ip", ip)
query:Callback(function(result)
if (!result or #result == 0) then
local query2 = mysql:Insert("bastion_antialt_userips")
query2:Insert("steamid", steamID)
query2:Insert("ip", ip)
query2:Insert("last_seen", os.time())
query2:Execute()
else
local query2 = mysql:Update("bastion_antialt_userips")
query2:Where("id", result[1].id)
query2:Update("last_seen", os.time())
query2:Execute()
end
end)
query:Execute()
-- Kick if new player on proxy/vpn
if (data.mode == self.TYPE_UNKNOWN) then
if (self.API_KEY and (data.ip.proxy == "yes" or (data.ip.risk or 0) > 60)) then
if (ix.config.Get("VPNKick")) then
self:ProxyAlert(client)
client:Kick(L("altSignupProxy", client))
else
if (!self:NotifyProxyJoin(client) or ix.config.Get("ProxyAlwaysAlert")) then
self:ProxyAlert(client)
end
end
elseif (data.localCookie == "") then
ix.log.Add(client, "altNewClient")
self:GenerateCookie(client, data)
else
-- Update the cookie's timestamps
self:StoreCookieInfo(data, fileChecks)
-- Add this cookie to client as well so it can be time matched/timestamps updated
self:StoreClientCookie(client, data)
end
elseif (data.localCookie == "") then
ix.log.Add(client, "altKnownNewCookie")
self:GenerateCookie(client, data)
else
-- Update the cookie's timestamps
self:StoreCookieInfo(data, fileChecks)
-- Add this cookie to client as well so it can be time matched/timestamps updated
self:StoreClientCookie(client, data)
end
if (sam) then
timer.Simple(3, function()
if (!IsValid(client)) then return end
local query1 = mysql:Select("bastion_antialt_alts")
query1:Where("steamid", client:SteamID64())
query1:Select("alt_id")
query1:Callback(function(result)
if (!IsValid(client)) then return end
if (!result or #result == 0) then return end
local query2 = mysql:Select("bastion_antialt_alts")
query2:Where("alt_id", result[1].alt_id)
query2:WhereNotEqual("steamid", client:SteamID64())
query2:Select("steamid")
query2:Callback(function(result2)
if (!IsValid(client)) then return end
if (!result2 or #result2 == 0) then return end
for k, v in ipairs(result2) do
sam.player.is_banned(util.SteamIDFrom64(v.steamid), function(banned)
if (!banned or !IsValid(client)) then return end
client:Kick("You have a banned alt account.")
return
end)
end
end)
query2:Execute()
end)
query1:Execute()
end)
end
end
--[[
COOKIE STUFF
]]
function PLUGIN:WhitelistPlayer(client)
local data = client.ixAltData
if (!data) then return end
if (data.localCookie) then
ix.log.Add(client, "altNewClient")
self:GenerateCookie(client, data)
return true
end
end
function PLUGIN.RandomString()
local result = {} -- The empty table we start with
while (#result != 64) do
local char = string.char(math.random(32, 126))
if (string.find(char, "%w")) then
result[#result + 1] = char
end
end
return table.concat(result)
end
function PLUGIN:GenerateCookie(client, data)
local cookie = self.RandomString()
self:UpdateLocalCookie(client, data, cookie)
local query = mysql:Insert("bastion_antialt_users")
query:Insert("steamid", client:SteamID64())
query:Insert("steam_name", client:SteamName())
query:Insert("cookie", cookie)
query:Execute()
end
function PLUGIN:UpdateLocalCookie(client, data, cookie)
data.localCookie = cookie
net.Start("RecieveDupe")
net.WriteUInt(2, 3)
net.WriteString(COOKIE_KEY)
net.WriteString(cookie)
net.Send(client)
self:StoreCookieInfo(data, fileChecks)
end
function PLUGIN:FindMatchingCookie(client, data)
for _, v in ipairs(data.otherTimeMatches) do
for _, v1 in ipairs(data.cookies) do
if (v1.cookie == v[1]) then
-- found a timestamp match belonging to the client, restore cookie
-- in case of e.g. gmod reinstall
ix.log.Add(client, "altKnownCookieMatched", v[2], #fileChecks)
self:UpdateLocalCookie(client, data, v[1])
return
end
end
end
end
--[[
LOGGING AND ALERTING
]]
local ALT_OFFSET = 0
function PLUGIN:AltFound(client, steamid, type, info)
local ids = {client:SteamID64(), steamid}
local data = client.ixAltData
data.altLogging = data.altLogging + 1
local query = mysql:Select("bastion_antialt_alts")
query:WhereIn("steamid", ids)
query:Callback(function(result)
if (!result or #result == 0) then
local alt_id = os.time() + ALT_OFFSET
ALT_OFFSET = ALT_OFFSET + 1
local text = table.concat(ids,"+")..": "..info.." (alt-id: "..alt_id..")"
self:InsertNewAlt(ids[1], alt_id, type, text)
self:InsertNewAlt(steamid, alt_id, type, text)
data.newAltFound = true
elseif (#result == 1) then
self:InsertNewAlt(
result[1].steamid == steamid and ids[1] or steamid,
result[1].alt_id,
type,
table.concat(ids,"+")..": "..info.." (alt-id: "..result[1].alt_id..")"
)
data.newAltFound = true
elseif (result[2].alt_id != result[1].alt_id) then
self:MergeAlts(
result[2].alt_id,
result[1].alt_id,
table.concat(ids,"+")..": "..info.." (alt-id: "..result[1].alt_id..")"
)
data.newAltFound = true
end
data.altLogging = data.altLogging - 1
self:AltFinalChecking(client)
end)
query:Execute()
end
function PLUGIN:DiscordAlert(client)
if (!self.DISCORD_WEBHOOK_ALTS or self.DISCORD_WEBHOOK_ALTS == "") then return end
local data = client.ixAltData
if (!data.newAltFound) then return end
local tbl = {
embeds = {{
title = "Alt found for "..client:SteamName(),
description = "An alt account match was found for **"..client:SteamName().."** (*"..client:SteamID64().."*)\n\n__COOKIE__: 99.99% certainty via installed cookie\nShown as 'SteamID64 (SteamName)'\n__TIMESTAMP__: check via installation date/time or absense of mounted games (hl2,ep2,css,csgo,gmod)\nMore matches = more certainty, especially if all/most are installed\nShown as 'SteamID64 (SteamName; date/time matches - installed matches)'\n__IP__: users that connected from the same IP address at any point\nShown as 'SteamID64'",
color = 13632027,
timestamp = os.date("%Y-%m-%d %X%z"),
footer = {
text = "Bastion Alt Checker for GMod by Gr4Ss"
},
author = {
name = "Bastion Alt Checker"
},
fields = {
}
}}
}
if (data.discordAlert.cookies and #data.discordAlert.cookies > 0) then
table.insert(tbl.embeds[1].fields, {name = "COOKIE", value = table.concat(data.discordAlert.cookies, "\n")})
end
if (data.discordAlert.timestamps and #data.discordAlert.timestamps > 0) then
table.insert(tbl.embeds[1].fields, {name = "TIMESTAMP", value = table.concat(data.discordAlert.timestamps, "\n")})
end
if (data.discordAlert.ips and #data.discordAlert.ips > 0) then
table.insert(tbl.embeds[1].fields, {
name = "IP",
value = string.format("Address: [%s](https://proxycheck.io/threats/%s)\n", data.ipAddress, data.ipAddress)..
table.concat(data.discordAlert.ips, "\n")
})
end
local ipLinks = {"["..client:SteamID64().."](https://steamidfinder.com/lookup/"..client:SteamID64().."/)"}
for k in pairs(data.discordAlert.altIDs) do
ipLinks[#ipLinks + 1] = "["..k.."](https://steamidfinder.com/lookup/"..k.."/)"
end
table.insert(tbl.embeds[1].fields, {
name = "SteamID Finder Links",
value = table.concat(ipLinks,"\n")
})
for _, field in ipairs(tbl.embeds[1].fields) do
if (string.len(field.value) > 1024) then
field.value = string.sub(field.value, 1, 1024)
end
end
local request = {
failed = function(error) print("discord error", error) end,
success = function(code, body, headers)
if (code != 200) then print("discord error", code, body) end
end,
method = "post",
url = self.DISCORD_WEBHOOK_ALTS,
body = util.TableToJSON(tbl),
type = "application/json; charset=utf-8"
}
CHTTP(request)
end
function PLUGIN:ProxyAlert(client)
if (!self.DISCORD_WEBHOOK_ALTS or self.DISCORD_WEBHOOK_ALTS == "") then return end
local ip, steamID = client.ixAltData.ipAddress, client:SteamID64()
local tbl = {
embeds = {{
title = "New player using VPN - "..client:SteamName(),
description = client:SteamName().." joined WN for the first time, but was using a proxy/VPN. They have been kicked.\n\n"..
string.format("More info: [%s](https://proxycheck.io/threats/%s) & [%s](https://steamidfinder.com/lookup/%s/)", ip, ip, steamID, steamID),
color = 16312092,
timestamp = os.date("%Y-%m-%d %X%z"),
footer = {
text = "Bastion Alt Checker for GMod by Gr4Ss"
},
author = {
name = "Bastion Alt Checker"
}
}}
}
local request = {
failed = function(error) print("discord error", error) end,
method = "post",
url = self.DISCORD_WEBHOOK_ALTS,
body = util.TableToJSON(tbl),
type = "application/json; charset=utf-8"
}
CHTTP(request)
end
function PLUGIN:NotifyProxyJoin(client)
local bSend = false
for _, v in ipairs(player.GetAll()) do
if (CAMI.PlayerHasAccess(v, "Helix - Proxy Notify")) then
bSend = true
v:NotifyLocalized("bastionProxyNotify", client:SteamName())
end
end
return bSend
end