mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 05:43:46 +03:00
507 lines
17 KiB
Lua
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 |