diff --git a/Projects/Server/Events/AccountLoginEvent.cs b/Projects/Server/Events/AccountLoginEvent.cs
deleted file mode 100644
index 76628e0eb3..0000000000
--- a/Projects/Server/Events/AccountLoginEvent.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-/*************************************************************************
- * ModernUO *
- * Copyright 2019-2023 - ModernUO Development Team *
- * Email: hi@modernuo.com *
- * File: AccountLoginEvent.cs *
- * *
- * 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. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- *************************************************************************/
-
-using System;
-using System.Runtime.CompilerServices;
-using Server.Network;
-
-namespace Server;
-
-public class AccountLoginEventArgs
-{
- public AccountLoginEventArgs(NetState state, string username, string password)
- {
- State = state;
- Username = username;
- Password = password;
- }
-
- public NetState State { get; }
-
- public string Username { get; }
-
- public string Password { get; }
-
- public bool Accepted { get; set; }
-
- public ALRReason RejectReason { get; set; }
-}
-
-public static partial class EventSink
-{
- public static event Action AccountLogin;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void InvokeAccountLogin(AccountLoginEventArgs e) => AccountLogin?.Invoke(e);
-}
diff --git a/Projects/UOContent/Accounting/AccountHandler.cs b/Projects/UOContent/Accounting/AccountHandler.cs
index 1dc33cf6b1..836f6d5474 100644
--- a/Projects/UOContent/Accounting/AccountHandler.cs
+++ b/Projects/UOContent/Accounting/AccountHandler.cs
@@ -1,4 +1,5 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Net;
using Server.Accounting;
@@ -24,13 +25,28 @@ public static 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 readonly bool RestrictDeletion = !TestCenter.Enabled;
+ private static readonly TimeSpan DeleteDelay = TimeSpan.FromDays(7.0);
private static bool PasswordCommandEnabled;
+ public static bool AsyncAccountLogin { get; private set; }
private static Dictionary m_IPTable;
- private static char[] m_ForbiddenChars = { '<', '>', ':', '"', '/', '\\', '|', '?', '*' };
+ private static readonly SearchValues _forbiddenAccountChars = SearchValues.Create([
+ '\0', '\x1', '\x2', '\x3', '\x4', '\x5', '\x6', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\x7', '\x8', '\x9',
+ '\xA', '\xB', '\xC', '\xD', '\xE', '\xF', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18',
+ '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F',
+
+ '<', '>', ':', '"', '/', '\\', '|', '?', '*',
+
+ '\x7F', '\xFF',
+ ]);
+
+ private static readonly SearchValues _forbiddenPasswordChars = SearchValues.Create([
+ '\0', '\x1', '\x2', '\x3', '\x4', '\x5', '\x6', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\x7', '\x8', '\x9',
+ '\xA', '\xB', '\xC', '\xD', '\xE', '\xF', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18',
+ '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F', '\x7F', '\xFF'
+ ]);
public static AccessLevel LockdownLevel { get; set; }
@@ -47,7 +63,7 @@ public static Dictionary IPTable
if (a.LoginIPs.Length > 0)
{
var ip = a.LoginIPs[0];
- m_IPTable[ip] = (m_IPTable.TryGetValue(ip, out var value) ? value : 0) + 1;
+ m_IPTable[ip] = (m_IPTable.GetValueOrDefault(ip, 0)) + 1;
}
}
}
@@ -58,6 +74,7 @@ public static Dictionary IPTable
public static void Configure()
{
+ AsyncAccountLogin = ServerConfiguration.GetSetting("accountHandler.useAsyncAccountLogin", false);
MaxAccountsPerIP = ServerConfiguration.GetOrUpdateSetting("accountHandler.maxAccountsPerIP", 1);
AutoAccountCreation = ServerConfiguration.GetOrUpdateSetting("accountHandler.enableAutoAccountCreation", true);
PasswordCommandEnabled = ServerConfiguration.GetOrUpdateSetting(
@@ -73,7 +90,6 @@ public static void Configure()
public static void Initialize()
{
- EventSink.AccountLogin += EventSink_AccountLogin;
EventSink.GameLogin += EventSink_GameLogin;
}
@@ -262,19 +278,6 @@ public static void DeleteRequest(NetState state, int index)
public static bool CanCreate(IPAddress ip) =>
!IPTable.TryGetValue(ip, out var result) || result < MaxAccountsPerIP;
- private static bool IsForbiddenChar(char c)
- {
- for (var 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)
@@ -282,21 +285,19 @@ private static Account CreateAccount(NetState state, string un, string pw)
return null;
}
- var isSafe = !(un.StartsWithOrdinal(" ") ||
- un.EndsWithOrdinal(" ") ||
- un.EndsWithOrdinal("."));
+ var unSpan = un.AsSpan();
- for (var i = 0; isSafe && i < un.Length; ++i)
+ if (unSpan[0] == ' ' || unSpan[^1] is ' ' or '.')
{
- isSafe = un[i] >= 0x20 && un[i] < 0x7F && !IsForbiddenChar(un[i]);
+ return null;
}
- for (var i = 0; isSafe && i < pw.Length; ++i)
+ if (unSpan.ContainsAny(_forbiddenAccountChars))
{
- isSafe = pw[i] >= 0x20 && pw[i] < 0x7F;
+ return null;
}
- if (!isSafe)
+ if (pw.AsSpan().ContainsAny(_forbiddenPasswordChars))
{
return null;
}
@@ -320,20 +321,18 @@ private static Account CreateAccount(NetState state, string un, string pw)
return a;
}
- public static void EventSink_AccountLogin(AccountLoginEventArgs e)
+ public static void AccountLogin(AccountLoginEventArgs e)
{
- var un = e.Username;
- var pw = e.Password;
-
- e.Accepted = false;
-
- if (Accounts.GetAccount(un) is not Account acct)
+ var state = e.State;
+ var username = e.Username;
+ var password = e.Password;
+ if (Accounts.GetAccount(username) is not Account acct)
{
// To prevent someone from making an account of just '' or a bunch of meaningless spaces
- if (AutoAccountCreation && !string.IsNullOrWhiteSpace(un))
+ if (AutoAccountCreation && !string.IsNullOrWhiteSpace(username))
{
- e.State.Account = acct = CreateAccount(e.State, un, pw);
- e.Accepted = acct?.CheckAccess(e.State) ?? false;
+ state.Account = acct = CreateAccount(state, username, password);
+ e.Accepted = acct?.CheckAccess(state) ?? false;
if (!e.Accepted)
{
@@ -342,37 +341,37 @@ public static void EventSink_AccountLogin(AccountLoginEventArgs e)
}
else
{
- logger.Information("Login: {NetState} Invalid username '{Username}'", e.State, un);
+ logger.Information("Login: {NetState} Invalid username '{Username}'", state, username);
e.RejectReason = ALRReason.Invalid;
}
}
- else if (!acct.HasAccess(e.State))
+ else if (!acct.HasAccess(state))
{
- logger.Information("Login: {NetState} Access denied for '{Username}'", e.State, un);
+ logger.Information("Login: {NetState} Access denied for '{Username}'", state, username);
e.RejectReason = LockdownLevel > AccessLevel.Player ? ALRReason.BadComm : ALRReason.BadPass;
}
- else if (!acct.CheckPassword(pw))
+ else if (!acct.CheckPassword(password))
{
- logger.Information("Login: {NetState} Invalid password for '{Username}'", e.State, un);
+ logger.Information("Login: {NetState} Invalid password for '{Username}'", state, username);
e.RejectReason = ALRReason.BadPass;
}
else if (acct.Banned)
{
- logger.Information("Login: {NetState} Banned account '{Username}'", e.State, un);
+ logger.Information("Login: {NetState} Banned account '{Username}'", state, username);
e.RejectReason = ALRReason.Blocked;
}
else
{
- logger.Information("Login: {NetState} Valid credentials for '{Username}'", e.State, un);
- e.State.Account = acct;
+ logger.Information("Login: {NetState} Valid credentials for '{Username}'", state, username);
+ state.Account = acct;
e.Accepted = true;
- acct.LogAccess(e.State);
+ acct.LogAccess(state);
}
if (!e.Accepted)
{
- AccountAttackLimiter.RegisterInvalidAccess(e.State);
+ AccountAttackLimiter.RegisterInvalidAccess(state);
}
}
diff --git a/Projects/UOContent/Misc/ServerAccess.cs b/Projects/UOContent/Misc/ServerAccess.cs
index f6786246c7..1c0bf20069 100644
--- a/Projects/UOContent/Misc/ServerAccess.cs
+++ b/Projects/UOContent/Misc/ServerAccess.cs
@@ -63,21 +63,21 @@ public static void Configure()
}
}
- public static void Initialize()
+ public static void ResetProtectedAccount(AccountLoginEventArgs e)
{
- EventSink.AccountLogin += EventSink_ResetProtectedAccount;
- }
+ var username = e.Username;
+ if (Accounts.GetAccount(username) is not Account acct)
+ {
+ return;
+ }
- public static void EventSink_ResetProtectedAccount(AccountLoginEventArgs e)
- {
- var username = e.Username.ToLower();
if (!ServerAccessConfiguration.ProtectedAccounts.Contains(username))
{
return;
}
- if (Accounts.GetAccount(username) is not Account acct
- || !acct.Banned && acct.AccessLevel >= AccessLevel.Owner || !acct.CheckPassword(e.Password))
+ var password = e.Password;
+ if (!acct.Banned && acct.AccessLevel >= AccessLevel.Owner || !acct.CheckPassword(password))
{
return;
}
@@ -87,7 +87,7 @@ public static void EventSink_ResetProtectedAccount(AccountLoginEventArgs e)
logger.Warning("Protected account \"{Username}\" has been reset.", username);
- if (e.RejectReason is ALRReason.Blocked or ALRReason.BadPass or ALRReason.BadComm)
+ if (!e.Accepted && e.RejectReason is ALRReason.Blocked or ALRReason.BadPass or ALRReason.BadComm)
{
e.Accepted = true;
}
diff --git a/Projects/UOContent/Network/Packets/AccountLoginEventArgs.cs b/Projects/UOContent/Network/Packets/AccountLoginEventArgs.cs
new file mode 100644
index 0000000000..dd556f9f02
--- /dev/null
+++ b/Projects/UOContent/Network/Packets/AccountLoginEventArgs.cs
@@ -0,0 +1,21 @@
+namespace Server.Network;
+
+public class AccountLoginEventArgs
+{
+ public AccountLoginEventArgs(NetState state, string username, string password)
+ {
+ State = state;
+ Username = username;
+ Password = password;
+ }
+
+ public NetState State { get; }
+
+ public string Username { get; }
+
+ public string Password { get; }
+
+ public bool Accepted { get; set; }
+
+ public ALRReason RejectReason { get; set; }
+}
diff --git a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs
index a6a87ae809..6e2d658de7 100644
--- a/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs
+++ b/Projects/UOContent/Network/Packets/IncomingAccountPackets.cs
@@ -17,6 +17,8 @@
using System.Buffers;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using Server.Accounting;
using Server.Assistants;
using Server.Commands;
@@ -496,11 +498,39 @@ public static void AccountLogin(NetState state, SpanReader reader)
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
- var accountLoginEventArgs = new AccountLoginEventArgs(state, username, password);
+ var loginEventArgs = new AccountLoginEventArgs(state, username, password);
- EventSink.InvokeAccountLogin(accountLoginEventArgs);
+ // Fire and forget the async login
+ Task.Run(() =>
+ {
+ AccountLoginAsync(loginEventArgs);
+ }
+ ).ConfigureAwait(false);
+ }
+
+ private static void AccountLoginAsync(AccountLoginEventArgs e)
+ {
+ // Replace with your async/await logic
+ Core.LoopContext.Send(
+ o =>
+ {
+ var loginEventArgs = (AccountLoginEventArgs)o;
+ AccountHandler.AccountLogin(loginEventArgs);
+ ServerAccess.ResetProtectedAccount(loginEventArgs);
+ }, e);
+
+ // Keep this to ensure the response is sent on the correct thread at the end of the async work
+ Core.LoopContext.Post(
+ o =>
+ {
+ SendLoginResponse((AccountLoginEventArgs)o);
+ }, e);
+ }
- if (accountLoginEventArgs.Accepted)
+ private static void SendLoginResponse(AccountLoginEventArgs e)
+ {
+ var state = e.State;
+ if (e.Accepted)
{
var serverListEventArgs = new ServerListEventArgs(state, state.Account);
@@ -520,7 +550,7 @@ public static void AccountLogin(NetState state, SpanReader reader)
else
{
state.Account = null;
- AccountLogin_ReplyRej(state, accountLoginEventArgs.RejectReason);
+ AccountLogin_ReplyRej(state, e.RejectReason);
}
}