Port TG's stickyban panel (#6806)

This commit is contained in:
Erki
2019-08-01 10:37:23 +03:00
committed by GitHub
parent c0908a708f
commit 9535f738ad
11 changed files with 932 additions and 37 deletions
+36
View File
@@ -0,0 +1,36 @@
--
-- Tables for holding stickyban/PRISM stuff.
--
CREATE TABLE `ss13_stickyban` (
`ckey` VARCHAR(32) NOT NULL,
`reason` VARCHAR(2048) NOT NULL,
`banning_admin` VARCHAR(32) NOT NULL,
`datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`ckey`)
) ENGINE=InnoDB;
CREATE TABLE `ss13_stickyban_matched_ckey` (
`stickyban` VARCHAR(32) NOT NULL,
`matched_ckey` VARCHAR(32) NOT NULL,
`first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`exempt` TINYINT(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`stickyban`, `matched_ckey`)
) ENGINE=InnoDB;
CREATE TABLE `ss13_stickyban_matched_ip` (
`stickyban` VARCHAR(32) NOT NULL,
`matched_ip` INT UNSIGNED NOT NULL,
`first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`stickyban`, `matched_ip`)
) ENGINE=InnoDB;
CREATE TABLE `ss13_stickyban_matched_cid` (
`stickyban` VARCHAR(32) NOT NULL,
`matched_cid` VARCHAR(32) NOT NULL,
`first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`stickyban`, `matched_cid`)
) ENGINE=InnoDB;
+2
View File
@@ -192,6 +192,7 @@
#include "code\controllers\subsystems\records.dm"
#include "code\controllers\subsystems\responseteam.dm"
#include "code\controllers\subsystems\statistics.dm"
#include "code\controllers\subsystems\stickyban.dm"
#include "code\controllers\subsystems\sun.dm"
#include "code\controllers\subsystems\sunlight.dm"
#include "code\controllers\subsystems\ticker.dm"
@@ -1072,6 +1073,7 @@
#include "code\modules\admin\player_notes.dm"
#include "code\modules\admin\player_notes_sql.dm"
#include "code\modules\admin\player_panel.dm"
#include "code\modules\admin\stickyban.dm"
#include "code\modules\admin\ticket.dm"
#include "code\modules\admin\topic.dm"
#include "code\modules\admin\callproc\callproc.dm"
+3
View File
@@ -51,3 +51,6 @@
#define NOT_ADMINHELPED 0
#define ADMINHELPED 1
#define ADMINHELPED_DISCORD 2
#define STICKYBAN_DB_CACHE_TIME (10 SECONDS)
#define STICKYBAN_ROGUE_CHECK_TIME 5
+197
View File
@@ -0,0 +1,197 @@
/var/datum/controller/subsystem/stickyban/SSstickyban
/datum/controller/subsystem/stickyban
name = "PRISM"
init_order = SS_INIT_MISC_FIRST
flags = SS_NO_FIRE
var/list/cache = list()
var/list/dbcache = list()
var/list/confirmed_exempt = list()
var/dbcacheexpire = 0
/datum/controller/subsystem/stickyban/New()
NEW_SS_GLOBAL(SSstickyban)
/datum/controller/subsystem/stickyban/Initialize(timeofday)
var/list/bannedkeys = sticky_banned_ckeys()
//sanitize the sticky ban list
//delete db bans that no longer exist in the database and add new legacy bans to the database
if (!establish_db_connection(dbcon))
for (var/oldban in (world.GetConfig("ban") - bannedkeys))
var/ckey = ckey(oldban)
if (ckey != oldban && (ckey in bannedkeys))
continue
var/list/ban = params2list(world.GetConfig("ban", oldban))
if (ban && !ban["fromdb"])
if (!import_raw_stickyban_to_db(ckey, ban))
log_debug("PRISM: Could not import stickyban on [oldban] into the database. Ignoring.")
continue
dbcacheexpire = 0
bannedkeys += ckey
world.SetConfig("ban", oldban, null)
for (var/bannedkey in bannedkeys)
var/ckey = ckey(bannedkey)
var/list/ban = get_stickyban_from_ckey(bannedkey)
//byond stores sticky bans by key, that's lame
if (ckey != bannedkey)
world.SetConfig("ban", bannedkey, null)
if (!ban["ckey"])
ban["ckey"] = ckey
ban["matches_this_round"] = list()
ban["existing_user_matches_this_round"] = list()
ban["admin_matches_this_round"] = list()
ban["pending_matches_this_round"] = list()
cache[ckey] = ban
world.SetConfig("ban", ckey, list2stickyban(ban))
return ..()
/datum/controller/subsystem/stickyban/proc/Populatedbcache()
var/newdbcache = list() //so if we runtime or the db connection dies we don't kill the existing cache
var/DBQuery/query_stickybans = dbcon.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM ss13_stickyban ORDER BY ckey")
var/DBQuery/query_ckey_matches = dbcon.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM ss13_stickyban_matched_ckey ORDER BY first_matched")
var/DBQuery/query_cid_matches = dbcon.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM ss13_stickyban_matched_cid ORDER BY first_matched")
var/DBQuery/query_ip_matches = dbcon.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM ss13_stickyban_matched_ip ORDER BY first_matched")
if (!query_stickybans.Execute())
return
while (query_stickybans.NextRow())
var/list/ban = list()
ban["ckey"] = query_stickybans.item[1]
ban["message"] = query_stickybans.item[2]
ban["reason"] = "(InGameBan)([query_stickybans.item[3]])"
ban["admin"] = query_stickybans.item[3]
ban["datetime"] = query_stickybans.item[4]
ban["type"] = list("sticky")
newdbcache["[query_stickybans.item[1]]"] = ban
if (query_ckey_matches.Execute())
while (query_ckey_matches.NextRow())
var/list/match = list()
match["stickyban"] = query_ckey_matches.item[1]
match["matched_ckey"] = query_ckey_matches.item[2]
match["first_matched"] = query_ckey_matches.item[3]
match["last_matched"] = query_ckey_matches.item[4]
match["exempt"] = text2num(query_ckey_matches.item[5])
var/ban = newdbcache[query_ckey_matches.item[1]]
if (!ban)
continue
var/keys = ban[text2num(query_ckey_matches.item[5]) ? "whitelist" : "keys"]
if (!keys)
keys = ban[text2num(query_ckey_matches.item[5]) ? "whitelist" : "keys"] = list()
keys[query_ckey_matches.item[2]] = match
if (query_cid_matches.Execute())
while (query_cid_matches.NextRow())
var/list/match = list()
match["stickyban"] = query_cid_matches.item[1]
match["matched_cid"] = query_cid_matches.item[2]
match["first_matched"] = query_cid_matches.item[3]
match["last_matched"] = query_cid_matches.item[4]
var/ban = newdbcache[query_cid_matches.item[1]]
if (!ban)
continue
var/computer_ids = ban["computer_id"]
if (!computer_ids)
computer_ids = ban["computer_id"] = list()
computer_ids[query_cid_matches.item[2]] = match
if (query_ip_matches.Execute())
while (query_ip_matches.NextRow())
var/list/match = list()
match["stickyban"] = query_ip_matches.item[1]
match["matched_ip"] = query_ip_matches.item[2]
match["first_matched"] = query_ip_matches.item[3]
match["last_matched"] = query_ip_matches.item[4]
var/ban = newdbcache[query_ip_matches.item[1]]
if (!ban)
continue
var/IPs = ban["IP"]
if (!IPs)
IPs = ban["IP"] = list()
IPs[query_ip_matches.item[2]] = match
dbcache = newdbcache
dbcacheexpire = world.time+STICKYBAN_DB_CACHE_TIME
/datum/controller/subsystem/stickyban/proc/import_raw_stickyban_to_db(ckey, list/ban)
. = FALSE
if (!ban["admin"])
ban["admin"] = "LEGACY"
if (!ban["message"])
ban["message"] = "Evasion"
var/DBQuery/query_create_stickyban = dbcon.NewQuery("INSERT IGNORE INTO ss13_stickyban (ckey, reason, banning_admin) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(ban["message"])]', '[sanitizeSQL(ban["admin"])]')")
if (!query_create_stickyban.Execute())
return
var/list/sqlckeys = list()
var/list/sqlcids = list()
var/list/sqlips = list()
if (ban["keys"])
var/list/keys = splittext(ban["keys"], ",")
for (var/key in keys)
var/list/sqlckey = list()
sqlckey["stickyban"] = "'[sanitizeSQL(ckey)]'"
sqlckey["matched_ckey"] = "'[sanitizeSQL(ckey(key))]'"
sqlckey["exempt"] = FALSE
sqlckeys[++sqlckeys.len] = sqlckey
if (ban["whitelist"])
var/list/keys = splittext(ban["whitelist"], ",")
for (var/key in keys)
var/list/sqlckey = list()
sqlckey["stickyban"] = "'[sanitizeSQL(ckey)]'"
sqlckey["matched_ckey"] = "'[sanitizeSQL(ckey(key))]'"
sqlckey["exempt"] = TRUE
sqlckeys[++sqlckeys.len] = sqlckey
if (ban["computer_id"])
var/list/cids = splittext(ban["computer_id"], ",")
for (var/cid in cids)
var/list/sqlcid = list()
sqlcid["stickyban"] = "'[sanitizeSQL(ckey)]'"
sqlcid["matched_cid"] = "'[sanitizeSQL(cid)]'"
sqlcids[++sqlcids.len] = sqlcid
if (ban["IP"])
var/list/ips = splittext(ban["IP"], ",")
for (var/ip in ips)
var/list/sqlip = list()
sqlip["stickyban"] = "'[sanitizeSQL(ckey)]'"
sqlip["matched_ip"] = "'[sanitizeSQL(ip)]'"
sqlips[++sqlips.len] = sqlip
if (length(sqlckeys))
dbcon.MassInsert("ss13_stickyban_matched_ckey", sqlckeys, FALSE, TRUE)
if (length(sqlcids))
dbcon.MassInsert("ss13_stickyban_matched_cid", sqlcids, FALSE, TRUE)
if (length(sqlips))
dbcon.MassInsert("ss13_stickyban_matched_ip", sqlips, FALSE, TRUE)
return TRUE
+90 -26
View File
@@ -31,7 +31,7 @@
#define BLOB 14
// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew
DBConnection
/DBConnection
var/_db_con // This variable contains a reference to the actual database connection.
var/con_dbi // This variable is a string containing the DBI MySQL requires.
var/con_user // This variable contains the username data.
@@ -42,7 +42,7 @@ DBConnection
var/con_database = ""
var/failed_connections = 0
DBConnection/New(server, port = 3306, database, username, password_handler, cursor_handler = Default_Cursor, dbi_handler)
/DBConnection/New(server, port = 3306, database, username, password_handler, cursor_handler = Default_Cursor, dbi_handler)
con_user = username
con_password = password_handler
con_cursor = cursor_handler
@@ -57,7 +57,7 @@ DBConnection/New(server, port = 3306, database, username, password_handler, curs
_db_con = _dm_db_new_con()
DBConnection/proc/Connect(dbi_handler = con_dbi, user_handler = con_user, password_handler = con_password, cursor_handler)
/DBConnection/proc/Connect(dbi_handler = con_dbi, user_handler = con_user, password_handler = con_password, cursor_handler)
if (!config.sql_enabled)
return 0
if (!src)
@@ -67,35 +67,99 @@ DBConnection/proc/Connect(dbi_handler = con_dbi, user_handler = con_user, passwo
cursor_handler = Default_Cursor
return _dm_db_connect(_db_con, dbi_handler, user_handler, password_handler, cursor_handler, null)
DBConnection/proc/Disconnect()
/DBConnection/proc/Disconnect()
return _dm_db_close(_db_con)
DBConnection/proc/Reconnect()
/DBConnection/proc/Reconnect()
Disconnect()
Connect()
DBConnection/proc/IsConnected()
/DBConnection/proc/IsConnected()
if(!config.sql_enabled)
return 0
var/success = _dm_db_is_connected(_db_con)
return success
DBConnection/proc/Quote(str)
/DBConnection/proc/Quote(str)
return _dm_db_quote(_db_con,str)
DBConnection/proc/ErrorMsg()
/DBConnection/proc/ErrorMsg()
return _dm_db_error_msg(_db_con)
DBConnection/proc/SelectDB(database_name, new_dbi)
/DBConnection/proc/SelectDB(database_name, new_dbi)
if (IsConnected())
Disconnect()
con_database = database_name
return Connect(new_dbi ? new_dbi : "dbi:mysql:[database_name]:[con_server]:[con_port]", con_user, con_password)
DBConnection/proc/NewQuery(sql_query, cursor_handler = con_cursor)
/DBConnection/proc/NewQuery(sql_query, cursor_handler = con_cursor)
return new/DBQuery(sql_query, src, cursor_handler)
DBQuery
/*
Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query.
Rows missing columns present in other rows will resolve to SQL NULL
You are expected to do your own escaping of the data, and expected to provide your own quotes for strings.
The duplicate_key arg can be true to automatically generate this part of the query
or set to a string that is appended to the end of the query
Ignore_errors instructes mysql to continue inserting rows if some of them have errors.
the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables,
It was included because it is still supported in mariadb.
It does not work with duplicate_key and the mysql server ignores it in those cases
*/
/DBConnection/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE)
if (!table || !rows || !istype(rows))
return
var/list/columns = list()
var/list/sorted_rows = list()
for (var/list/row in rows)
var/list/sorted_row = list()
sorted_row.len = columns.len
for (var/column in row)
var/idx = columns[column]
if (!idx)
idx = columns.len + 1
columns[column] = idx
sorted_row.len = columns.len
sorted_row[idx] = row[column]
sorted_rows[++sorted_rows.len] = sorted_row
if (duplicate_key == TRUE)
var/list/column_list = list()
for (var/column in columns)
column_list += "[column] = VALUES([column])"
duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n"
else if (duplicate_key == FALSE)
duplicate_key = null
if (ignore_errors)
ignore_errors = " IGNORE"
else
ignore_errors = null
if (delayed)
delayed = " DELAYED"
else
delayed = null
var/list/sqlrowlist = list()
var/len = columns.len
for (var/list/row in sorted_rows)
if (length(row) != len)
row.len = len
for (var/value in row)
if (value == null)
value = "NULL"
sqlrowlist += "([row.Join(", ")])"
sqlrowlist = " [sqlrowlist.Join(",\n ")]"
var/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]")
return Query.Execute()
/DBQuery
var/sql // The sql query being executed.
var/default_cursor
var/list/columns //list of DB Columns populated by Columns()
@@ -105,7 +169,7 @@ DBQuery
var/DBConnection/db_connection
var/_db_query
DBQuery/New(var/sql_query, var/DBConnection/connection_handler, var/cursor_handler)
/DBQuery/New(var/sql_query, var/DBConnection/connection_handler, var/cursor_handler)
if (sql_query)
sql = sql_query
if (connection_handler)
@@ -115,10 +179,10 @@ DBQuery/New(var/sql_query, var/DBConnection/connection_handler, var/cursor_handl
_db_query = _dm_db_new_query()
return ..()
DBQuery/proc/Connect(DBConnection/connection_handler)
/DBQuery/proc/Connect(DBConnection/connection_handler)
db_connection = connection_handler
DBQuery/proc/Execute(var/list/argument_list = null, var/pass_not_found = 0, sql_query = sql, cursor_handler = default_cursor)
/DBQuery/proc/Execute(var/list/argument_list = null, var/pass_not_found = 0, sql_query = sql, cursor_handler = default_cursor)
Close()
if (argument_list)
@@ -137,24 +201,24 @@ DBQuery/proc/Execute(var/list/argument_list = null, var/pass_not_found = 0, sql_
return result
DBQuery/proc/NextRow()
/DBQuery/proc/NextRow()
return _dm_db_next_row(_db_query,item,conversions)
DBQuery/proc/RowsAffected()
/DBQuery/proc/RowsAffected()
return _dm_db_rows_affected(_db_query)
DBQuery/proc/RowCount()
/DBQuery/proc/RowCount()
return _dm_db_row_count(_db_query)
DBQuery/proc/ErrorMsg()
/DBQuery/proc/ErrorMsg()
return _dm_db_error_msg(_db_query)
DBQuery/proc/Columns()
/DBQuery/proc/Columns()
if (!columns)
columns = _dm_db_columns(_db_query,/DBColumn)
return columns
DBQuery/proc/GetRowData()
/DBQuery/proc/GetRowData()
var/list/columns = Columns()
var/list/results
if (columns.len)
@@ -165,16 +229,16 @@ DBQuery/proc/GetRowData()
results[C] = item[(cur_col.position+1)]
return results
DBQuery/proc/Close()
/DBQuery/proc/Close()
item.len = 0
columns = null
conversions = null
return _dm_db_close(_db_query)
DBQuery/proc/Quote(str)
/DBQuery/proc/Quote(str)
return db_connection.Quote(str)
DBQuery/proc/SetConversion(column,conversion)
/DBQuery/proc/SetConversion(column,conversion)
if (istext(column))
column = columns.Find(column)
if (!conversions)
@@ -293,7 +357,7 @@ DBQuery/proc/SetConversion(column,conversion)
return "([text])"
DBColumn
/DBColumn
var/name
var/table
var/position //1-based index into item data
@@ -302,7 +366,7 @@ DBColumn
var/length
var/max_length
DBColumn/New(name_handler, table_handler, position_handler, type_handler, flag_handler, length_handler, max_length_handler)
/DBColumn/New(name_handler, table_handler, position_handler, type_handler, flag_handler, length_handler, max_length_handler)
name = name_handler
table = table_handler
position = position_handler
@@ -313,7 +377,7 @@ DBColumn/New(name_handler, table_handler, position_handler, type_handler, flag_h
return ..()
DBColumn/proc/SqlTypeName(type_handler = sql_type)
/DBColumn/proc/SqlTypeName(type_handler = sql_type)
switch (type_handler)
if (TINYINT)
return "TINYINT"
+1 -1
View File
@@ -315,7 +315,7 @@
if (dset[3] == C.computer_id)
new_info &= ~BAD_CID
var/list/bdata = world.IsBanned(dset[1], dset[2], dset[3], 1)
var/list/bdata = world.IsBanned(dset[1], dset[2], dset[3], 1, real_bans_only = TRUE)
if (bdata && bdata.len && !isnull(bdata["id"]))
ding_bannu = bdata["id"]
break
+132 -8
View File
@@ -1,9 +1,20 @@
#ifndef OVERRIDE_BAN_SYSTEM
#define STICKYBAN_MAX_MATCHES 15
#define STICKYBAN_MAX_EXISTING_USER_MATCHES 3 //ie, users who were connected before the ban triggered
#define STICKYBAN_MAX_ADMIN_MATCHES 1
//Blocks an attempt to connect before even creating our client datum thing.
world/IsBanned(key,address,computer_id)
world/IsBanned(key, address, computer_id, type, real_bans_only = FALSE)
if(ckey(key) in admin_datums)
return ..()
var/ckey = ckey(key)
var/admin = (ckey in admin_datums)
//IsBanned can get re-called on a user in certain situations, this prevents that leading to repeated messages to admins.
var/static/list/checkedckeys = list()
//magic voodo to check for a key in a list while also adding that key to the list without having to do two associated lookups
var/message = !checkedckeys[ckey]++
//Guest Checking
if(!(config.guests_allowed || config.external_auth) && IsGuestKey(key))
log_access("Failed Login: [key] - Guests not allowed",ckey=key_name(key))
@@ -12,7 +23,7 @@ world/IsBanned(key,address,computer_id)
if(config.ban_legacy_system)
//Ban Checking
. = CheckBan( ckey(key), computer_id, address )
. = CheckBan(ckey, computer_id, address)
if(.)
log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]",ckey=key_name(key))
message_admins("<span class='notice'>Failed Login: [key] id:[computer_id] ip:[address] - Banned [.["reason"]]</span>")
@@ -32,12 +43,11 @@ world/IsBanned(key,address,computer_id)
message_admins("[key] tried to connect without a computer ID.")
return list("reason" = "Temporary ban", "desc" = "Your connection did not broadcast an computer ID to check.")
var/ckey = ckey(key)
if(!establish_db_connection(dbcon))
error("Ban database connection failure. Key [ckey] not checked")
log_misc("Ban database connection failure. Key [ckey] not checked")
return
return ..()
var/pulled_ban_id = get_active_mirror(ckey, address, computer_id)
@@ -80,6 +90,120 @@ world/IsBanned(key,address,computer_id)
return list("reason"="[bantype]", "desc"="[desc]", "id" = ban_id)
return ..() //default pager ban stuff
#endif
#undef OVERRIDE_BAN_SYSTEM
var/list/ban = ..() //default pager ban stuff
if (ban) // stickyban management stuff.
. = ban
var/client/C = directory[ckey]
if (real_bans_only)
return
var/bannedckey = "ERROR"
if (ban["ckey"])
bannedckey = ban["ckey"]
var/newmatch = FALSE
var/list/cachedban = SSstickyban.cache[bannedckey]
//rogue ban in the process of being reverted.
if (cachedban && (cachedban["reverting"] || cachedban["timeout"]))
world.SetConfig("ban", bannedckey, null)
return null
if (cachedban && ckey != bannedckey)
newmatch = TRUE
if (cachedban["keys"])
if (cachedban["keys"][ckey])
newmatch = FALSE
if (cachedban["matches_this_round"][ckey])
newmatch = FALSE
if (newmatch && cachedban)
var/list/newmatches = cachedban["matches_this_round"]
var/list/pendingmatches = cachedban["matches_this_round"]
var/list/newmatches_connected = cachedban["existing_user_matches_this_round"]
var/list/newmatches_admin = cachedban["admin_matches_this_round"]
if (C)
newmatches_connected[ckey] = ckey
newmatches_connected = cachedban["existing_user_matches_this_round"]
pendingmatches[ckey] = ckey
sleep(STICKYBAN_ROGUE_CHECK_TIME)
pendingmatches -= ckey
if (admin)
newmatches_admin[ckey] = ckey
if (cachedban["reverting"] || cachedban["timeout"])
return null
newmatches[ckey] = ckey
if (\
newmatches.len+pendingmatches.len > STICKYBAN_MAX_MATCHES || \
newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \
newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \
)
var/action
if (ban["fromdb"])
cachedban["timeout"] = TRUE
action = "putting it on timeout for the remainder of the round"
else
cachedban["reverting"] = TRUE
action = "reverting to its roundstart state"
world.SetConfig("ban", bannedckey, null)
//we always report this
log_game("Stickyban on [bannedckey] detected as rogue, [action]")
message_admins("Stickyban on [bannedckey] detected as rogue, [action]")
//do not convert to timer.
spawn (5)
world.SetConfig("ban", bannedckey, null)
sleep(1)
world.SetConfig("ban", bannedckey, null)
if (!ban["fromdb"])
cachedban = cachedban.Copy() //so old references to the list still see the ban as reverting
cachedban["matches_this_round"] = list()
cachedban["existing_user_matches_this_round"] = list()
cachedban["admin_matches_this_round"] = list()
cachedban -= "reverting"
SSstickyban.cache[bannedckey] = cachedban
world.SetConfig("ban", bannedckey, list2stickyban(cachedban))
return null
if (ban["fromdb"])
if(establish_db_connection(dbcon))
spawn()
var/DBQuery/query = dbcon.NewQuery("INSERT INTO ss13_stickyban_matched_ckey (matched_ckey, stickyban) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(bannedckey)]') ON DUPLICATE KEY UPDATE last_matched = now()")
query.Execute()
query = dbcon.NewQuery("INSERT INTO ss13_stickyban_matched_ip (matched_ip, stickyban) VALUES ( INET_ATON('[sanitizeSQL(address)]'), '[sanitizeSQL(bannedckey)]') ON DUPLICATE KEY UPDATE last_matched = now()")
query.Execute()
query = dbcon.NewQuery("INSERT INTO ss13_stickyban_matched_cid (matched_cid, stickyban) VALUES ('[sanitizeSQL(computer_id)]', '[sanitizeSQL(bannedckey)]') ON DUPLICATE KEY UPDATE last_matched = now()")
query.Execute()
//byond will not trigger isbanned() for "global" host bans,
//ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked)
//So it's safe to let admins walk thru host/sticky bans here
if (admin)
log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]")
if (message)
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]</span>")
return null
if (C) //user is already connected!.
to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was automatically reversed.")
var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n"
. = list("reason" = "Stickyban", "desc" = desc)
log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]")
return .
#undef STICKYBAN_MAX_MATCHES
#undef STICKYBAN_MAX_EXISTING_USER_MATCHES
#undef STICKYBAN_MAX_ADMIN_MATCHES
+2 -1
View File
@@ -104,7 +104,8 @@ var/list/admin_verbs_admin = list(
var/list/admin_verbs_ban = list(
/client/proc/unban_panel,
/client/proc/jobbans,
/client/proc/warning_panel
/client/proc/warning_panel,
/client/proc/stickybanpanel
)
var/list/admin_verbs_sounds = list(
/client/proc/play_local_sound,
+465
View File
@@ -0,0 +1,465 @@
/datum/admins/proc/stickyban(action,data)
if(!check_rights(R_BAN))
return
switch (action)
if ("show")
stickyban_show()
if ("add")
var/list/ban = list()
var/ckey
ban["admin"] = usr.ckey
ban["type"] = list("sticky")
ban["reason"] = "(InGameBan)([usr.key])" //this will be displayed in dd only
if (data["ckey"])
ckey = ckey(data["ckey"])
else
ckey = input(usr,"Ckey","Ckey","") as text|null
if (!ckey)
return
ckey = ckey(ckey)
ban["ckey"] = ckey
if (get_stickyban_from_ckey(ckey))
to_chat(usr, "<span class='adminnotice'>Error: Can not add a stickyban: User already has a current sticky ban</span>")
return
if (data["reason"])
ban["message"] = data["reason"]
else
var/reason = input(usr,"Reason","Reason","Ban Evasion") as text|null
if (!reason)
return
ban["message"] = "[reason]"
if(establish_db_connection(dbcon))
var/DBQuery/query_create_stickyban = dbcon.NewQuery("INSERT INTO ss13_stickyban (ckey, reason, banning_admin) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(ban["message"])]', '[sanitizeSQL(usr.ckey)]')")
if (query_create_stickyban.Execute())
ban["fromdb"] = TRUE
world.SetConfig("ban",ckey,list2stickyban(ban))
ban = stickyban2list(list2stickyban(ban))
ban["matches_this_round"] = list()
ban["existing_user_matches_this_round"] = list()
ban["admin_matches_this_round"] = list()
ban["pending_matches_this_round"] = list()
SSstickyban.cache[ckey] = ban
log_and_message_admins("has stickybanned [ckey].\nReason: [ban["message"]]")
if ("remove")
if (!data["ckey"])
return
var/ckey = data["ckey"]
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
if (alert("Are you sure you want to remove the sticky ban on [ckey]?","Are you sure","Yes","No") == "No")
return
if (!get_stickyban_from_ckey(ckey))
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
world.SetConfig("ban",ckey, null)
SSstickyban.cache -= ckey
if (establish_db_connection(dbcon))
var/DBQuery/query = dbcon.NewQuery("DELETE FROM ss13_stickyban WHERE ckey = '[sanitizeSQL(ckey)]'")
query.Execute()
query = dbcon.NewQuery("DELETE FROM ss13_stickyban_matched_ckey WHERE stickyban = '[sanitizeSQL(ckey)]'")
query.Execute()
query = dbcon.NewQuery("DELETE FROM ss13_stickyban_matched_cid WHERE stickyban = '[sanitizeSQL(ckey)]'")
query.Execute()
query = dbcon.NewQuery("DELETE FROM ss13_stickyban_matched_ip WHERE stickyban = '[sanitizeSQL(ckey)]'")
query.Execute()
log_and_message_admins("removed [ckey]'s stickyban")
if ("remove_alt")
if (!data["ckey"])
return
var/ckey = data["ckey"]
if (!data["alt"])
return
var/alt = ckey(data["alt"])
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/key = LAZYACCESS(ban["keys"], alt)
if (!key)
to_chat(usr, "<span class='adminnotice'>Error: [alt] is not linked to [ckey]'s sticky ban!</span>")
return
if (alert("Are you sure you want to disassociate [alt] from [ckey]'s sticky ban? \nNote: Nothing stops byond from re-linking them, Use \[E] to exempt them","Are you sure","Yes","No") == "No")
return
//we have to do this again incase something changes
ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
key = LAZYACCESS(ban["keys"], alt)
if (!key)
to_chat(usr, "<span class='adminnotice'>Error: [alt] link to [ckey]'s sticky ban disappeared.</span>")
return
LAZYREMOVE(ban["keys"], alt)
world.SetConfig("ban",ckey,list2stickyban(ban))
SSstickyban.cache[ckey] = ban
if (establish_db_connection(dbcon))
var/DBQuery/query_remove_stickyban_alt = dbcon.NewQuery("DELETE FROM ss13_stickyban_matched_ckey WHERE stickyban = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(alt)]'")
query_remove_stickyban_alt.Execute()
log_and_message_admins("has disassociated [alt] from [ckey]'s sticky ban")
if ("edit")
if (!data["ckey"])
return
var/ckey = data["ckey"]
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/oldreason = ban["message"]
var/reason = input(usr,"Reason","Reason","[ban["message"]]") as text|null
if (!reason || reason == oldreason)
return
//we have to do this again incase something changed while we waited for input
ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
ban["message"] = "[reason]"
world.SetConfig("ban",ckey,list2stickyban(ban))
SSstickyban.cache[ckey] = ban
if (establish_db_connection(dbcon))
var/DBQuery/query_edit_stickyban = dbcon.NewQuery("UPDATE ss13_stickyban SET reason = '[sanitizeSQL(reason)]' WHERE ckey = '[sanitizeSQL(ckey)]'")
query_edit_stickyban.Execute()
log_and_message_admins("has edited [ckey]'s sticky ban reason from [oldreason] to [reason]")
if ("exempt")
if (!data["ckey"])
return
var/ckey = data["ckey"]
if (!data["alt"])
return
var/alt = ckey(data["alt"])
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/key = LAZYACCESS(ban["keys"], alt)
if (!key)
to_chat(usr, "<span class='adminnotice'>Error: [alt] is not linked to [ckey]'s sticky ban!</span>")
return
if (alert("Are you sure you want to exempt [alt] from [ckey]'s sticky ban?","Are you sure","Yes","No") == "No")
return
//we have to do this again incase something changes
ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
key = LAZYACCESS(ban["keys"], alt)
if (!key)
to_chat(usr, "<span class='adminnotice'>Error: [alt]'s link to [ckey]'s sticky ban disappeared.</span>")
return
LAZYREMOVE(ban["keys"], alt)
key["exempt"] = TRUE
LAZYSET(ban["whitelist"], alt, key)
world.SetConfig("ban",ckey,list2stickyban(ban))
SSstickyban.cache[ckey] = ban
if (establish_db_connection(dbcon))
var/DBQuery/query_exempt_stickyban_alt = dbcon.NewQuery("UPDATE ss13_stickyban_matched_ckey SET exempt = 1 WHERE stickyban = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(alt)]'")
query_exempt_stickyban_alt.Execute()
log_and_message_admins("has exempted [alt] from [ckey]'s sticky ban")
if ("unexempt")
if (!data["ckey"])
return
var/ckey = data["ckey"]
if (!data["alt"])
return
var/alt = ckey(data["alt"])
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/key = LAZYACCESS(ban["whitelist"], alt)
if (!key)
to_chat(usr, "<span class='adminnotice'>Error: [alt] is not exempt from [ckey]'s sticky ban!</span>")
return
if (alert("Are you sure you want to unexempt [alt] from [ckey]'s sticky ban?","Are you sure","Yes","No") == "No")
return
//we have to do this again incase something changes
ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
key = LAZYACCESS(ban["whitelist"], alt)
if (!key)
to_chat(usr, "<span class='adminnotice'>Error: [alt]'s exemption from [ckey]'s sticky ban disappeared.</span>")
return
LAZYREMOVE(ban["whitelist"], alt)
key["exempt"] = FALSE
LAZYSET(ban["keys"], alt, key)
world.SetConfig("ban",ckey,list2stickyban(ban))
SSstickyban.cache[ckey] = ban
if (establish_db_connection(dbcon))
var/DBQuery/query_unexempt_stickyban_alt = dbcon.NewQuery("UPDATE ss13_stickyban_matched_ckey SET exempt = 0 WHERE stickyban = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(alt)]'")
query_unexempt_stickyban_alt.Execute()
log_and_message_admins("has unexempted [alt] from [ckey]'s sticky ban")
if ("timeout")
if (!data["ckey"])
return
if (!establish_db_connection(dbcon))
to_chat(usr, "<span class='adminnotice'>No database connection!</span>")
return
var/ckey = data["ckey"]
if (alert("Are you sure you want to put [ckey]'s stickyban on timeout until next round (or removed)?","Are you sure","Yes","No") == "No")
return
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
ban["timeout"] = TRUE
world.SetConfig("ban", ckey, null)
var/cachedban = SSstickyban.cache[ckey]
if (cachedban)
cachedban["timeout"] = TRUE
log_and_message_admins("has put [ckey]'s sticky ban on timeout.")
if ("untimeout")
if (!data["ckey"])
return
if (!establish_db_connection(dbcon))
to_chat(usr, "<span class='adminnotice'>No database connection!</span>")
return
var/ckey = data["ckey"]
if (alert("Are you sure you want to lift the timeout on [ckey]'s stickyban?","Are you sure","Yes","No") == "No")
return
var/ban = get_stickyban_from_ckey(ckey)
var/cachedban = SSstickyban.cache[ckey]
if (cachedban)
cachedban["timeout"] = FALSE
if (!ban)
if (!cachedban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
ban = cachedban
ban["timeout"] = FALSE
world.SetConfig("ban",ckey,list2stickyban(ban))
log_and_message_admins("has taken [ckey]'s sticky ban off of timeout.")
if ("revert")
if (!data["ckey"])
return
var/ckey = data["ckey"]
if (alert("Are you sure you want to revert the sticky ban on [ckey] to its state at round start (or last edit)?","Are you sure","Yes","No") == "No")
return
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/cached_ban = SSstickyban.cache[ckey]
if (!cached_ban)
to_chat(usr, "<span class='adminnotice'>Error: No cached sticky ban for [ckey] found!</span>")
world.SetConfig("ban",ckey,null)
log_and_message_admins("has reverted [ckey]'s sticky ban to its state at round start.")
//revert is mostly used when shit goes rouge, so we have to set it to null
// and wait a byond tick before assigning it to ensure byond clears its shit.
sleep(world.tick_lag)
world.SetConfig("ban",ckey,list2stickyban(cached_ban))
/datum/admins/proc/stickyban_gethtml(ckey)
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
return
var/timeout
if (establish_db_connection(dbcon))
timeout = "<a href='?_src_=holder;stickyban=[(ban["timeout"] ? "untimeout" : "timeout")]&ckey=[ckey]'>\[[(ban["timeout"] ? "untimeout" : "timeout" )]\]</a>"
else
timeout = "<a href='?_src_=holder;stickyban=revert&ckey=[ckey]'>\[revert\]</a>"
. = list({"
<a href='?_src_=holder;stickyban=remove&ckey=[ckey]'>\[-\]</a>
[timeout]
<b>[ckey]</b>
<br />"
[ban["message"]] <b><a href='?_src_=holder;stickyban=edit&ckey=[ckey]'>\[Edit\]</a></b><br />
"})
if (ban["admin"])
. += "[ban["admin"]]<br />"
else
. += "LEGACY<br />"
. += "Caught keys<br />\n<ol>"
for (var/key in ban["keys"])
if (ckey(key) == ckey)
continue
. += "<li><a href='?_src_=holder;stickyban=remove_alt&ckey=[ckey]&alt=[ckey(key)]'>\[-\]</a>[key]<a href='?_src_=holder;stickyban=exempt&ckey=[ckey]&alt=[ckey(key)]'>\[E\]</a></li>"
for (var/key in ban["whitelist"])
if (ckey(key) == ckey)
continue
. += "<li><a href='?_src_=holder;stickyban=remove_alt&ckey=[ckey]&alt=[ckey(key)]'>\[-\]</a>[key]<a href='?_src_=holder;stickyban=unexempt&ckey=[ckey]&alt=[ckey(key)]'>\[UE\]</a></li>"
. += "</ol>\n"
/datum/admins/proc/stickyban_show()
if(!check_rights(R_BAN))
return
var/list/bans = sticky_banned_ckeys()
var/list/banhtml = list()
for(var/key in bans)
var/ckey = ckey(key)
banhtml += "<br /><hr />\n"
banhtml += stickyban_gethtml(ckey)
var/html = {"
<head>
<title>Sticky Bans</title>
</head>
<body>
<h2>All Sticky Bans:</h2> <a href='?_src_=holder;stickyban=add'>\[+\]</a><br>
[banhtml.Join("")]
</body>
"}
usr << browse(html,"window=stickybans;size=700x400")
/proc/sticky_banned_ckeys()
if (establish_db_connection(dbcon) || length(SSstickyban.dbcache))
if (SSstickyban.dbcacheexpire < world.time)
SSstickyban.Populatedbcache()
if (SSstickyban.dbcacheexpire)
return SSstickyban.dbcache.Copy()
return sortList(world.GetConfig("ban"))
/proc/get_stickyban_from_ckey(var/ckey)
. = list()
if (!ckey)
return null
if (establish_db_connection(dbcon) || length(SSstickyban.dbcache))
if (SSstickyban.dbcacheexpire < world.time)
SSstickyban.Populatedbcache()
if (SSstickyban.dbcacheexpire)
. = SSstickyban.dbcache[ckey]
//reset the cache incase its a newer ban (but only if we didn't update the cache recently)
if (!. && SSstickyban.dbcacheexpire != world.time+STICKYBAN_DB_CACHE_TIME)
SSstickyban.dbcacheexpire = 1
SSstickyban.Populatedbcache()
. = SSstickyban.dbcache[ckey]
if (.)
var/list/cachedban = SSstickyban.cache["[ckey]"]
if (cachedban)
.["timeout"] = cachedban["timeout"]
.["fromdb"] = TRUE
return
. = stickyban2list(world.GetConfig("ban", ckey)) || stickyban2list(world.GetConfig("ban", ckey(ckey))) || list()
if (!length(.))
return null
/proc/stickyban2list(ban, strictdb = TRUE)
if (!ban)
return null
. = params2list(ban)
if (.["keys"])
var/keys = splittext(.["keys"], ",")
var/ckeys = list()
for (var/key in keys)
var/ckey = ckey(key)
ckeys[ckey] = ckey //to make searching faster.
.["keys"] = ckeys
if (.["whitelist"])
var/keys = splittext(.["whitelist"], ",")
var/ckeys = list()
for (var/key in keys)
var/ckey = ckey(key)
ckeys[ckey] = ckey //to make searching faster.
.["whitelist"] = ckeys
.["type"] = splittext(.["type"], ",")
.["IP"] = splittext(.["IP"], ",")
.["computer_id"] = splittext(.["computer_id"], ",")
. -= "fromdb"
/proc/list2stickyban(list/ban)
if (!ban || !islist(ban))
return null
. = ban.Copy()
if (.["keys"])
.["keys"] = jointext(.["keys"], ",")
if (.["IP"])
.["IP"] = jointext(.["IP"], ",")
if (.["computer_id"])
.["computer_id"] = jointext(.["computer_id"], ",")
if (.["whitelist"])
.["whitelist"] = jointext(.["whitelist"], ",")
if (.["type"])
.["type"] = jointext(.["type"], ",")
. -= "reverting"
. -= "matches_this_round"
. -= "existing_user_matches_this_round"
. -= "admin_matches_this_round"
. -= "pending_matches_this_round"
. = list2params(.)
/client/proc/stickybanpanel()
set name = "Sticky Ban Panel"
set category = "Admin"
if (!holder)
return
holder.stickyban_show()
+3
View File
@@ -10,6 +10,9 @@
check_antagonists()
return
else if(href_list["stickyban"])
stickyban(href_list["stickyban"],href_list)
if(href_list["dbsearchckey"] || href_list["dbsearchadmin"])
var/adminckey = href_list["dbsearchadmin"]
+1 -1
View File
@@ -30,7 +30,7 @@
client.verbs += typesof(/client/verb) // Let's return regular client verbs
client.authed = TRUE // We declare client as authed now
// Check for bans
var/list/ban_data = world.IsBanned(ckey(newkey), c.address, c.computer_id)
var/list/ban_data = world.IsBanned(ckey(newkey), c.address, c.computer_id, 1, TRUE)
if(ban_data)
to_chat(c, "You are banned for this server.")
to_chat(c, "Reason: [ban_data["reason"]]")