namespace Supercell.Laser.Server.Logic { using Newtonsoft.Json; public static class BanRegistry { private static readonly object SyncRoot = new object(); private static readonly string StoragePath = Path.Combine(AppContext.BaseDirectory, "bans.json"); private static BanRegistryState State = new BanRegistryState(); public static void Init() { lock (SyncRoot) { if (File.Exists(StoragePath)) { State = JsonConvert.DeserializeObject(File.ReadAllText(StoragePath)) ?? new BanRegistryState(); } else { State = new BanRegistryState(); SaveLocked(); } CleanupExpiredLocked(); } } public static string NormalizeIp(string ipAddress) { if (string.IsNullOrWhiteSpace(ipAddress)) { return null; } return ipAddress.Trim(); } public static string NormalizeCountryCode(string countryCode) { if (string.IsNullOrWhiteSpace(countryCode)) { return null; } return countryCode.Trim().ToUpperInvariant(); } public static bool TryGetIpBan(string ipAddress, out BanEntry entry) { string normalizedIpAddress = NormalizeIp(ipAddress); lock (SyncRoot) { CleanupExpiredLocked(); entry = State.IpBans.FirstOrDefault(x => x.Value == normalizedIpAddress); return entry != null; } } public static bool TryGetCountryBan(string countryCode, out BanEntry entry) { string normalizedCountryCode = NormalizeCountryCode(countryCode); lock (SyncRoot) { CleanupExpiredLocked(); entry = State.CountryBans.FirstOrDefault(x => x.Value == normalizedCountryCode); return entry != null; } } public static void BanIp(string ipAddress, DateTime? expiresAtUtc = null, string reason = null) { string normalizedIpAddress = NormalizeIp(ipAddress); if (normalizedIpAddress == null) { return; } lock (SyncRoot) { UpsertLocked(State.IpBans, normalizedIpAddress, expiresAtUtc, reason); SaveLocked(); } } public static void UnbanIp(string ipAddress) { string normalizedIpAddress = NormalizeIp(ipAddress); lock (SyncRoot) { State.IpBans.RemoveAll(x => x.Value == normalizedIpAddress); SaveLocked(); } } public static void BanCountry(string countryCode, DateTime? expiresAtUtc = null, string reason = null) { string normalizedCountryCode = NormalizeCountryCode(countryCode); if (normalizedCountryCode == null) { return; } lock (SyncRoot) { UpsertLocked(State.CountryBans, normalizedCountryCode, expiresAtUtc, reason); SaveLocked(); } } public static void UnbanCountry(string countryCode) { string normalizedCountryCode = NormalizeCountryCode(countryCode); lock (SyncRoot) { State.CountryBans.RemoveAll(x => x.Value == normalizedCountryCode); SaveLocked(); } } private static void UpsertLocked(List entries, string value, DateTime? expiresAtUtc, string reason) { BanEntry entry = entries.FirstOrDefault(x => x.Value == value); if (entry == null) { entry = new BanEntry(); entry.Value = value; entries.Add(entry); } entry.ExpiresAtUtc = expiresAtUtc; entry.Reason = reason; } private static void CleanupExpiredLocked() { bool removedExpiredEntries = false; DateTime now = DateTime.UtcNow; removedExpiredEntries |= State.IpBans.RemoveAll(x => x.ExpiresAtUtc.HasValue && x.ExpiresAtUtc.Value <= now) > 0; removedExpiredEntries |= State.CountryBans.RemoveAll(x => x.ExpiresAtUtc.HasValue && x.ExpiresAtUtc.Value <= now) > 0; if (removedExpiredEntries) { SaveLocked(); } } private static void SaveLocked() { File.WriteAllText(StoragePath, JsonConvert.SerializeObject(State, Formatting.Indented)); } } public class BanRegistryState { [JsonProperty] public List IpBans = new List(); [JsonProperty] public List CountryBans = new List(); } public class BanEntry { [JsonProperty] public string Value; [JsonProperty] public DateTime? ExpiresAtUtc; [JsonProperty] public string Reason; [JsonIgnore] public bool IsPermanent => !ExpiresAtUtc.HasValue; } }