Adds assemblies config, fixes crash bugs. (#134)
This commit is contained in:
parent
0140eb73b4
commit
8ec166bcd0
3223 changed files with 191 additions and 191 deletions
46
Projects/UOContent/Accounting/AccessRestrictions.cs
Normal file
46
Projects/UOContent/Accounting/AccessRestrictions.cs
Normal 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.Context.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1049
Projects/UOContent/Accounting/Account.cs
Normal file
1049
Projects/UOContent/Accounting/Account.cs
Normal file
File diff suppressed because it is too large
Load diff
132
Projects/UOContent/Accounting/AccountAttackLimiter.cs
Normal file
132
Projects/UOContent/Accounting/AccountAttackLimiter.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
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;
|
||||
public static void Configure()
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("accountAttackLimiter.enable", true);
|
||||
}
|
||||
|
||||
private static readonly 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Projects/UOContent/Accounting/AccountComment.cs
Normal file
73
Projects/UOContent/Accounting/AccountComment.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
415
Projects/UOContent/Accounting/AccountHandler.cs
Normal file
415
Projects/UOContent/Accounting/AccountHandler.cs
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Server.Accounting;
|
||||
using Server.Engines.Help;
|
||||
using Server.Network;
|
||||
using Server.Regions;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class AccountHandler
|
||||
{
|
||||
private static int MaxAccountsPerIP;
|
||||
private static bool AutoAccountCreation;
|
||||
private static bool RestrictDeletion = !TestCenter.Enabled;
|
||||
private static TimeSpan DeleteDelay = TimeSpan.FromDays(7.0);
|
||||
private static bool PasswordCommandEnabled;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
MaxAccountsPerIP = ServerConfiguration.GetOrUpdateSetting("accountHandler.maxAccountsPerIP", 1);
|
||||
AutoAccountCreation = ServerConfiguration.GetOrUpdateSetting("accountHandler.enableAutoAccountCreation", true);
|
||||
PasswordCommandEnabled = ServerConfiguration.GetOrUpdateSetting("accountHandler.enablePlayerPasswordCommand", false);
|
||||
}
|
||||
|
||||
private static readonly 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 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.");
|
||||
|
||||
/* The next available Counselor/Game Master will respond as soon as possible.
|
||||
* Please check your Journal for messages every few minutes.
|
||||
*/
|
||||
from.SendLocalizedMessage(501234, "", 0x35);
|
||||
|
||||
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(NetState state, int 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Projects/UOContent/Accounting/AccountTag.cs
Normal file
50
Projects/UOContent/Accounting/AccountTag.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Projects/UOContent/Accounting/Accounts.cs
Normal file
92
Projects/UOContent/Accounting/Accounts.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
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 IEnumerable<IAccount> GetAccounts() => 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(bool message)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
270
Projects/UOContent/Accounting/Firewall.cs
Normal file
270
Projects/UOContent/Accounting/Firewall.cs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
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;
|
||||
* */
|
||||
}
|
||||
|
||||
public interface IFirewallEntry
|
||||
{
|
||||
bool IsBlocked(IPAddress address);
|
||||
}
|
||||
|
||||
public class IPFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly IPAddress m_Address;
|
||||
|
||||
public IPFirewallEntry(IPAddress address) => m_Address = address;
|
||||
|
||||
public bool IsBlocked(IPAddress address) => m_Address.Equals(address);
|
||||
|
||||
public override string ToString() => 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() => m_Address.GetHashCode();
|
||||
}
|
||||
|
||||
public class CIDRFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly int m_CIDRLength;
|
||||
private readonly IPAddress m_CIDRPrefix;
|
||||
|
||||
public CIDRFirewallEntry(IPAddress cidrPrefix, int cidrLength)
|
||||
{
|
||||
m_CIDRPrefix = cidrPrefix;
|
||||
m_CIDRLength = cidrLength;
|
||||
}
|
||||
|
||||
public bool IsBlocked(IPAddress address) => Utility.IPMatchCIDR(m_CIDRPrefix, address, m_CIDRLength);
|
||||
|
||||
public override string ToString() => $"{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() => m_CIDRPrefix.GetHashCode() ^ m_CIDRLength.GetHashCode();
|
||||
}
|
||||
|
||||
public class WildcardIPFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly 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() => 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() => m_Entry.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Projects/UOContent/Accounting/IPLimiter.cs
Normal file
53
Projects/UOContent/Accounting/IPLimiter.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class IPLimiter
|
||||
{
|
||||
public static bool Enabled { get; private set; }
|
||||
public static bool SocketBlock { get; private set; }
|
||||
public static int MaxAddresses { get; private set; }
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("ipLimiter.enable", true);
|
||||
SocketBlock = ServerConfiguration.GetOrUpdateSetting("ipLimiter.blockAtConnection", true);
|
||||
MaxAddresses = ServerConfiguration.GetOrUpdateSetting("ipLimiter.maxConnectionsPerIP", 10);
|
||||
}
|
||||
|
||||
public static IPAddress[] Exemptions =
|
||||
{
|
||||
// IPAddress.Parse( "127.0.0.1" ),
|
||||
};
|
||||
|
||||
public static bool IsExempt(IPAddress ip) => Exemptions.Contains(ip);
|
||||
|
||||
public static bool Verify(IPAddress ourAddress)
|
||||
{
|
||||
if (!Enabled || IsExempt(ourAddress))
|
||||
return true;
|
||||
|
||||
List<NetState> netStates = TcpServer.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Projects/UOContent/Accounting/IPasswordProtection.cs
Normal file
8
Projects/UOContent/Accounting/IPasswordProtection.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace Server.Accounting
|
||||
{
|
||||
public interface IPasswordProtection
|
||||
{
|
||||
string EncryptPassword(string plainPassword);
|
||||
bool ValidatePassword(string encryptedPassword, string plainPassword);
|
||||
}
|
||||
}
|
||||
71
Projects/UOContent/Accounting/Security/AccountSecurity.cs
Normal file
71
Projects/UOContent/Accounting/Security/AccountSecurity.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: AccountSecurity.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public enum PasswordProtectionAlgorithm
|
||||
{
|
||||
// Obsolete algorithms from RunUO. These are not secure!
|
||||
// They are included for password upgrades only.
|
||||
None,
|
||||
MD5,
|
||||
SHA1,
|
||||
|
||||
// Supported algorithms
|
||||
SHA2, // ServUO compatibility
|
||||
PBKDF2,
|
||||
Argon2 // Recommended algorithm for real security.
|
||||
}
|
||||
|
||||
public static class AccountSecurity
|
||||
{
|
||||
public static PasswordProtectionAlgorithm CurrentAlgorithm { get; set; }
|
||||
|
||||
public static IPasswordProtection CurrentPasswordProtection => GetPasswordProtection(CurrentAlgorithm);
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
CurrentAlgorithm =
|
||||
ServerConfiguration.GetOrUpdateSetting("accountSecurity.encryptionAlgorithm", PasswordProtectionAlgorithm.Argon2);
|
||||
|
||||
if (CurrentAlgorithm < PasswordProtectionAlgorithm.SHA2)
|
||||
throw new Exception($"Security: {CurrentAlgorithm} is obsolete and not secure. Do not use it.");
|
||||
}
|
||||
|
||||
public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm)
|
||||
{
|
||||
var passwordProtection = algorithm switch
|
||||
{
|
||||
PasswordProtectionAlgorithm.MD5 => MD5PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.SHA1 => SHA1PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.SHA2 => SHA2PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.Argon2 => Argon2PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.None => throw new Exception("Do not use PasswordProtectionAlgorithm.None"),
|
||||
_ => throw new Exception("No algorithm")
|
||||
};
|
||||
|
||||
return passwordProtection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: Argon2PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class Argon2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new Argon2PasswordProtection();
|
||||
private Argon2PasswordHasher m_PasswordHasher = new Argon2PasswordHasher();
|
||||
|
||||
public string EncryptPassword(string plainPassword) =>
|
||||
m_PasswordHasher.Hash(plainPassword);
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
m_PasswordHasher.Verify(encryptedPassword, plainPassword);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: MD5PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class MD5PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new MD5PasswordProtection();
|
||||
private MD5CryptoServiceProvider m_MD5HashProvider = new MD5CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return HexStringConverter.GetString(m_MD5HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: PBKDF2PasswordProtection.cs *
|
||||
* Created: 2020/04/30 - Updated: 2020/05/01 *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Security.Cryptography;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class PBKDF2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new PBKDF2PasswordProtection();
|
||||
|
||||
private const ushort m_MinIterations = 1024;
|
||||
private const ushort m_MaxIterations = 1536;
|
||||
private static readonly HashAlgorithmName m_Algorithm = HashAlgorithmName.SHA256;
|
||||
private const int m_SaltSize = 8;
|
||||
private const int m_HashSize = 32;
|
||||
private const int m_OutputSize = 2 + m_SaltSize + m_HashSize;
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
Span<byte> output = stackalloc byte[m_OutputSize];
|
||||
int iterations = Utility.RandomMinMax(m_MinIterations, m_MaxIterations);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(output.Slice(0, 2), (ushort)iterations);
|
||||
|
||||
var rfc2898 = new Rfc2898DeriveBytes(plainPassword, m_SaltSize, iterations, m_Algorithm);
|
||||
rfc2898.Salt.CopyTo(output.Slice(2, m_SaltSize));
|
||||
rfc2898.GetBytes(m_HashSize).CopyTo(output.Slice(m_SaltSize + 2));
|
||||
|
||||
return HexStringConverter.GetString(output);
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword)
|
||||
{
|
||||
Span<byte> encryptedBytes = stackalloc byte[m_OutputSize];
|
||||
HexStringConverter.GetBytes(encryptedPassword, encryptedBytes);
|
||||
|
||||
ushort iterations = BinaryPrimitives.ReadUInt16LittleEndian(encryptedBytes.Slice(0, 2));
|
||||
Span<byte> salt = encryptedBytes.Slice(2, m_SaltSize);
|
||||
|
||||
ReadOnlySpan<byte> hash =
|
||||
new Rfc2898DeriveBytes(plainPassword, salt.ToArray(), iterations, m_Algorithm).GetBytes(m_HashSize);
|
||||
|
||||
return hash.SequenceEqual(encryptedBytes.Slice(m_SaltSize + 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: SHA1PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class SHA1PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new SHA1PasswordProtection();
|
||||
private SHA1CryptoServiceProvider m_SHA1HashProvider = new SHA1CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return HexStringConverter.GetString(m_SHA1HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: SHA2PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* 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 <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class SHA2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new SHA2PasswordProtection();
|
||||
private SHA512CryptoServiceProvider m_SHA2HashProvider = new SHA512CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return HexStringConverter.GetString(m_SHA2HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue