/* TShock, a server mod for Terraria Copyright (C) 2011-2019 Pryaxis & TShock Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Linq; using System.Collections.Generic; using System.Data; using MySql.Data.MySqlClient; namespace TShockAPI.DB { /// /// Class that manages bans. /// public class BanManager { private IDbConnection database; /// /// Event invoked when a ban check occurs /// public static event EventHandler OnBanCheck; /// /// Event invoked when a ban is added /// public static event EventHandler OnBanAdded; /// /// Initializes a new instance of the class. /// /// A valid connection to the TShock database public BanManager(IDbConnection db) { database = db; var table = new SqlTable("PlayerBans", new SqlColumn("Identifier", MySqlDbType.Text) { Primary = true, Unique = true }, new SqlColumn("Reason", MySqlDbType.Text), new SqlColumn("BanningUser", MySqlDbType.Text), new SqlColumn("Date", MySqlDbType.Text), new SqlColumn("Expiration", MySqlDbType.Text) ); var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite ? (IQueryBuilder)new SqliteQueryCreator() : new MysqlQueryCreator()); try { creator.EnsureTableStructure(table); } catch (DllNotFoundException) { System.Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?"); throw new Exception("Could not find a database library (probably Sqlite3.dll)"); } OnBanCheck += CheckBanValid; } /// /// Converts bans from the old ban system to the new. /// public void ConvertBans() { using (var reader = database.QueryReader("SELECT * FROM Bans")) { while (reader.Read()) { var ip = reader.Get("IP"); var uuid = reader.Get("UUID"); var account = reader.Get("AccountName"); var reason = reader.Get("Reason"); var banningUser = reader.Get("BanningUser"); var date = reader.Get("Date"); var expiration = reader.Get("Expiration"); if (!string.IsNullOrWhiteSpace(ip)) { InsertBan($"{Ban.Identifiers.IP}{ip}", reason, banningUser, date, expiration); } if (!string.IsNullOrWhiteSpace(account)) { InsertBan($"{Ban.Identifiers.Account}{account}", reason, banningUser, date, expiration); } if (!string.IsNullOrWhiteSpace(uuid)) { InsertBan($"{Ban.Identifiers.UUID}{uuid}", reason, banningUser, date, expiration); } } } } /// /// Determines whether or not a ban is valid /// /// /// public bool IsValidBan(Ban ban) { BanEventArgs args = new BanEventArgs { Ban = ban }; OnBanCheck?.Invoke(this, args); return args.Valid; } internal void CheckBanValid(object sender, BanEventArgs args) { //We consider a ban to be valid if the start time is before now and the end time is after now args.Valid = args.Ban.BanDateTime < DateTime.UtcNow && args.Ban.ExpirationDateTime > DateTime.UtcNow; } /// /// Adds a new ban for the given identifier. If the addition succeeds, returns a ban object with the ban details. Else returns null /// /// /// /// /// /// /// public Ban InsertBan(string identifier, string reason, string banningUser, DateTime fromDate, DateTime toDate) => InsertBan(identifier, reason, banningUser, fromDate.ToString("s"), toDate.ToString("s")); /// /// Adds a new ban for the given identifier. If the addition succeeds, returns a ban object with the ban details. Else returns null /// /// /// /// /// /// /// public Ban InsertBan(string identifier, string reason, string banningUser, string fromDate, string toDate) { Ban b = new Ban(identifier, reason, banningUser, fromDate, toDate); BanEventArgs args = new BanEventArgs { Ban = b }; OnBanAdded?.Invoke(this, args); if (!args.Valid) { return null; } int rowsModified = database.Query("INSERT OR IGNORE INTO PlayerBans (Identifier, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4);", identifier, reason, banningUser, fromDate, toDate); //Return the ban if the query actually inserted the row. If the given identifier already exists, the INSERT is ignored and 0 rows are modified. return rowsModified != 0 ? b : null; } /// /// Attempts to remove a ban. Returns true if the ban was removed or expired. False if the ban could not be removed or expired /// /// /// If true, deletes the ban from the database. If false, marks the expiration time as now, rendering the ban expired. Defaults to false /// public bool RemoveBan(string identifier, bool fullDelete = false) { int rowsModified; if (fullDelete) { rowsModified = database.Query("DELETE FROM PlayerBans WHERE Identifier=@0", identifier); } else { rowsModified = database.Query("UPDATE PlayerBans SET Expiration=@0 WHERE Identifier=@1", DateTime.UtcNow.ToString("s"), identifier); } return rowsModified > 0; } /// /// Retrieves a ban for a given identifier, or null if no matches are found /// /// /// public Ban GetBanByIdentifier(string identifier) { using (var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE Identifier=@0", identifier)) { if (reader.Read()) { var id = reader.Get("Identifier"); var reason = reader.Get("Reason"); var banningUser = reader.Get("BanningUser"); var date = reader.Get("Date"); var expiration = reader.Get("Expiration"); return new Ban(id, reason, banningUser, date, expiration); } } return null; } /// /// Retrieves an enumerable of bans for a given set of identifiers /// /// /// public IEnumerable GetBansByIdentifiers(params string[] identifiers) { //Generate a sequence of '@0, @1, @2, ... etc' var parameters = string.Join(", ", Enumerable.Range(0, identifiers.Count()).Select(p => $"@{p}")); using (var reader = database.QueryReader($"SELECT * FROM PlayerBans WHERE Identifier IN ({parameters})", identifiers)) { while (reader.Read()) { var id = reader.Get("Identifier"); var reason = reader.Get("Reason"); var banningUser = reader.Get("BanningUser"); var date = reader.Get("Date"); var expiration = reader.Get("Expiration"); yield return new Ban(id, reason, banningUser, date, expiration); } } } /// /// Gets a list of bans sorted by their addition date from newest to oldest /// public List GetAllBans() => GetAllBansSorted(BanSortMethod.AddedNewestToOldest).ToList(); /// /// Retrieves an enumerable of objects, sorted using the provided sort method /// /// /// public IEnumerable GetAllBansSorted(BanSortMethod sortMethod) { List banlist = new List(); try { var orderBy = SortToOrderByMap[sortMethod]; using (var reader = database.QueryReader($"SELECT * FROM PlayerBans ORDER BY {orderBy}")) { while (reader.Read()) { var identifier = reader.Get("Identifier"); var reason = reader.Get("Reason"); var banningUser = reader.Get("BanningUser"); var date = reader.Get("Date"); var expiration = reader.Get("Expiration"); var ban = new Ban(identifier, reason, banningUser, date, expiration); banlist.Add(ban); } } return banlist; } catch (Exception ex) { TShock.Log.Error(ex.ToString()); Console.WriteLine(ex.StackTrace); } return null; } /// /// Removes all bans from the database /// /// true, if bans were cleared, false otherwise. public bool ClearBans() { try { return database.Query("DELETE FROM PlayerBans") != 0; } catch (Exception ex) { TShock.Log.Error(ex.ToString()); } return false; } internal Dictionary SortToOrderByMap = new Dictionary { { BanSortMethod.AddedNewestToOldest, "Date DESC" }, { BanSortMethod.AddedOldestToNewest, "Date ASC" }, { BanSortMethod.ExpirationSoonestToLatest, "Expiration ASC" }, { BanSortMethod.ExpirationLatestToSoonest, "Expiration DESC" } }; } /// /// Enum containing sort options for ban retrieval /// public enum BanSortMethod { /// /// Bans will be sorted on expiration date, from soonest to latest /// ExpirationSoonestToLatest, /// /// Bans will be sorted on expiration date, from latest to soonest /// ExpirationLatestToSoonest, /// /// Bans will be sorted by the date they were added, from newest to oldest /// AddedNewestToOldest, /// /// Bans will be sorted by the date they were added, from oldest to newest /// AddedOldestToNewest } /// /// Event args used when a ban check is invoked, or a new ban is added /// public class BanEventArgs : EventArgs { /// /// The ban being checked or added /// public Ban Ban { get; set; } /// /// Whether or not the operation is valid /// public bool Valid { get; set; } = true; } /// /// Model class that represents a ban entry in the TShock database. /// public class Ban { /// /// Contains constants for different identifier types known to TShock /// public class Identifiers { /// /// IP identifier prefix constant /// public const string IP = "ip:"; /// /// UUID identifier prefix constant /// public const string UUID = "uuid:"; /// /// Player name identifier prefix constant /// public const string Name = "name:"; /// /// User account identifier prefix constant /// public const string Account = "acc:"; } /// /// An identifiable piece of information to ban /// public string Identifier { get; set; } /// /// Gets or sets the ban reason. /// /// The ban reason. public string Reason { get; set; } /// /// Gets or sets the name of the user who added this ban entry. /// /// The banning user. public string BanningUser { get; set; } /// /// DateTime from which the ban will take effect /// public DateTime BanDateTime { get; } /// /// DateTime at which the ban will end /// public DateTime ExpirationDateTime { get; } /// /// Initializes a new instance of the class. /// /// Identifier to apply the ban to /// Reason for the ban /// Account name that executed the ban /// Ban start datetime /// Ban end datetime public Ban(string identifier, string reason, string banningUser, string start, string end) { Identifier = identifier; Reason = reason; BanningUser = banningUser; if (DateTime.TryParse(start, out DateTime d)) { BanDateTime = d; } else { BanDateTime = DateTime.UtcNow; } if (DateTime.TryParse(end, out DateTime e)) { ExpirationDateTime = e; } else { ExpirationDateTime = DateTime.MaxValue; } } } }