Reorganizes Project (#41)

This commit is contained in:
Kamron Batman 2019-08-02 18:13:40 -07:00 committed by GitHub
parent 08bf44af9a
commit 3614a66aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3499 changed files with 79 additions and 55 deletions

View file

@ -0,0 +1,46 @@
using System;
using System.IO;
using System.Net;
using Server.Misc;
namespace Server
{
public class AccessRestrictions
{
public static void Initialize()
{
EventSink.SocketConnect += EventSink_SocketConnect;
}
private static void EventSink_SocketConnect(SocketConnectEventArgs e)
{
try
{
IPAddress ip = ((IPEndPoint)e.Socket.RemoteEndPoint).Address;
if (Firewall.IsBlocked(ip))
{
Console.WriteLine("Client: {0}: Firewall blocked connection attempt.", ip);
e.AllowConnection = false;
return;
}
if (IPLimiter.SocketBlock && !IPLimiter.Verify(ip))
{
Console.WriteLine("Client: {0}: Past IP limit threshold", ip);
using (StreamWriter op = new StreamWriter("ipLimits.log", true))
{
op.WriteLine("{0}\tPast IP limit threshold\t{1}", ip, DateTime.UtcNow);
}
e.AllowConnection = false;
}
}
catch
{
e.AllowConnection = false;
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Server.Network;
namespace Server.Accounting
{
public class AccountAttackLimiter
{
public static bool Enabled = true;
private static List<InvalidAccountAccessLog> m_List = new List<InvalidAccountAccessLog>();
public static void Initialize()
{
if (!Enabled)
return;
PacketHandlers.RegisterThrottler(0x80, Throttle_Callback);
PacketHandlers.RegisterThrottler(0x91, Throttle_Callback);
PacketHandlers.RegisterThrottler(0xCF, Throttle_Callback);
}
public static TimeSpan Throttle_Callback(NetState ns)
{
InvalidAccountAccessLog accessLog = FindAccessLog(ns);
if (accessLog == null)
return TimeSpan.Zero;
DateTime date = DateTime.UtcNow;
DateTime access = accessLog.LastAccessTime + ComputeThrottle(accessLog.Counts);
return date >= access ? TimeSpan.Zero : date - access;
}
public static InvalidAccountAccessLog FindAccessLog(NetState ns)
{
if (ns == null)
return null;
IPAddress ipAddress = ns.Address;
for (int i = 0; i < m_List.Count; ++i)
{
InvalidAccountAccessLog accessLog = m_List[i];
if (accessLog.HasExpired)
m_List.RemoveAt(i--);
else if (accessLog.Address.Equals(ipAddress))
return accessLog;
}
return null;
}
public static void RegisterInvalidAccess(NetState ns)
{
if (ns == null || !Enabled)
return;
InvalidAccountAccessLog accessLog = FindAccessLog(ns);
if (accessLog == null)
m_List.Add(accessLog = new InvalidAccountAccessLog(ns.Address));
accessLog.Counts += 1;
accessLog.RefreshAccessTime();
if (accessLog.Counts >= 3)
try
{
using (StreamWriter op = new StreamWriter("throttle.log", true))
{
op.WriteLine(
"{0}\t{1}\t{2}",
DateTime.UtcNow,
ns,
accessLog.Counts
);
}
}
catch
{
// ignored
}
}
public static TimeSpan ComputeThrottle(int counts)
{
if (counts >= 15)
return TimeSpan.FromMinutes(5.0);
if (counts >= 10)
return TimeSpan.FromMinutes(1.0);
if (counts >= 5)
return TimeSpan.FromSeconds(20.0);
if (counts >= 3)
return TimeSpan.FromSeconds(10.0);
if (counts >= 1)
return TimeSpan.FromSeconds(2.0);
return TimeSpan.Zero;
}
}
public class InvalidAccountAccessLog
{
public InvalidAccountAccessLog(IPAddress address)
{
Address = address;
RefreshAccessTime();
}
public IPAddress Address{ get; set; }
public DateTime LastAccessTime{ get; set; }
public bool HasExpired => DateTime.UtcNow >= LastAccessTime + TimeSpan.FromHours(1.0);
public int Counts{ get; set; }
public void RefreshAccessTime()
{
LastAccessTime = DateTime.UtcNow;
}
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Xml;
namespace Server.Accounting
{
public class AccountComment
{
private string m_Content;
/// <summary>
/// Constructs a new AccountComment instance.
/// </summary>
/// <param name="addedBy">Initial AddedBy value.</param>
/// <param name="content">Initial Content value.</param>
public AccountComment(string addedBy, string content)
{
AddedBy = addedBy;
m_Content = content;
LastModified = DateTime.UtcNow;
}
/// <summary>
/// Deserializes an AccountComment instance from an xml element.
/// </summary>
/// <param name="node">The XmlElement instance from which to deserialize.</param>
public AccountComment(XmlElement node)
{
AddedBy = Utility.GetAttribute(node, "addedBy", "empty");
LastModified = Utility.GetXMLDateTime(Utility.GetAttribute(node, "lastModified"), DateTime.UtcNow);
m_Content = Utility.GetText(node, "");
}
/// <summary>
/// A string representing who added this comment.
/// </summary>
public string AddedBy{ get; }
/// <summary>
/// Gets or sets the body of this comment. Setting this value will reset LastModified.
/// </summary>
public string Content
{
get => m_Content;
set
{
m_Content = value;
LastModified = DateTime.UtcNow;
}
}
/// <summary>
/// The date and time when this account was last modified -or- the comment creation time, if never modified.
/// </summary>
public DateTime LastModified{ get; private set; }
/// <summary>
/// Serializes this AccountComment instance to an XmlTextWriter.
/// </summary>
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
public void Save(XmlTextWriter xml)
{
xml.WriteStartElement("comment");
xml.WriteAttributeString("addedBy", AddedBy);
xml.WriteAttributeString("lastModified", XmlConvert.ToString(LastModified, XmlDateTimeSerializationMode.Utc));
xml.WriteString(m_Content);
xml.WriteEndElement();
}
}
}

View file

@ -0,0 +1,423 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Server.Accounting;
using Server.Commands;
using Server.Engines.Help;
using Server.Network;
using Server.Regions;
namespace Server.Misc
{
public enum PasswordProtection
{
None,
Crypt,
NewCrypt
}
public class AccountHandler
{
private static int MaxAccountsPerIP = 1;
private static bool AutoAccountCreation = true;
private static bool RestrictDeletion = !TestCenter.Enabled;
private static TimeSpan DeleteDelay = TimeSpan.FromDays(7.0);
public static PasswordProtection ProtectPasswords = PasswordProtection.NewCrypt;
private static CityInfo[] StartingCities =
{
new CityInfo("New Haven", "New Haven Bank", 1150168, 3667, 2625, 0),
new CityInfo("Yew", "The Empath Abbey", 1075072, 633, 858, 0),
new CityInfo("Minoc", "The Barnacle", 1075073, 2476, 413, 15),
new CityInfo("Britain", "The Wayfarer's Inn", 1075074, 1602, 1591, 20),
new CityInfo("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0),
new CityInfo("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0),
new CityInfo("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0),
new CityInfo("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0),
new CityInfo("Vesper", "The Ironwood Inn", 1075080, 2771, 976, 0)
};
/* Old Haven/Magincia Locations
new CityInfo( "Britain", "Sweet Dreams Inn", 1496, 1628, 10 );
// ..
// Trinsic
new CityInfo( "Magincia", "The Great Horns Tavern", 3734, 2222, 20 ),
// Jhelom
// ..
new CityInfo( "Haven", "Buckler's Hideaway", 3667, 2625, 0 )
if ( Core.AOS )
{
//CityInfo haven = new CityInfo( "Haven", "Uzeraan's Mansion", 3618, 2591, 0 );
CityInfo haven = new CityInfo( "Haven", "Uzeraan's Mansion", 3503, 2574, 14 );
StartingCities[StartingCities.Length - 1] = haven;
}
*/
private static bool PasswordCommandEnabled = false;
private static Dictionary<IPAddress, int> m_IPTable;
private static readonly char[] m_ForbiddenChars =
{
'<', '>', ':', '"', '/', '\\', '|', '?', '*'
};
public static AccessLevel LockdownLevel{ get; set; }
public static Dictionary<IPAddress, int> IPTable
{
get
{
if (m_IPTable == null)
{
m_IPTable = new Dictionary<IPAddress, int>();
foreach (Account a in Accounts.GetAccounts())
if (a.LoginIPs.Length > 0)
{
IPAddress ip = a.LoginIPs[0];
m_IPTable[ip] = (m_IPTable.TryGetValue(ip, out int value) ? value : 0) + 1;
}
}
return m_IPTable;
}
}
public static void Initialize()
{
EventSink.DeleteRequest += EventSink_DeleteRequest;
EventSink.AccountLogin += EventSink_AccountLogin;
EventSink.GameLogin += EventSink_GameLogin;
if (PasswordCommandEnabled)
CommandSystem.Register("Password", AccessLevel.Player, Password_OnCommand);
}
[Usage("Password <newPassword> <repeatPassword>")]
[Description(
"Changes the password of the commanding players account. Requires the same C-class IP address as the account's creator.")]
public static void Password_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
if (!(from.Account is Account acct))
return;
IPAddress[] accessList = acct.LoginIPs;
if (accessList.Length == 0)
return;
NetState ns = from.NetState;
if (ns == null)
return;
if (e.Length == 0)
{
from.SendMessage("You must specify the new password.");
return;
}
if (e.Length == 1)
{
from.SendMessage("To prevent potential typing mistakes, you must type the password twice. Use the format:");
from.SendMessage("Password \"(newPassword)\" \"(repeated)\"");
return;
}
string pass = e.GetString(0);
string pass2 = e.GetString(1);
if (pass != pass2)
{
from.SendMessage("The passwords do not match.");
return;
}
bool isSafe = true;
for (int i = 0; isSafe && i < pass.Length; ++i)
isSafe = pass[i] >= 0x20 && pass[i] < 0x7F;
if (!isSafe)
{
from.SendMessage("That is not a valid password.");
return;
}
try
{
IPAddress ipAddress = ns.Address;
if (Utility.IPMatchClassC(accessList[0], ipAddress))
{
acct.SetPassword(pass);
from.SendMessage("The password to your account has changed.");
}
else
{
PageEntry entry = PageQueue.GetEntry(from);
if (entry != null)
{
if (entry.Message.StartsWith("[Automated: Change Password]"))
from.SendMessage("You already have a password change request in the help system queue.");
else
from.SendMessage("Your IP address does not match that which created this account.");
}
else if (PageQueue.CheckAllowedToPage(from))
{
from.SendMessage(
"Your IP address does not match that which created this account. A page has been entered into the help system on your behalf.");
from.SendLocalizedMessage(501234, "",
0x35); /* The next available Counselor/Game Master will respond as soon as possible.
* Please check your Journal for messages every few minutes.
*/
PageQueue.Enqueue(new PageEntry(from,
$"[Automated: Change Password]<br>Desired password: {pass}<br>Current IP address: {ipAddress}<br>Account IP address: {accessList[0]}",
PageType.Account));
}
}
}
catch
{
// ignored
}
}
private static void EventSink_DeleteRequest(DeleteRequestEventArgs e)
{
NetState state = e.State;
int index = e.Index;
if (!(state.Account is Account acct))
{
state.Dispose();
}
else if (index < 0 || index >= acct.Length)
{
state.Send(new DeleteResult(DeleteResultType.BadRequest));
state.Send(new CharacterListUpdate(acct));
}
else
{
Mobile m = acct[index];
if (m == null)
{
state.Send(new DeleteResult(DeleteResultType.CharNotExist));
state.Send(new CharacterListUpdate(acct));
}
else if (m.NetState != null)
{
state.Send(new DeleteResult(DeleteResultType.CharBeingPlayed));
state.Send(new CharacterListUpdate(acct));
}
else if (RestrictDeletion && DateTime.UtcNow < m.CreationTime + DeleteDelay)
{
state.Send(new DeleteResult(DeleteResultType.CharTooYoung));
state.Send(new CharacterListUpdate(acct));
}
else if (m.AccessLevel == AccessLevel.Player &&
Region.Find(m.LogoutLocation, m.LogoutMap).IsPartOf<Jail>()
) //Don't need to check current location, if netstate is null, they're logged out
{
state.Send(new DeleteResult(DeleteResultType.BadRequest));
state.Send(new CharacterListUpdate(acct));
}
else
{
Console.WriteLine("Client: {0}: Deleting character {1} (0x{2:X})", state, index, m.Serial.Value);
acct.Comments.Add(new AccountComment("System", $"Character #{index + 1} {m} deleted by {state}"));
m.Delete();
state.Send(new CharacterListUpdate(acct));
}
}
}
public static bool CanCreate(IPAddress ip)
{
if (!IPTable.ContainsKey(ip))
return true;
return IPTable[ip] < MaxAccountsPerIP;
}
private static bool IsForbiddenChar(char c)
{
for (int i = 0; i < m_ForbiddenChars.Length; ++i)
if (c == m_ForbiddenChars[i])
return true;
return false;
}
private static Account CreateAccount(NetState state, string un, string pw)
{
if (un.Length == 0 || pw.Length == 0)
return null;
bool isSafe = !(un.StartsWith(" ") || un.EndsWith(" ") || un.EndsWith("."));
for (int i = 0; isSafe && i < un.Length; ++i)
isSafe = un[i] >= 0x20 && un[i] < 0x7F && !IsForbiddenChar(un[i]);
for (int i = 0; isSafe && i < pw.Length; ++i)
isSafe = pw[i] >= 0x20 && pw[i] < 0x7F;
if (!isSafe)
return null;
if (!CanCreate(state.Address))
{
Console.WriteLine("Login: {0}: Account '{1}' not created, ip already has {2} account{3}.", state, un,
MaxAccountsPerIP, MaxAccountsPerIP == 1 ? "" : "s");
return null;
}
Console.WriteLine("Login: {0}: Creating new account '{1}'", state, un);
Account a = new Account(un, pw);
return a;
}
public static void EventSink_AccountLogin(AccountLoginEventArgs e)
{
if (!IPLimiter.SocketBlock && !IPLimiter.Verify(e.State.Address))
{
e.Accepted = false;
e.RejectReason = ALRReason.InUse;
Console.WriteLine("Login: {0}: Past IP limit threshold", e.State);
using (StreamWriter op = new StreamWriter("ipLimits.log", true))
{
op.WriteLine("{0}\tPast IP limit threshold\t{1}", e.State, DateTime.UtcNow);
}
return;
}
string un = e.Username;
string pw = e.Password;
e.Accepted = false;
if (!(Accounts.GetAccount(un) is Account acct))
{
// To prevent someone from making an account of just '' or a bunch of meaningless spaces
if (AutoAccountCreation && un.Trim().Length > 0)
{
e.State.Account = acct = CreateAccount(e.State, un, pw);
e.Accepted = acct?.CheckAccess(e.State) ?? false;
if (!e.Accepted)
e.RejectReason = ALRReason.BadComm;
}
else
{
Console.WriteLine("Login: {0}: Invalid username '{1}'", e.State, un);
e.RejectReason = ALRReason.Invalid;
}
}
else if (!acct.HasAccess(e.State))
{
Console.WriteLine("Login: {0}: Access denied for '{1}'", e.State, un);
e.RejectReason = LockdownLevel > AccessLevel.Player ? ALRReason.BadComm : ALRReason.BadPass;
}
else if (!acct.CheckPassword(pw))
{
Console.WriteLine("Login: {0}: Invalid password for '{1}'", e.State, un);
e.RejectReason = ALRReason.BadPass;
}
else if (acct.Banned)
{
Console.WriteLine("Login: {0}: Banned account '{1}'", e.State, un);
e.RejectReason = ALRReason.Blocked;
}
else
{
Console.WriteLine("Login: {0}: Valid credentials for '{1}'", e.State, un);
e.State.Account = acct;
e.Accepted = true;
acct.LogAccess(e.State);
}
if (!e.Accepted)
AccountAttackLimiter.RegisterInvalidAccess(e.State);
}
public static void EventSink_GameLogin(GameLoginEventArgs e)
{
if (!IPLimiter.SocketBlock && !IPLimiter.Verify(e.State.Address))
{
e.Accepted = false;
Console.WriteLine("Login: {0}: Past IP limit threshold", e.State);
using (StreamWriter op = new StreamWriter("ipLimits.log", true))
{
op.WriteLine("{0}\tPast IP limit threshold\t{1}", e.State, DateTime.UtcNow);
}
return;
}
string un = e.Username;
string pw = e.Password;
if (!(Accounts.GetAccount(un) is Account acct))
{
e.Accepted = false;
}
else if (!acct.HasAccess(e.State))
{
Console.WriteLine("Login: {0}: Access denied for '{1}'", e.State, un);
e.Accepted = false;
}
else if (!acct.CheckPassword(pw))
{
Console.WriteLine("Login: {0}: Invalid password for '{1}'", e.State, un);
e.Accepted = false;
}
else if (acct.Banned)
{
Console.WriteLine("Login: {0}: Banned account '{1}'", e.State, un);
e.Accepted = false;
}
else
{
acct.LogAccess(e.State);
Console.WriteLine("Login: {0}: Account '{1}' at character list", e.State, un);
e.State.Account = acct;
e.Accepted = true;
e.CityInfo = StartingCities;
}
if (!e.Accepted)
AccountAttackLimiter.RegisterInvalidAccess(e.State);
}
public static bool CheckAccount(Mobile mobCheck, Mobile accCheck)
{
if (accCheck?.Account is Account a)
for (int i = 0; i < a.Length; ++i)
if (a[i] == mobCheck)
return true;
return false;
}
}
}

View file

@ -0,0 +1,50 @@
using System.Xml;
namespace Server.Accounting
{
public class AccountTag
{
/// <summary>
/// Constructs a new AccountTag instance with a specific name and value.
/// </summary>
/// <param name="name">Initial name.</param>
/// <param name="value">Initial value.</param>
public AccountTag(string name, string value)
{
Name = name;
Value = value;
}
/// <summary>
/// Deserializes an AccountTag instance from an xml element.
/// </summary>
/// <param name="node">The XmlElement instance from which to deserialize.</param>
public AccountTag(XmlElement node)
{
Name = Utility.GetAttribute(node, "name", "empty");
Value = Utility.GetText(node, "");
}
/// <summary>
/// Gets or sets the name of this tag.
/// </summary>
public string Name{ get; set; }
/// <summary>
/// Gets or sets the value of this tag.
/// </summary>
public string Value{ get; set; }
/// <summary>
/// Serializes this AccountTag instance to an XmlTextWriter.
/// </summary>
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
public void Save(XmlTextWriter xml)
{
xml.WriteStartElement("tag");
xml.WriteAttributeString("name", Name);
xml.WriteString(Value);
xml.WriteEndElement();
}
}
}

View file

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
namespace Server.Accounting
{
public class Accounts
{
private static Dictionary<string, IAccount> m_Accounts = new Dictionary<string, IAccount>();
static Accounts()
{
}
public static int Count => m_Accounts.Count;
public static void Configure()
{
EventSink.WorldLoad += Load;
EventSink.WorldSave += Save;
}
public static ICollection<IAccount> GetAccounts()
{
return m_Accounts.Values;
}
public static IAccount GetAccount(string username)
{
m_Accounts.TryGetValue(username, out IAccount a);
return a;
}
public static void Add(IAccount a)
{
m_Accounts[a.Username] = a;
}
public static void Remove(string username)
{
m_Accounts.Remove(username);
}
public static void Load()
{
m_Accounts = new Dictionary<string, IAccount>(32, StringComparer.OrdinalIgnoreCase);
string filePath = Path.Combine("Saves/Accounts", "accounts.xml");
if (!File.Exists(filePath))
return;
XmlDocument doc = new XmlDocument();
doc.Load(filePath);
XmlElement root = doc["accounts"];
foreach (XmlElement account in root.GetElementsByTagName("account"))
try
{
new Account(account);
}
catch
{
Console.WriteLine("Warning: Account instance load failed");
}
}
public static void Save(WorldSaveEventArgs e)
{
if (!Directory.Exists("Saves/Accounts"))
Directory.CreateDirectory("Saves/Accounts");
string filePath = Path.Combine("Saves/Accounts", "accounts.xml");
using (StreamWriter op = new StreamWriter(filePath))
{
XmlTextWriter xml = new XmlTextWriter(op) { Formatting = Formatting.Indented, IndentChar = '\t', Indentation = 1 };
xml.WriteStartDocument(true);
xml.WriteStartElement("accounts");
xml.WriteAttributeString("count", m_Accounts.Count.ToString());
foreach (Account a in GetAccounts())
a.Save(xml);
xml.WriteEndElement();
xml.Close();
}
}
}
}

View file

@ -0,0 +1,306 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
namespace Server
{
public class Firewall
{
static Firewall()
{
List = new List<IFirewallEntry>();
string path = "firewall.cfg";
if (File.Exists(path))
using (StreamReader ip = new StreamReader(path))
{
string line;
while ((line = ip.ReadLine()) != null)
{
line = line.Trim();
if (line.Length == 0)
continue;
List.Add(ToFirewallEntry(line));
/*
object toAdd;
IPAddress addr;
if ( IPAddress.TryParse( line, out addr ) )
toAdd = addr;
else
toAdd = line;
m_Blocked.Add( toAdd.ToString() );
* */
}
}
}
public static List<IFirewallEntry> List{ get; }
public static IFirewallEntry ToFirewallEntry(object entry)
{
if (entry is IFirewallEntry firewallEntry)
return firewallEntry;
if (entry is IPAddress address)
return new IPFirewallEntry(address);
if (entry is string s)
return ToFirewallEntry(s);
return null;
}
public static IFirewallEntry ToFirewallEntry(string entry)
{
if (IPAddress.TryParse(entry, out IPAddress addr))
return new IPFirewallEntry(addr);
//Try CIDR parse
string[] str = entry.Split('/');
if (str.Length == 2)
if (IPAddress.TryParse(str[0], out IPAddress cidrPrefix))
if (int.TryParse(str[1], out int cidrLength))
return new CIDRFirewallEntry(cidrPrefix, cidrLength);
return new WildcardIPFirewallEntry(entry);
}
public static void RemoveAt(int index)
{
List.RemoveAt(index);
Save();
}
public static void Remove(object obj)
{
IFirewallEntry entry = ToFirewallEntry(obj);
if (entry != null)
{
List.Remove(entry);
Save();
}
}
public static void Add(object obj)
{
if (obj is IPAddress address)
Add(address);
else if (obj is string s)
Add(s);
else if (obj is IFirewallEntry entry)
Add(entry);
}
public static void Add(IFirewallEntry entry)
{
if (!List.Contains(entry))
List.Add(entry);
Save();
}
public static void Add(string pattern)
{
IFirewallEntry entry = ToFirewallEntry(pattern);
if (!List.Contains(entry))
List.Add(entry);
Save();
}
public static void Add(IPAddress ip)
{
IFirewallEntry entry = new IPFirewallEntry(ip);
if (!List.Contains(entry))
List.Add(entry);
Save();
}
public static void Save()
{
string path = "firewall.cfg";
using (StreamWriter op = new StreamWriter(path))
{
for (int i = 0; i < List.Count; ++i)
op.WriteLine(List[i]);
}
}
public static bool IsBlocked(IPAddress ip)
{
for (int i = 0; i < List.Count; i++)
if (List[i].IsBlocked(ip))
return true;
return false;
/*
bool contains = false;
for ( int i = 0; !contains && i < m_Blocked.Count; ++i )
{
if ( m_Blocked[i] is IPAddress )
contains = ip.Equals( m_Blocked[i] );
else if ( m_Blocked[i] is String )
{
string s = (string)m_Blocked[i];
contains = Utility.IPMatchCIDR( s, ip );
if ( !contains )
contains = Utility.IPMatch( s, ip );
}
}
return contains;
* */
}
#region Firewall Entries
public interface IFirewallEntry
{
bool IsBlocked(IPAddress address);
}
public class IPFirewallEntry : IFirewallEntry
{
private IPAddress m_Address;
public IPFirewallEntry(IPAddress address)
{
m_Address = address;
}
public bool IsBlocked(IPAddress address)
{
return m_Address.Equals(address);
}
public override string ToString()
{
return m_Address.ToString();
}
public override bool Equals(object obj)
{
if (obj is IPAddress)
return obj.Equals(m_Address);
if (obj is string s)
{
if (IPAddress.TryParse(s, out IPAddress otherAddress))
return otherAddress.Equals(m_Address);
}
else if (obj is IPFirewallEntry entry)
{
return m_Address.Equals(entry.m_Address);
}
return false;
}
public override int GetHashCode()
{
return m_Address.GetHashCode();
}
}
public class CIDRFirewallEntry : IFirewallEntry
{
private int m_CIDRLength;
private IPAddress m_CIDRPrefix;
public CIDRFirewallEntry(IPAddress cidrPrefix, int cidrLength)
{
m_CIDRPrefix = cidrPrefix;
m_CIDRLength = cidrLength;
}
public bool IsBlocked(IPAddress address)
{
return Utility.IPMatchCIDR(m_CIDRPrefix, address, m_CIDRLength);
}
public override string ToString()
{
return $"{m_CIDRPrefix}/{m_CIDRLength}";
}
public override bool Equals(object obj)
{
if (obj is string entry)
{
string[] str = entry.Split('/');
if (str.Length == 2)
if (IPAddress.TryParse(str[0], out IPAddress cidrPrefix))
if (int.TryParse(str[1], out int cidrLength))
return m_CIDRPrefix.Equals(cidrPrefix) && m_CIDRLength.Equals(cidrLength);
}
else if (obj is CIDRFirewallEntry cidrEntry)
{
return m_CIDRPrefix.Equals(cidrEntry.m_CIDRPrefix) && m_CIDRLength.Equals(cidrEntry.m_CIDRLength);
}
return false;
}
public override int GetHashCode()
{
return m_CIDRPrefix.GetHashCode() ^ m_CIDRLength.GetHashCode();
}
}
public class WildcardIPFirewallEntry : IFirewallEntry
{
private string m_Entry;
private bool m_Valid;
public WildcardIPFirewallEntry(string entry)
{
m_Entry = entry;
}
public bool IsBlocked(IPAddress address)
{
if (!m_Valid)
return false; //Why process if it's invalid? it'll return false anyway after processing it.
bool matched = Utility.IPMatch(m_Entry, address, out bool valid);
m_Valid = valid;
return matched;
}
public override string ToString()
{
return m_Entry;
}
public override bool Equals(object obj)
{
if (obj is string)
return obj.Equals(m_Entry);
return obj is WildcardIPFirewallEntry entry && m_Entry.Equals(entry.m_Entry);
}
public override int GetHashCode()
{
return m_Entry.GetHashCode();
}
}
#endregion
}
}

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Net;
using Server.Network;
namespace Server.Misc
{
public class IPLimiter
{
public static bool Enabled = true;
public static bool SocketBlock = true; // true to block at connection, false to block at login request
public static int MaxAddresses = 10;
public static IPAddress[] Exemptions =
{
//IPAddress.Parse( "127.0.0.1" ),
};
public static bool IsExempt(IPAddress ip)
{
for (int i = 0; i < Exemptions.Length; i++)
if (ip.Equals(Exemptions[i]))
return true;
return false;
}
public static bool Verify(IPAddress ourAddress)
{
if (!Enabled || IsExempt(ourAddress))
return true;
List<NetState> netStates = NetState.Instances;
int count = 0;
for (int i = 0; i < netStates.Count; ++i)
{
NetState compState = netStates[i];
if (ourAddress.Equals(compState.Address))
{
++count;
if (count >= MaxAddresses)
return false;
}
}
return true;
}
}
}