Updated Bnet Server.

Port From (https://github.com/TrinityCore/TrinityCore)
This commit is contained in:
hondacrx
2024-02-21 00:05:48 -05:00
parent 7960e7b192
commit 9e3a7df6a7
62 changed files with 2186 additions and 731 deletions
+4
View File
@@ -2,9 +2,13 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using BNetServer; using BNetServer;
using BNetServer.Networking;
using BNetServer.REST;
public static class Global public static class Global
{ {
public static RealmManager RealmMgr { get { return RealmManager.Instance; } } public static RealmManager RealmMgr { get { return RealmManager.Instance; } }
public static SessionManager SessionMgr { get { return SessionManager.Instance; } }
public static LoginRESTService LoginService { get { return LoginRESTService.Instance; } }
public static LoginServiceManager LoginServiceMgr { get { return LoginServiceManager.Instance; } } public static LoginServiceManager LoginServiceMgr { get { return LoginServiceManager.Instance; } }
} }
@@ -4,13 +4,11 @@
using BNetServer.Networking; using BNetServer.Networking;
using Framework.Configuration; using Framework.Configuration;
using Framework.Constants; using Framework.Constants;
using Framework.Web;
using Google.Protobuf; using Google.Protobuf;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Net;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@@ -19,67 +17,12 @@ namespace BNetServer
public class LoginServiceManager : Singleton<LoginServiceManager> public class LoginServiceManager : Singleton<LoginServiceManager>
{ {
ConcurrentDictionary<(uint ServiceHash, uint MethodId), BnetServiceHandler> serviceHandlers = new(); ConcurrentDictionary<(uint ServiceHash, uint MethodId), BnetServiceHandler> serviceHandlers = new();
FormInputs formInputs = new();
string[] _hostnames = new string[2];
IPAddress[] _addresses = new IPAddress[2];
int _port;
X509Certificate2 certificate; X509Certificate2 certificate;
LoginServiceManager() { } LoginServiceManager() { }
public void Initialize() public void Initialize()
{ {
_port = ConfigMgr.GetDefaultValue("LoginREST.Port", 8081);
if (_port < 0 || _port > 0xFFFF)
{
Log.outError(LogFilter.Network, $"Specified login service port ({_port}) out of allowed range (1-65535), defaulting to 8081");
_port = 8081;
}
_hostnames[0] = ConfigMgr.GetDefaultValue("LoginREST.ExternalAddress", "127.0.0.1");
var externalAddress = Dns.GetHostAddresses(_hostnames[0], System.Net.Sockets.AddressFamily.InterNetwork);
if (externalAddress == null || externalAddress.Empty())
{
Log.outError(LogFilter.Network, $"Could not resolve LoginREST.ExternalAddress {_hostnames[0]}");
return;
}
_addresses[0] = externalAddress[0];
_hostnames[1] = ConfigMgr.GetDefaultValue("LoginREST.LocalAddress", "127.0.0.1");
var localAddress = Dns.GetHostAddresses(_hostnames[1], System.Net.Sockets.AddressFamily.InterNetwork);
if (localAddress == null || localAddress.Empty())
{
Log.outError(LogFilter.Network, $"Could not resolve LoginREST.ExternalAddress {_hostnames[1]}");
return;
}
_addresses[1] = localAddress[0];
// set up form inputs
formInputs.Type = "LOGIN_FORM";
var input = new FormInput();
input.Id = "account_name";
input.Type = "text";
input.Label = "E-mail";
input.MaxLength = 320;
formInputs.Inputs.Add(input);
input = new FormInput();
input.Id = "password";
input.Type = "password";
input.Label = "Password";
input.MaxLength = 16;
formInputs.Inputs.Add(input);
input = new FormInput();
input.Id = "log_in_submit";
input.Type = "submit";
input.Label = "Log In";
formInputs.Inputs.Add(input);
certificate = new X509Certificate2(ConfigMgr.GetDefaultValue("CertificatesFile", "./BNetServer.pfx")); certificate = new X509Certificate2(ConfigMgr.GetDefaultValue("CertificatesFile", "./BNetServer.pfx"));
Assembly currentAsm = Assembly.GetExecutingAssembly(); Assembly currentAsm = Assembly.GetExecutingAssembly();
@@ -117,21 +60,6 @@ namespace BNetServer
return serviceHandlers.LookupByKey((serviceHash, methodId)); return serviceHandlers.LookupByKey((serviceHash, methodId));
} }
public string GetHostnameForClient(IPEndPoint address)
{
if (IPAddress.IsLoopback(address.Address))
return _hostnames[1];
return _hostnames[0];
}
public int GetPort() { return _port; }
public FormInputs GetFormInput()
{
return formInputs;
}
public X509Certificate2 GetCertificate() public X509Certificate2 GetCertificate()
{ {
return certificate; return certificate;
-174
View File
@@ -1,174 +0,0 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Configuration;
using Framework.Constants;
using Framework.Database;
using Framework.Networking;
using Framework.Serialization;
using Framework.Web;
using System;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
namespace BNetServer.Networking
{
public class RestSession : SSLSocket
{
public RestSession(Socket socket) : base(socket) { }
public override void Accept()
{
AsyncHandshake(Global.LoginServiceMgr.GetCertificate());
}
public async override void ReadHandler(byte[] data, int receivedLength)
{
var httpRequest = HttpHelper.ParseRequest(data, receivedLength);
if (httpRequest == null)
return;
switch (httpRequest.Method)
{
case "GET":
default:
SendResponse(HttpCode.Ok, Global.LoginServiceMgr.GetFormInput());
break;
case "POST":
HandleLoginRequest(httpRequest);
return;
}
await AsyncRead();
}
public void HandleLoginRequest(HttpHeader request)
{
LogonData loginForm = Json.CreateObject<LogonData>(request.Content);
LogonResult loginResult = new();
if (loginForm == null)
{
loginResult.AuthenticationState = "LOGIN";
loginResult.ErrorCode = "UNABLE_TO_DECODE";
loginResult.ErrorMessage = "There was an internal error while connecting to Battle.net. Please try again later.";
SendResponse(HttpCode.BadRequest, loginResult);
return;
}
string login = "";
string password = "";
for (int i = 0; i < loginForm.Inputs.Count; ++i)
{
switch (loginForm.Inputs[i].Id)
{
case "account_name":
login = loginForm.Inputs[i].Value;
break;
case "password":
password = loginForm.Inputs[i].Value;
break;
}
}
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelBnetAuthentication);
stmt.AddValue(0, login);
SQLResult result = DB.Login.Query(stmt);
if (!result.IsEmpty())
{
uint accountId = result.Read<uint>(0);
string pass_hash = result.Read<string>(1);
uint failedLogins = result.Read<uint>(2);
string loginTicket = result.Read<string>(3);
uint loginTicketExpiry = result.Read<uint>(4);
bool isBanned = result.Read<ulong>(5) != 0;
if (CalculateShaPassHash(login.ToUpper(), password.ToUpper()) == pass_hash)
{
if (loginTicket.IsEmpty() || loginTicketExpiry < Time.UnixTime)
{
byte[] ticket = Array.Empty<byte>().GenerateRandomKey(20);
loginTicket = "TC-" + ticket.ToHexString();
}
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UpdBnetAuthentication);
stmt.AddValue(0, loginTicket);
stmt.AddValue(1, Time.UnixTime + 3600);
stmt.AddValue(2, accountId);
DB.Login.Execute(stmt);
loginResult.LoginTicket = loginTicket;
}
else if (!isBanned)
{
uint maxWrongPassword = ConfigMgr.GetDefaultValue("WrongPass.MaxCount", 0u);
if (ConfigMgr.GetDefaultValue("WrongPass.Logging", false))
Log.outDebug(LogFilter.Network, $"[{request.Host}, Account {login}, Id {accountId}] Attempted to connect with wrong password!");
if (maxWrongPassword != 0)
{
SQLTransaction trans = new();
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UpdBnetFailedLogins);
stmt.AddValue(0, accountId);
trans.Append(stmt);
++failedLogins;
Log.outDebug(LogFilter.Network, $"MaxWrongPass : {maxWrongPassword}, failed_login : {accountId}");
if (failedLogins >= maxWrongPassword)
{
BanMode banType = ConfigMgr.GetDefaultValue("WrongPass.BanType", BanMode.IP);
int banTime = ConfigMgr.GetDefaultValue("WrongPass.BanTime", 600);
if (banType == BanMode.Account)
{
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.InsBnetAccountAutoBanned);
stmt.AddValue(0, accountId);
}
else
{
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.InsIpAutoBanned);
stmt.AddValue(0, request.Host);
}
stmt.AddValue(1, banTime);
trans.Append(stmt);
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UpdBnetResetFailedLogins);
stmt.AddValue(0, accountId);
trans.Append(stmt);
}
DB.Login.CommitTransaction(trans);
}
}
loginResult.AuthenticationState = "DONE";
SendResponse(HttpCode.Ok, loginResult);
}
else
{
loginResult.AuthenticationState = "LOGIN";
loginResult.ErrorCode = "UNABLE_TO_DECODE";
loginResult.ErrorMessage = "There was an internal error while connecting to Battle.net. Please try again later.";
SendResponse(HttpCode.BadRequest, loginResult);
}
}
async void SendResponse<T>(HttpCode code, T response)
{
await AsyncWrite(HttpHelper.CreateResponse(code, Json.CreateString(response)));
}
string CalculateShaPassHash(string name, string password)
{
SHA256 sha256 = SHA256.Create();
var email = sha256.ComputeHash(Encoding.UTF8.GetBytes(name));
return sha256.ComputeHash(Encoding.UTF8.GetBytes(email.ToHexString() + ":" + password)).ToHexString(true);
}
}
}
@@ -39,11 +39,9 @@ namespace BNetServer.Networking
os = logonRequest.Platform; os = logonRequest.Platform;
build = (uint)logonRequest.ApplicationVersion; build = (uint)logonRequest.ApplicationVersion;
var hostname = Global.LoginServiceMgr.GetHostnameForClient(GetRemoteIpEndPoint());
ChallengeExternalRequest externalChallenge = new(); ChallengeExternalRequest externalChallenge = new();
externalChallenge.PayloadType = "web_auth_url"; externalChallenge.PayloadType = "web_auth_url";
externalChallenge.Payload = ByteString.CopyFromUtf8($"https://{Global.LoginServiceMgr.GetHostnameForClient(GetRemoteIpEndPoint())}:{Global.LoginServiceMgr.GetPort()}/bnetserver/login/"); externalChallenge.Payload = ByteString.CopyFromUtf8($"https://{Global.LoginService.GetHostnameForClient(GetRemoteIpAddress())}:{Global.LoginService.GetPort()}/bnetserver/login/");
SendRequest((uint)OriginalHash.ChallengeListener, 3, externalChallenge); SendRequest((uint)OriginalHash.ChallengeListener, 3, externalChallenge);
return BattlenetRpcErrorCode.Ok; return BattlenetRpcErrorCode.Ok;
@@ -55,7 +53,7 @@ namespace BNetServer.Networking
if (verifyWebCredentialsRequest.WebCredentials.IsEmpty) if (verifyWebCredentialsRequest.WebCredentials.IsEmpty)
return BattlenetRpcErrorCode.Denied; return BattlenetRpcErrorCode.Denied;
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelBnetAccountInfo); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_ACCOUNT_INFO);
stmt.AddValue(0, verifyWebCredentialsRequest.WebCredentials.ToStringUtf8()); stmt.AddValue(0, verifyWebCredentialsRequest.WebCredentials.ToStringUtf8());
SQLResult result = DB.Login.Query(stmt); SQLResult result = DB.Login.Query(stmt);
@@ -81,7 +79,7 @@ namespace BNetServer.Networking
} while (characterCountsResult.NextRow()); } while (characterCountsResult.NextRow());
} }
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelBnetLastPlayerCharacters); stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_LAST_PLAYER_CHARACTERS);
stmt.AddValue(0, accountInfo.Id); stmt.AddValue(0, accountInfo.Id);
SQLResult lastPlayerCharactersResult = DB.Login.Query(stmt); SQLResult lastPlayerCharactersResult = DB.Login.Query(stmt);
@@ -102,7 +100,7 @@ namespace BNetServer.Networking
} while (lastPlayerCharactersResult.NextRow()); } while (lastPlayerCharactersResult.NextRow());
} }
string ip_address = GetRemoteIpEndPoint().ToString(); string ip_address = GetRemoteIpAddress().ToString();
// If the IP is 'locked', check that the player comes indeed from the correct IP address // If the IP is 'locked', check that the player comes indeed from the correct IP address
if (accountInfo.IsLockedToIP) if (accountInfo.IsLockedToIP)
@@ -5,11 +5,14 @@ using Bgs.Protocol;
using Bgs.Protocol.GameUtilities.V1; using Bgs.Protocol.GameUtilities.V1;
using Framework.Constants; using Framework.Constants;
using Framework.Database; using Framework.Database;
using Framework.Serialization; using Framework.IO;
using Framework.Web; using Framework.Web;
using Framework.Web.Rest.Realmlist;
using Google.Protobuf; using Google.Protobuf;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace BNetServer.Networking namespace BNetServer.Networking
{ {
@@ -36,7 +39,7 @@ namespace BNetServer.Networking
{ {
Bgs.Protocol.Attribute attr = request.Attribute[i]; Bgs.Protocol.Attribute attr = request.Attribute[i];
if (attr.Name.Contains("Command_")) if (attr.Name.Contains("Command_"))
{ {
command = attr; command = attr;
Params[removeSuffix(attr.Name)] = attr.Value; Params[removeSuffix(attr.Name)] = attr.Value;
} }
@@ -80,10 +83,15 @@ namespace BNetServer.Networking
Variant identity = Params.LookupByKey("Param_Identity"); Variant identity = Params.LookupByKey("Param_Identity");
if (identity != null) if (identity != null)
{ {
var realmListTicketIdentity = Json.CreateObject<RealmListTicketIdentity>(identity.BlobValue.ToStringUtf8(), true); string jsonString = identity.BlobValue.ToStringUtf8().TrimEnd('\0');
var gameAccount = accountInfo.GameAccounts.LookupByKey(realmListTicketIdentity.GameAccountId); int jsonStart = jsonString.IndexOf(':');
if (gameAccount != null) if (jsonStart != -1)
gameAccountInfo = gameAccount; {
var realmListTicketIdentity = JsonSerializer.Deserialize<RealmListTicketIdentity>(jsonString.Substring(jsonStart + 1));
var gameAccount = accountInfo.GameAccounts.LookupByKey(realmListTicketIdentity.GameAccountId);
if (gameAccount != null)
gameAccountInfo = gameAccount;
}
} }
if (gameAccountInfo == null) if (gameAccountInfo == null)
@@ -98,18 +106,23 @@ namespace BNetServer.Networking
Variant clientInfo = Params.LookupByKey("Param_ClientInfo"); Variant clientInfo = Params.LookupByKey("Param_ClientInfo");
if (clientInfo != null) if (clientInfo != null)
{ {
var realmListTicketClientInformation = Json.CreateObject<RealmListTicketClientInformation>(clientInfo.BlobValue.ToStringUtf8(), true); string jsonString = clientInfo.BlobValue.ToStringUtf8().Trim('\0');
clientInfoOk = true; int jsonStart = jsonString.IndexOf(':');
int i = 0; if (jsonStart != -1)
foreach (byte b in realmListTicketClientInformation.Info.Secret) {
clientSecret[i++] = b; var realmListTicketClientInformation = JsonSerializer.Deserialize<RealmListTicketClientInformation>(jsonString.Substring(jsonStart + 1));
clientInfoOk = true;
int i = 0;
foreach (byte b in realmListTicketClientInformation.Info.Secret)
clientSecret[i++] = b;
}
} }
if (!clientInfoOk) if (!clientInfoOk)
return BattlenetRpcErrorCode.WowServicesDeniedRealmListTicket; return BattlenetRpcErrorCode.WowServicesDeniedRealmListTicket;
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UpdBnetLastLoginInfo); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_LAST_LOGIN_INFO);
stmt.AddValue(0, GetRemoteIpEndPoint().ToString()); stmt.AddValue(0, GetRemoteIpAddress().ToString());
stmt.AddValue(1, (byte)locale.ToEnum<Locale>()); stmt.AddValue(1, (byte)locale.ToEnum<Locale>());
stmt.AddValue(2, os); stmt.AddValue(2, os);
stmt.AddValue(3, accountInfo.Id); stmt.AddValue(3, accountInfo.Id);
@@ -119,7 +132,7 @@ namespace BNetServer.Networking
var attribute = new Bgs.Protocol.Attribute(); var attribute = new Bgs.Protocol.Attribute();
attribute.Name = "Param_RealmListTicket"; attribute.Name = "Param_RealmListTicket";
attribute.Value = new Variant(); attribute.Value = new Variant();
attribute.Value.BlobValue = ByteString.CopyFrom("AuthRealmListTicket", System.Text.Encoding.UTF8); attribute.Value.BlobValue = ByteString.CopyFrom("AuthRealmListTicket", Encoding.UTF8);
response.Attribute.Add(attribute); response.Attribute.Add(attribute);
return BattlenetRpcErrorCode.Ok; return BattlenetRpcErrorCode.Ok;
@@ -197,7 +210,10 @@ namespace BNetServer.Networking
realmCharacterCounts.Counts.Add(countEntry); realmCharacterCounts.Counts.Add(countEntry);
} }
compressed = Json.Deflate("JSONRealmCharacterCountList", realmCharacterCounts); var jsonData = Encoding.UTF8.GetBytes("JSONRealmCharacterCountList:" + JsonSerializer.Serialize(realmCharacterCounts) + "\0");
var compressedData = ZLib.Compress(jsonData);
compressed = BitConverter.GetBytes(jsonData.Length).Combine(compressedData);
attribute = new Bgs.Protocol.Attribute(); attribute = new Bgs.Protocol.Attribute();
attribute.Name = "Param_CharacterCountList"; attribute.Name = "Param_CharacterCountList";
@@ -211,9 +227,9 @@ namespace BNetServer.Networking
{ {
Variant realmAddress = Params.LookupByKey("Param_RealmAddress"); Variant realmAddress = Params.LookupByKey("Param_RealmAddress");
if (realmAddress != null) if (realmAddress != null)
return Global.RealmMgr.JoinRealm((uint)realmAddress.UintValue, build, GetRemoteIpEndPoint().Address, clientSecret, (Locale)Enum.Parse(typeof(Locale), locale), os, gameAccountInfo.Name, response); return Global.RealmMgr.JoinRealm((uint)realmAddress.UintValue, build, GetRemoteIpAddress(), clientSecret, (Locale)Enum.Parse(typeof(Locale), locale), os, _timezoneOffset, gameAccountInfo.Name, response);
return BattlenetRpcErrorCode.WowServicesInvalidJoinTicket; return BattlenetRpcErrorCode.WowServicesInvalidJoinTicket;
} }
} }
} }
+25 -11
View File
@@ -12,10 +12,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks;
namespace BNetServer.Networking namespace BNetServer.Networking
{ {
partial class Session : SSLSocket public partial class Session : SSLSocket
{ {
AccountInfo accountInfo; AccountInfo accountInfo;
GameAccountInfo gameAccountInfo; GameAccountInfo gameAccountInfo;
@@ -23,6 +24,7 @@ namespace BNetServer.Networking
string locale; string locale;
string os; string os;
uint build; uint build;
TimeSpan _timezoneOffset;
string ipCountry; string ipCountry;
byte[] clientSecret; byte[] clientSecret;
@@ -39,19 +41,19 @@ namespace BNetServer.Networking
responseCallbacks = new Dictionary<uint, Action<CodedInputStream>>(); responseCallbacks = new Dictionary<uint, Action<CodedInputStream>>();
} }
public override void Accept() public override void Start()
{ {
string ipAddress = GetRemoteIpEndPoint().ToString(); string ipAddress = GetRemoteIpAddress().ToString();
Log.outInfo(LogFilter.Network, $"{GetClientInfo()} Connection Accepted."); Log.outInfo(LogFilter.Network, $"{GetClientInfo()} Connection Accepted.");
// Verify that this IP is not in the ip_banned table // Verify that this IP is not in the ip_banned table
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DelExpiredIpBans)); DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS));
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelIpInfo); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_IP_INFO);
stmt.AddValue(0, ipAddress); stmt.AddValue(0, ipAddress);
stmt.AddValue(1, BitConverter.ToUInt32(GetRemoteIpEndPoint().Address.GetAddressBytes(), 0)); stmt.AddValue(1, BitConverter.ToUInt32(GetRemoteIpAddress().GetAddressBytes(), 0));
queryProcessor.AddCallback(DB.Login.AsyncQuery(stmt).WithCallback(async result => queryProcessor.AddCallback(DB.Login.AsyncQuery(stmt).WithCallback(async result =>
{ {
if (!result.IsEmpty()) if (!result.IsEmpty())
{ {
@@ -78,6 +80,18 @@ namespace BNetServer.Networking
})); }));
} }
public async override Task HandshakeHandler(Exception ex = null)
{
if (ex != null)
{
Log.outError(LogFilter.Session, $"{GetClientInfo()} SSL Handshake failed {ex.Message}");
CloseSocket();
return;
}
await AsyncRead();
}
public override bool Update() public override bool Update()
{ {
if (!base.Update()) if (!base.Update())
@@ -103,7 +117,7 @@ namespace BNetServer.Networking
header.MergeFrom(data, readPos, headerLength); header.MergeFrom(data, readPos, headerLength);
readPos += headerLength; readPos += headerLength;
var stream = new CodedInputStream(data, readPos, (int)header.Size); var stream = new CodedInputStream(data, readPos, (int)header.Size);
readPos += (int)header.Size; readPos += (int)header.Size;
if (header.ServiceId != 0xFE && header.ServiceHash != 0) if (header.ServiceId != 0xFE && header.ServiceHash != 0)
@@ -181,7 +195,7 @@ namespace BNetServer.Networking
{ {
var size = (ushort)header.CalculateSize(); var size = (ushort)header.CalculateSize();
byte[] bytes = new byte[2]; byte[] bytes = new byte[2];
bytes[0] = (byte)((size >> 8) & 0xff); bytes[0] = (byte)(size >> 8 & 0xff);
bytes[1] = (byte)(size & 0xff); bytes[1] = (byte)(size & 0xff);
var headerSizeBytes = BitConverter.GetBytes((ushort)header.CalculateSize()); var headerSizeBytes = BitConverter.GetBytes((ushort)header.CalculateSize());
@@ -192,7 +206,7 @@ namespace BNetServer.Networking
public string GetClientInfo() public string GetClientInfo()
{ {
string stream = '[' + GetRemoteIpEndPoint().ToString(); string stream = '[' + GetRemoteIpAddress().ToString();
if (accountInfo != null && !accountInfo.Login.IsEmpty()) if (accountInfo != null && !accountInfo.Login.IsEmpty())
stream += ", Account: " + accountInfo.Login; stream += ", Account: " + accountInfo.Login;
@@ -206,7 +220,7 @@ namespace BNetServer.Networking
} }
public class AccountInfo public class AccountInfo
{ {
public uint Id; public uint Id;
public string Login; public string Login;
public bool IsLockedToIP; public bool IsLockedToIP;
@@ -0,0 +1,27 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Networking;
using System.Net.Sockets;
namespace BNetServer.Networking
{
public class SessionManager : SocketManager<Session>
{
public static SessionManager Instance { get; } = new SessionManager();
public override bool StartNetwork(string bindIp, int port, int threadCount = 1)
{
if (!base.StartNetwork(bindIp, port, threadCount))
return false;
Acceptor.AsyncAcceptSocket(OnSocketAccept);
return true;
}
static void OnSocketAccept(Socket sock)
{
Global.SessionMgr.OnSocketOpen(sock);
}
}
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Constants;
using Framework.Cryptography;
using Framework.Database;
using Framework.Networking.Http;
using System;
using System.Linq;
using System.Net.Sockets;
namespace BNetServer.REST
{
public class LoginSessionState : SessionState
{
public BnetSRP6Base Srp;
}
public class LoginHttpSession : SslSocket<LoginHttpSession>
{
public static string SESSION_ID_COOKIE = "JSESSIONID";
public LoginHttpSession(Socket socket) : base(socket) { }
public override void Start()
{
string ip_address = GetRemoteIpAddress().ToString();
Log.outTrace(LogFilter.Http, $"{GetClientInfo()} Accepted connection");
// Verify that this IP is not in the ip_banned table
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS));
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_IP_INFO);
stmt.AddValue(0, ip_address);
_queryProcessor.AddCallback(DB.Login.AsyncQuery(stmt).WithCallback(CheckIpCallback));
}
async void CheckIpCallback(SQLResult result)
{
if (!result.IsEmpty())
{
bool banned = false;
do
{
if (result.Read<ulong>(0) != 0)
banned = true;
} while (result.NextRow());
if (banned)
{
Log.outDebug(LogFilter.Http, $"{GetClientInfo()} tries to log in using banned IP!");
CloseSocket();
return;
}
}
await AsyncHandshake(Global.LoginServiceMgr.GetCertificate());
}
public override RequestHandlerResult RequestHandler(RequestContext context)
{
return Global.LoginService.GetDispatcherService().HandleRequest(this, context);
}
public override SessionState ObtainSessionState(RequestContext context)
{
SessionState state = null;
var cookieString = context.request.Cookie;
if (!cookieString.IsEmpty())
{
var cookies = cookieString.Split(';', StringSplitOptions.RemoveEmptyEntries);
int eq = 0;
var sessionIdItr = cookies.FirstOrDefault(cookie =>
{
string name = cookie;
eq = cookie.IndexOf('=');
if (eq != -1)
name = cookie.Substring(0, eq);
return name == SESSION_ID_COOKIE;
});
if (!sessionIdItr.IsEmpty())
{
string value = sessionIdItr.Substring(eq + 1);
state = Global.LoginService.GetSessionService().FindAndRefreshSessionState(value, GetRemoteIpAddress());
}
}
if (state == null)
{
state = Global.LoginService.CreateNewSessionState(GetRemoteIpAddress());
string host = context.request.Host;
int port = host.IndexOf(':');
if (port != -1)
host = host.Remove(host.Length - port);
context.response.Cookie = $"{SESSION_ID_COOKIE}={state.Id}; Path=/bnetserver; Domain={host}; Secure; HttpOnly; SameSite=None";
}
return state;
}
public LoginSessionState GetSessionState() { return _state as LoginSessionState; }
}
}
+584
View File
@@ -0,0 +1,584 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Configuration;
using Framework.Constants;
using Framework.Cryptography;
using Framework.Database;
using Framework.Networking.Http;
using Framework.Web;
using Framework.Web.Rest.Login;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace BNetServer.REST
{
public class LoginRESTService : HttpService<LoginHttpSession>
{
public static LoginRESTService Instance { get; } = new LoginRESTService();
FormInputs _formInputs = new();
int _port;
string[] _hostnames = new string[2];
IPAddress[] _addresses = new IPAddress[2];
uint _loginTicketDuration;
LoginRESTService() : base(LogFilter.Http) { }
public override bool StartNetwork(string bindIp, int port, int threadCount = 1)
{
if (!base.StartNetwork(bindIp, port, threadCount))
return false;
RegisterHandler(HttpMethod.Get, "/bnetserver/login/", HandleGetForm);
RegisterHandler(HttpMethod.Get, "/bnetserver/gameAccounts/", HandleGetGameAccounts);
RegisterHandler(HttpMethod.Get, "/bnetserver/portal/", HandleGetPortal);
RegisterHandler(HttpMethod.Post, "/bnetserver/login/", HandlePostLogin, RequestHandlerFlag.DoNotLogRequestContent);
RegisterHandler(HttpMethod.Post, "/bnetserver/login/srp/", HandlePostLoginSrpChallenge);
RegisterHandler(HttpMethod.Post, "/bnetserver/refreshLoginTicket/", HandlePostRefreshLoginTicket);
_port = port;
_hostnames[0] = ConfigMgr.GetDefaultValue("LoginREST.ExternalAddress", "127.0.0.1");
var externalAddress = Dns.GetHostAddresses(_hostnames[0], System.Net.Sockets.AddressFamily.InterNetwork);
if (externalAddress == null || externalAddress.Empty())
{
Log.outError(LogFilter.Network, $"Could not resolve LoginREST.ExternalAddress {_hostnames[0]}");
return false;
}
_addresses[0] = externalAddress[0];
_hostnames[1] = ConfigMgr.GetDefaultValue("LoginREST.LocalAddress", "127.0.0.1");
var localAddress = Dns.GetHostAddresses(_hostnames[1], AddressFamily.InterNetwork);
if (localAddress == null || localAddress.Empty())
{
Log.outError(LogFilter.Http, $"Could not resolve LoginREST.LocalAddress {_hostnames[1]}");
return false;
}
_addresses[1] = localAddress[0];
// set up form inputs
_formInputs.Type = "LOGIN_FORM";
var input = new FormInput();
input.Id = "account_name";
input.Type = "text";
input.Label = "E-mail";
input.MaxLength = 320;
_formInputs.Inputs.Add(input);
input = new FormInput();
input.Id = "password";
input.Type = "password";
input.Label = "Password";
input.MaxLength = 128;
_formInputs.Inputs.Add(input);
input = new FormInput();
input.Id = "log_in_submit";
input.Type = "submit";
input.Label = "Log In";
_formInputs.Inputs.Add(input);
_loginTicketDuration = ConfigMgr.GetDefaultValue("LoginREST.TicketDuration", 3600u);
MigrateLegacyPasswordHashes();
Acceptor.AsyncAcceptSocket(OnSocketAccept);
return true;
}
public string GetHostnameForClient(IPAddress address)
{
if (IPAddress.IsLoopback(address))
return _hostnames[1];
return _hostnames[0];
}
string ExtractAuthorization(HttpHeader request)
{
if (request.Authorization.IsEmpty())
return null;
string BASIC_PREFIX = "Basic ";
string authorization = request.Authorization;
if (authorization.StartsWith(BASIC_PREFIX))
authorization = authorization.Remove(0, BASIC_PREFIX.Length);
byte[] decoded = Convert.FromBase64String(authorization);
if (decoded.Empty())
return null;
string decodedHeader = Encoding.UTF8.GetString(decoded);
int ticketEnd = decodedHeader.IndexOf(':');
if (ticketEnd != -1)
decodedHeader = decodedHeader.Remove(decodedHeader.Length - ticketEnd);
return decodedHeader;
}
RequestHandlerResult HandleGetForm(LoginHttpSession session, RequestContext context)
{
FormInputs form = _formInputs;
form.SrpUrl = $"https://{GetHostnameForClient(session.GetRemoteIpAddress())}:{_port}/bnetserver/login/srp/";
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(form);
return RequestHandlerResult.Handled;
}
RequestHandlerResult HandleGetGameAccounts(LoginHttpSession session, RequestContext context)
{
string ticket = ExtractAuthorization(context.request);
if (ticket.IsEmpty())
return dispatcherService.HandleUnauthorized(session, context);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_GAME_ACCOUNT_LIST);
stmt.AddValue(0, ticket);
session.QueueQuery(DB.Login.AsyncQuery(stmt).WithCallback(result =>
{
GameAccountList gameAccounts = new();
if (!result.IsEmpty())
{
string formatDisplayName(string name)
{
var hashPos = name.IndexOf('#');
if (hashPos != -1)
return "WoW" + name.Substring(++hashPos);
else
return name;
};
long now = Time.UnixTime;
do
{
GameAccountInfo gameAccount = new();
gameAccount.DisplayName = formatDisplayName(result.Read<string>(0));
gameAccount.Expansion = result.Read<byte>(1);
if (!result.IsNull(2))
{
uint banDate = result.Read<uint>(2);
uint unbanDate = result.Read<uint>(3);
gameAccount.IsSuspended = unbanDate > now;
gameAccount.IsBanned = banDate == unbanDate;
gameAccount.SuspensionReason = result.Read<string>(4);
gameAccount.SuspensionExpires = unbanDate;
}
gameAccounts.GameAccounts.Add(gameAccount);
} while (result.NextRow());
}
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(gameAccounts);
session.SendResponse(context);
}));
return RequestHandlerResult.Async;
}
RequestHandlerResult HandleGetPortal(LoginHttpSession session, RequestContext context)
{
context.response.ContentType = "text/plain";
context.response.Content = $"{session.GetRemoteIpAddress()}:{ConfigMgr.GetDefaultValue("BattlenetPort", 1119)}";
return RequestHandlerResult.Handled;
}
RequestHandlerResult HandlePostLogin(LoginHttpSession session, RequestContext context)
{
LoginForm loginForm = JsonSerializer.Deserialize<LoginForm>(context.request.Content);
if (loginForm == null)
{
LoginResult loginResult = new();
loginResult.AuthenticationState = AuthenticationState.LOGIN.ToString();
loginResult.ErrorCode = "UNABLE_TO_DECODE";
loginResult.ErrorMessage = "There was an internal error while connecting to Battle.net. Please try again later.";
context.response.Status = HttpStatusCode.BadRequest;
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(loginResult);
session.SendResponse(context);
return RequestHandlerResult.Handled;
}
string getInputValue(LoginForm loginForm, string inputId)
{
for (int i = 0; i < loginForm.Inputs.Count; ++i)
if (loginForm.Inputs[i].Id == inputId)
return loginForm.Inputs[i].Value;
return "";
};
string login = getInputValue(loginForm, "account_name").ToUpper();
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_AUTHENTICATION);
stmt.AddValue(0, login);
session.QueueQuery(DB.Login.AsyncQuery(stmt).WithChainingCallback((callback, result) =>
{
if (result.IsEmpty())
{
LoginResult loginResult = new();
loginResult.AuthenticationState = AuthenticationState.DONE.ToString();
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(loginResult);
session.SendResponse(context);
return;
}
string login = getInputValue(loginForm, "account_name").ToUpper();
bool passwordCorrect = false;
string serverM2 = null;
uint accountId = result.Read<uint>(0);
if (session.GetSessionState().Srp == null)
{
SrpVersion version = (SrpVersion)result.Read<sbyte>(1);
string srpUsername = SHA256.HashData(Encoding.UTF8.GetBytes(login)).ToHexString();
byte[] s = result.Read<byte[]>(2);
byte[] v = result.Read<byte[]>(3);
session.GetSessionState().Srp = CreateSrpImplementation(version, SrpHashFunction.Sha256, srpUsername, s, v);
string password = getInputValue(loginForm, "password");
if (version == SrpVersion.v1)
password = password.ToUpper();
passwordCorrect = session.GetSessionState().Srp.CheckCredentials(srpUsername, password);
}
else
{
BigInteger A = new(getInputValue(loginForm, "public_A").ToByteArray(), true, true);
BigInteger M1 = new(getInputValue(loginForm, "client_evidence_M1").ToByteArray(), true, true);
var sessionKey = session.GetSessionState().Srp.VerifyClientEvidence(A, M1);
if (sessionKey.HasValue)
{
passwordCorrect = true;
serverM2 = session.GetSessionState().Srp.CalculateServerEvidence(A, M1, sessionKey.Value).ToByteArray(true, true).ToHexString();
}
}
uint failedLogins = result.Read<uint>(4);
string loginTicket = result.Read<string>(5);
uint loginTicketExpiry = result.Read<uint>(6);
bool isBanned = result.Read<ulong>(7) != 0;
if (!passwordCorrect)
{
if (!isBanned)
{
string ip_address = session.GetRemoteIpAddress().ToString();
uint maxWrongPassword = ConfigMgr.GetDefaultValue("WrongPass.MaxCount", 0u);
if (ConfigMgr.GetDefaultValue("WrongPass.Logging", false))
Log.outDebug(LogFilter.Http, $"[{ip_address}, Account {login}, Id {accountId}] Attempted to connect with wrong password!");
if (maxWrongPassword != 0)
{
SQLTransaction trans = new();
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_FAILED_LOGINS);
stmt.AddValue(0, accountId);
trans.Append(stmt);
++failedLogins;
Log.outDebug(LogFilter.Http, $"MaxWrongPass : {maxWrongPassword}, failed_login : {accountId}");
if (failedLogins >= maxWrongPassword)
{
BanMode banType = ConfigMgr.GetDefaultValue("WrongPass.BanType", BanMode.IP);
int banTime = ConfigMgr.GetDefaultValue("WrongPass.BanTime", 600);
if (banType == BanMode.Account)
{
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_BNET_ACCOUNT_AUTO_BANNED);
stmt.AddValue(0, accountId);
}
else
{
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_IP_AUTO_BANNED);
stmt.AddValue(0, ip_address);
}
stmt.AddValue(1, banTime);
trans.Append(stmt);
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_RESET_FAILED_LOGINS);
stmt.AddValue(0, accountId);
trans.Append(stmt);
}
DB.Login.CommitTransaction(trans);
}
}
LoginResult loginResult = new();
loginResult.AuthenticationState = AuthenticationState.DONE.ToString();
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(loginResult);
session.SendResponse(context);
return;
}
if (loginTicket.IsEmpty() || loginTicketExpiry < Time.UnixTime)
loginTicket = "TC-" + RandomHelper.GetRandomBytes(20).ToHexString();
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_AUTHENTICATION);
stmt.AddValue(0, loginTicket);
stmt.AddValue(1, Time.UnixTime + _loginTicketDuration);
stmt.AddValue(2, accountId);
callback.WithCallback(_ =>
{
LoginResult loginResult = new();
loginResult.AuthenticationState = AuthenticationState.DONE.ToString();
loginResult.LoginTicket = loginTicket;
if (!serverM2.IsEmpty())
loginResult.ServerEvidenceM2 = serverM2;
context.response.ContentType = "application/json;charset=utf-8";
JsonSerializerOptions options = new JsonSerializerOptions();
options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
context.response.Content = JsonSerializer.Serialize(loginResult);
session.SendResponse(context);
}).SetNextQuery(DB.Login.AsyncQuery(stmt));
}));
return RequestHandlerResult.Async;
}
RequestHandlerResult HandlePostLoginSrpChallenge(LoginHttpSession session, RequestContext context)
{
LoginForm loginForm = JsonSerializer.Deserialize<LoginForm>(context.request.Content);
if (loginForm == null)
{
LoginResult loginResult = new();
loginResult.AuthenticationState = AuthenticationState.LOGIN.ToString();
loginResult.ErrorCode = "UNABLE_TO_DECODE";
loginResult.ErrorMessage = "There was an internal error while connecting to Battle.net. Please try again later.";
context.response.Status = HttpStatusCode.BadRequest;
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(loginResult);
session.SendResponse(context);
return RequestHandlerResult.Handled;
}
string login = "";
for (int i = 0; i < loginForm.Inputs.Count; ++i)
if (loginForm.Inputs[i].Id == "account_name")
login = loginForm.Inputs[i].Value;
login = login.ToUpper();
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD_BY_EMAIL);
stmt.AddValue(0, login);
session.QueueQuery(DB.Login.AsyncQuery(stmt).WithCallback(result =>
{
if (result.IsEmpty())
{
LoginResult loginResult = new();
loginResult.AuthenticationState = AuthenticationState.DONE.ToString();
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(loginResult);
session.SendResponse(context);
return;
}
SrpVersion version = (SrpVersion)result.Read<byte>(0);
SrpHashFunction hashFunction = SrpHashFunction.Sha256;
string srpUsername = SHA256.HashData(Encoding.UTF8.GetBytes(login)).ToHexString();
byte[] s = result.Read<byte[]>(1);
byte[] v = result.Read<byte[]>(2);
session.GetSessionState().Srp = CreateSrpImplementation(version, hashFunction, srpUsername, s, v);
if (session.GetSessionState().Srp == null)
{
context.response.Status = HttpStatusCode.InternalServerError;
session.SendResponse(context);
return;
}
SrpLoginChallenge challenge = new();
challenge.Version = session.GetSessionState().Srp.GetVersion();
challenge.Iterations = (int)session.GetSessionState().Srp.GetXIterations();
challenge.Modulus = session.GetSessionState().Srp.GetN().ToByteArray(true, true).ToHexString();
challenge.Generator = session.GetSessionState().Srp.Getg().ToByteArray(true, true).ToHexString();
string hash = "";
switch (hashFunction)
{
case SrpHashFunction.Sha256:
hash = "SHA-256";
break;
case SrpHashFunction.Sha512:
hash = "SHA-512";
break;
}
challenge.HashFunction = hash;
challenge.Username = srpUsername;
challenge.Salt = session.GetSessionState().Srp.s.ToHexString();
challenge.PublicB = session.GetSessionState().Srp.B.ToByteArray(true, true).ToHexString();
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(challenge);
session.SendResponse(context);
}));
return RequestHandlerResult.Async;
}
RequestHandlerResult HandlePostRefreshLoginTicket(LoginHttpSession session, RequestContext context)
{
string ticket = ExtractAuthorization(context.request);
if (ticket.IsEmpty())
return dispatcherService.HandleUnauthorized(session, context);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_EXISTING_AUTHENTICATION);
stmt.AddValue(0, ticket);
session.QueueQuery(DB.Login.AsyncQuery(stmt).WithCallback(result =>
{
LoginRefreshResult loginRefreshResult = new();
if (!result.IsEmpty())
{
uint loginTicketExpiry = result.Read<uint>(0);
long now = Time.UnixTime;
if (loginTicketExpiry > now)
{
loginRefreshResult.LoginTicketExpiry = (now + _loginTicketDuration);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_EXISTING_AUTHENTICATION);
stmt.AddValue(0, (uint)(now + _loginTicketDuration));
stmt.AddValue(1, ticket);
DB.Login.Execute(stmt);
}
else
loginRefreshResult.IsExpired = true;
}
else
loginRefreshResult.IsExpired = true;
context.response.ContentType = "application/json;charset=utf-8";
context.response.Content = JsonSerializer.Serialize(loginRefreshResult);
session.SendResponse(context);
}));
return RequestHandlerResult.Async;
}
public BnetSRP6Base CreateSrpImplementation(SrpVersion version, SrpHashFunction hashFunction, string username, byte[] salt, byte[] verifier)
{
if (version == SrpVersion.v2)
{
if (hashFunction == SrpHashFunction.Sha256)
return new BnetSRP6v2Hash256(username, salt, verifier);
if (hashFunction == SrpHashFunction.Sha512)
return new BnetSRP6v2Hash512(username, salt, verifier);
}
if (version == SrpVersion.v1)
{
if (hashFunction == SrpHashFunction.Sha256)
return new BnetSRP6v1Hash256(username, salt, verifier);
if (hashFunction == SrpHashFunction.Sha512)
return new BnetSRP6v1Hash512(username, salt, verifier);
}
return null;
}
public override SessionState CreateNewSessionState(IPAddress address)
{
var state = new LoginSessionState();
sessionService.InitAndStoreSessionState(state, address);
return state;
}
public static void OnSocketAccept(Socket sock)
{
Global.LoginService.OnSocketOpen(sock);
}
void MigrateLegacyPasswordHashes()
{
if (DB.Login.Query("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'battlenet_accounts' AND COLUMN_NAME = 'sha_pass_hash'").IsEmpty())
return;
Log.outInfo(_logger, "Updating password hashes...");
uint start = Time.GetMSTime();
// the auth update query nulls salt/verifier if they cannot be converted
// if they are non-null but s/v have been cleared, that means a legacy tool touched our auth DB (otherwise, the core might've done it itself, it used to use those hacks too)
SQLResult result = DB.Login.Query("SELECT id, sha_pass_hash, IF((salt IS null) OR (verifier IS null), 0, 1) AS shouldWarn FROM battlenet_accounts WHERE sha_pass_hash != DEFAULT(sha_pass_hash) OR salt IS NULL OR verifier IS NULL");
if (result.IsEmpty())
{
Log.outInfo(_logger, $"No password hashes to update - this took us {Time.GetMSTimeDiffToNow(start)} ms to realize");
return;
}
bool hadWarning = false;
uint count = 0;
SQLTransaction tx = new SQLTransaction();
do
{
uint id = result.Read<uint>(0);
byte[] salt = RandomHelper.GetRandomBytes(SRP6.SaltLength);
BigInteger x = new BigInteger(SHA256.HashData(salt.Combine(result.Read<string>(1).ToByteArray(true))), true);
byte[] verifier = BigInteger.ModPow(BnetSRP6v1Base.g, x, BnetSRP6v1Base.N).ToByteArray(true, true);
if (result.Read<long>(2) != 0)
{
if (!hadWarning)
{
hadWarning = true;
Log.outWarn(_logger,
" ========\n" +
"(!) You appear to be using an outdated external account management tool.\n" +
"(!) Update your external tool.\n" +
"(!!) If no update is available, refer your tool's developer to https://github.com/TrinityCore/TrinityCore/issues/25157.\n" +
" ========");
}
}
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_LOGON);
stmt.AddValue(0, (sbyte)SrpVersion.v1);
stmt.AddValue(1, salt);
stmt.AddValue(2, verifier);
stmt.AddValue(3, id);
tx.Append(stmt);
tx.Append($"UPDATE battlenet_accounts SET sha_pass_hash = DEFAULT(sha_pass_hash) WHERE id = {id}");
if (tx.GetSize() >= 10000)
{
DB.Login.CommitTransaction(tx);
tx = new SQLTransaction();
}
++count;
} while (result.NextRow());
DB.Login.CommitTransaction(tx);
Log.outInfo(_logger, $"{count} password hashes updated in {Time.GetMSTimeDiffToNow(start)} ms");
}
public int GetPort() { return _port; }
}
}
+21 -21
View File
@@ -27,36 +27,36 @@ namespace BNetServer
if (!StartDB()) if (!StartDB())
ExitNow(); ExitNow();
string bindIp = ConfigMgr.GetDefaultValue("BindIP", "0.0.0.0"); string httpBindIp = ConfigMgr.GetDefaultValue("BindIP", "0.0.0.0");
int httpPort = ConfigMgr.GetDefaultValue("LoginREST.Port", 8081);
var restSocketServer = new SocketManager<RestSession>(); if (httpPort <= 0 || httpPort > 0xFFFF)
int restPort = ConfigMgr.GetDefaultValue("LoginREST.Port", 8081);
if (restPort < 0 || restPort > 0xFFFF)
{ {
Log.outError(LogFilter.Network, $"Specified login service port ({restPort}) out of allowed range (1-65535), defaulting to 8081"); Log.outError(LogFilter.Server, $"Specified login service port ({httpPort}) out of allowed range (1-65535)");
restPort = 8081;
}
if (!restSocketServer.StartNetwork(bindIp, restPort))
{
Log.outError(LogFilter.Server, "Failed to initialize Rest Socket Server");
ExitNow(); ExitNow();
} }
// Get the list of realms for the server if (!Global.LoginService.StartNetwork(httpBindIp, httpPort))
Global.RealmMgr.Initialize(ConfigMgr.GetDefaultValue("RealmsStateUpdateDelay", 10)); {
Global.LoginServiceMgr.Initialize(); Log.outError(LogFilter.Server, "Failed to initialize login service");
ExitNow();
}
var sessionSocketServer = new SocketManager<Session>();
// Start the listening port (acceptor) for auth connections // Start the listening port (acceptor) for auth connections
int bnPort = ConfigMgr.GetDefaultValue("BattlenetPort", 1119); int bnPort = ConfigMgr.GetDefaultValue("BattlenetPort", 1119);
if (bnPort < 0 || bnPort > 0xFFFF) if (bnPort <= 0 || bnPort > 0xFFFF)
{ {
Log.outError(LogFilter.Server, $"Specified battle.net port ({bnPort}) out of allowed range (1-65535)"); Log.outError(LogFilter.Server, $"Specified battle.net port ({bnPort}) out of allowed range (1-65535)");
ExitNow(); ExitNow();
} }
if (!sessionSocketServer.StartNetwork(bindIp, bnPort)) // Get the list of realms for the server
Global.RealmMgr.Initialize(ConfigMgr.GetDefaultValue("RealmsStateUpdateDelay", 10));
Global.LoginServiceMgr.Initialize();
string bindIp = ConfigMgr.GetDefaultValue("BindIP", "0.0.0.0");
if (!Global.SessionMgr.StartNetwork(bindIp, bnPort))
{ {
Log.outError(LogFilter.Network, "Failed to start BnetServer Network"); Log.outError(LogFilter.Network, "Failed to start BnetServer Network");
ExitNow(); ExitNow();
@@ -89,9 +89,9 @@ namespace BNetServer
static void BanExpiryCheckTimer_Elapsed(object sender, ElapsedEventArgs e) static void BanExpiryCheckTimer_Elapsed(object sender, ElapsedEventArgs e)
{ {
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DelExpiredIpBans)); DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS));
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.UpdExpiredAccountBans)); DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.UPD_EXPIRED_ACCOUNT_BANS));
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DelBnetExpiredAccountBanned)); DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DEL_BNET_EXPIRED_ACCOUNT_BANNED));
} }
static Timer _banExpiryCheckTimer; static Timer _banExpiryCheckTimer;
@@ -133,4 +133,16 @@ namespace Framework.Constants
NameTakenByThisAccount = 4, NameTakenByThisAccount = 4,
Unknown = 5 Unknown = 5
} }
public enum SrpVersion
{
v1 = 1,
v2 = 2
}
public enum SrpHashFunction
{
Sha256 = 0,
Sha512 = 1
}
} }
@@ -18,4 +18,18 @@ namespace Framework.Constants
ThreadUnsafe, //packet is not thread-safe - process it in World.UpdateSessions() ThreadUnsafe, //packet is not thread-safe - process it in World.UpdateSessions()
ThreadSafe //packet is thread-safe - process it in Map.Update() ThreadSafe //packet is thread-safe - process it in Map.Update()
} }
public enum RequestHandlerFlag
{
None = 0x0,
DoNotLogRequestContent = 0x1,
DoNotLogResponseContent = 0x2,
}
public enum RequestHandlerResult
{
Handled,
Error,
Async,
}
} }
+377 -21
View File
@@ -2,44 +2,400 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System; using System;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
namespace Framework.Cryptography namespace Framework.Cryptography
{ {
public class SRP6 public abstract class SRP6
{ {
static SHA1 _sha1; public static int SaltLength = 32;
static BigInteger _g;
static BigInteger _N;
static SRP6() public byte[] s = new byte[SaltLength]; // s - the user's password salt, random, used to calculate v on registration
protected BigInteger I; // H(I) - the username, all uppercase
protected BigInteger b; // b - randomly chosen by the server, same length as N, never given out
protected BigInteger v; // v - the user's password verifier, derived from s + H(USERNAME || ":" || PASSWORD)
public BigInteger B; // B = k*v + g^b
bool _used; // a single instance can only be used to verify once
public SRP6() { }
public SRP6(BigInteger i, byte[] salt, byte[] verifier, BigInteger N, BigInteger g, BigInteger k)
{ {
_sha1 = SHA1.Create(); s = salt;
_g = new BigInteger(7); I = i;
_N = new BigInteger(new byte[] b = CalculatePrivateB(N);
{ v = new BigInteger(verifier, true);
0x89, 0x4B, 0x64, 0x5E, 0x89, 0xE1, 0x53, 0x5B, 0xBD, 0xAD, 0x5B, 0x8B, 0x29, 0x06, 0x50, 0x53, B = CalculatePublicB(N, g, k);
0x08, 0x01, 0xB1, 0x8E, 0xBF, 0xBF, 0x5E, 0x8F, 0xAB, 0x3C, 0x82, 0x87, 0x2A, 0x3E, 0x9B, 0xB7,
}, true, true);
} }
public static (byte[] Salt, byte[] Verifier) MakeRegistrationData(string username, string password) public BigInteger? VerifyClientEvidence(BigInteger A, BigInteger clientM1)
{ {
var salt = new byte[0].GenerateRandomKey(32); // random salt Cypher.Assert(!_used, "A single SRP6 object must only ever be used to verify ONCE!");
return (salt, CalculateVerifier(username, password, salt)); _used = true;
return DoVerifyClientEvidence(A, clientM1);
} }
public static byte[] CalculateVerifier(string username, string password, byte[] salt) public bool CheckCredentials(string username, string password)
{
return v == new BigInteger(CalculateVerifier(username, password, s), true);
}
BigInteger CalculatePrivateB(BigInteger N)
{
BigInteger b = new BigInteger(RandomHelper.GetRandomBytes((int)N.GetBitLength()), true);
b = b % (N - 1);
return b;
}
BigInteger CalculatePublicB(BigInteger N, BigInteger g, BigInteger k)
{
return (BigInteger.ModPow(g, b, N) + (v * k)) % N;
}
byte[] CalculateVerifier(string username, string password, byte[] salt)
{ {
// v = g ^ H(s || H(u || ':' || p)) mod N // v = g ^ H(s || H(u || ':' || p)) mod N
return BigInteger.ModPow(_g, new BigInteger(_sha1.ComputeHash(salt.Combine(_sha1.ComputeHash(Encoding.UTF8.GetBytes(username.ToUpperInvariant() + ":" + password.ToUpperInvariant())))), true), _N).ToByteArray(); return BigInteger.ModPow(Getg(), CalculateX(username, password, salt), GetN()).ToByteArray();
} }
public static bool CheckLogin(string username, string password, byte[] salt, byte[] verifier) public abstract BigInteger GetN();
public abstract BigInteger Getg();
public abstract BigInteger CalculateServerEvidence(BigInteger A, BigInteger clientM1, BigInteger K);
public static (byte[], byte[]) MakeAccountRegistrationData<T>(string username, string password) where T : new()
{ {
return verifier.Compare(CalculateVerifier(username, password, salt)); GruntSRP6 impl = new();
return (impl.s, impl.CalculateVerifier(username, password, impl.s));
}
public static (byte[], byte[]) MakeBNetRegistrationData<T>(string username, string password) where T : new()
{
BnetSRP6v2Hash256 impl = new();
return (impl.s, impl.CalculateVerifier(username, password, impl.s));
}
public abstract BigInteger CalculateX(string username, string password, byte[] salt);
public abstract BigInteger? DoVerifyClientEvidence(BigInteger A, BigInteger clientM1);
}
public class GruntSRP6 : SRP6
{
static BigInteger N;// the modulus, an algorithm parameter; all operations are mod this
static BigInteger g;// a [g]enerator for the ring of integers mod N, algorithm parameter
static SHA1 _sha1 = SHA1.Create();
static GruntSRP6()
{
N = new BigInteger("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7".ToByteArray(), true, true);
g = new BigInteger(7u);
}
public GruntSRP6() : base() { }
public GruntSRP6(string username, byte[] salt, byte[] verifier) : base(new BigInteger(_sha1.ComputeHash(Encoding.UTF8.GetBytes(username)), true, true), salt, verifier, N, g, 3) { }
public override BigInteger CalculateServerEvidence(BigInteger A, BigInteger clientM1, BigInteger K)
{
return new BigInteger(_sha1.ComputeHash(A.ToByteArray().Combine(clientM1.ToByteArray().Combine(K.ToByteArray()))), true, true);
}
public override BigInteger CalculateX(string username, string password, byte[] salt)
{
return new BigInteger(_sha1.ComputeHash(salt.Combine(_sha1.ComputeHash(Encoding.UTF8.GetBytes(username + ":" + password)))), true, true);
}
public override BigInteger? DoVerifyClientEvidence(BigInteger A, BigInteger clientM1)
{
if ((A % N).IsZero)
return null;
BigInteger u = new(_sha1.ComputeHash(A.ToByteArray().Combine(B.ToByteArray())), false, true);
byte[] S = BigInteger.ModPow(A * BigInteger.ModPow(v, u, N), b, N).ToByteArray();
byte[] K = SHA1Interleave(S);
// NgHash = H(N) xor H(g)
byte[] NHash = _sha1.ComputeHash(N.ToByteArray());
byte[] gHash = _sha1.ComputeHash(g.ToByteArray());
byte[] NgHash = NHash.Select((x, i) => (byte)(x ^ gHash[i])).ToArray();
BigInteger ourM = new BigInteger(_sha1.ComputeHash(NgHash.Combine(I.ToByteArray().Combine(s.Combine(A.ToByteArray().Combine(B.ToByteArray().Combine(K)))))), true, true);
if (ourM == clientM1)
return new BigInteger(K, true, true);
return null;
}
byte[] SHA1Interleave(byte[] S)
{
// split S into two buffers
byte[] buf0 = new byte[32 / 2];
byte[] buf1 = new byte[32 / 2];
for (int i = 0; i < S.Length / 2; ++i)
{
buf0[i] = S[2 * i + 0];
buf1[i] = S[2 * i + 1];
}
// find position of first nonzero byte
int p = 0;
while (p < S.Length && S[p] == 0)
++p;
if ((p & 1) != 0)
++p; // skip one extra byte if p is odd
p /= 2; // offset into buffers
// hash each of the halves, starting at the first nonzero byte
byte[] hash0 = _sha1.ComputeHash(buf0[p..].Combine(S[(S.Length / 2 - p)..]));
byte[] hash1 = _sha1.ComputeHash(buf1[p..].Combine(S[(S.Length / 2 - p)..]));
// stick the two hashes back together
byte[] K = new byte[SHA1.HashSizeInBytes * 2];
for (int i = 0; i < SHA1.HashSizeInBytes; ++i)
{
K[2 * i + 0] = hash0[i];
K[2 * i + 1] = hash1[i];
}
return K;
}
public override BigInteger GetN() { return N; }
public override BigInteger Getg() { return g; }
}
public abstract class BnetSRP6Base : SRP6
{
protected static SHA256 _sha256 = SHA256.Create();
protected static SHA512 _sha512 = SHA512.Create();
public BnetSRP6Base() : base() { }
public BnetSRP6Base(BigInteger i, byte[] salt, byte[] verifier, BigInteger N, BigInteger g, BigInteger k) : base(i, salt, verifier, N, g, k) { }
public override BigInteger CalculateServerEvidence(BigInteger A, BigInteger clientM1, BigInteger K)
{
return DoCalculateEvidence(A, clientM1, K);
}
public override BigInteger? DoVerifyClientEvidence(BigInteger A, BigInteger clientM1)
{
BigInteger N = GetN();
if ((A % N).IsZero)
return null;
BigInteger u = CalculateU(A);
if ((u % N).IsZero)
return null;
BigInteger S = BigInteger.ModPow(A * BigInteger.ModPow(v, u, N), b, N);
BigInteger ourM = DoCalculateEvidence(A, B, S);
if (ourM != clientM1)
return null;
return S;
}
byte[] GetBrokenEvidenceVector(BigInteger bn)
{
int bytes = (int)(bn.GetBitLength() + 8) >> 3;
var byteArray = bn.ToByteArray(true, true);
return new byte[bytes - byteArray.Length].Combine(byteArray);
}
public abstract byte GetVersion();
public abstract uint GetXIterations();
public virtual BigInteger CalculateU(BigInteger A) { return default; }
public virtual BigInteger DoCalculateEvidence(params BigInteger[] bns) { return default; }
public BigInteger DoCalculateEvidenceHash256(params BigInteger[] bns)
{
for (var i = 0; i < bns.Length; ++i)
{
var bytes = GetBrokenEvidenceVector(bns[i]);
if (i == bns.Length - 1)
{
_sha256.TransformFinalBlock(bytes, 0, bytes.Length);
break;
}
_sha256.TransformBlock(bytes, 0, bytes.Length, null, 0);
}
return new BigInteger(_sha256.Hash, true, true);
}
public BigInteger DoCalculateEvidenceHash512(params BigInteger[] bns)
{
for (var i = 0; i < bns.Length; ++i)
{
var bytes = GetBrokenEvidenceVector(bns[i]);
if (i == bns.Length - 1)
{
_sha512.TransformFinalBlock(bytes, 0, bytes.Length);
break;
}
_sha512.TransformBlock(bytes, 0, bytes.Length, null, 0);
}
return new BigInteger(_sha512.Hash, true, true);
} }
} }
}
public class BnetSRP6v1Base : BnetSRP6Base
{
/// <summary>
/// // the modulus, an algorithm parameter; all operations are mod this
/// </summary>
public static BigInteger N;
/// <summary>
/// // a [g]enerator for the ring of integers mod N, algorithm parameter
/// </summary>
public static BigInteger g;
static BnetSRP6v1Base()
{
g = new BigInteger(2u);
N = new BigInteger("86A7F6DEEB306CE519770FE37D556F29944132554DED0BD68205E27F3231FEF5A10108238A3150C59CAF7B0B6478691C13A6ACF5E1B5ADAFD4A943D4A21A142B800E8A55F8BFBAC700EB77A7235EE5A609E350EA9FC19F10D921C2FA832E4461B7125D38D254A0BE873DFC27858ACB3F8B9F258461E4373BC3A6C2A9634324AB".ToByteArray(), true, true);
}
public BnetSRP6v1Base() : base() { }
public BnetSRP6v1Base(string username, byte[] salt, byte[] verifier, BigInteger k) : base(new BigInteger(SHA256.HashData(Encoding.UTF8.GetBytes(username)), true), salt, verifier, N, g, k) { }
public override BigInteger CalculateX(string username, string password, byte[] salt)
{
return new BigInteger(SHA256.HashData(salt.Combine(SHA256.HashData(Encoding.UTF8.GetBytes(username + ":" + password)))), true);
}
public override BigInteger GetN() { return N; }
public override BigInteger Getg() { return g; }
public override byte GetVersion() { return 1; }
public override uint GetXIterations() { return 1; }
}
public class BnetSRP6v2Base : BnetSRP6Base
{
/// <summary>
/// // the modulus, an algorithm parameter; all operations are mod this
/// </summary>
protected static BigInteger N;
/// <summary>
/// // a [g]enerator for the ring of integers mod N, algorithm parameter
/// </summary>
protected static BigInteger g;
static BnetSRP6v2Base()
{
N = new BigInteger("AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73".ToByteArray(), true, true);
g = new BigInteger(2u);
}
public BnetSRP6v2Base() : base() { }
public BnetSRP6v2Base(string username, byte[] salt, byte[] verifier, BigInteger k) : base(new BigInteger(SHA256.HashData(Encoding.UTF8.GetBytes(username)), true), salt, verifier, N, g, k) { }
public override BigInteger CalculateX(string username, string password, byte[] salt)
{
string tmp = username + ":" + password;
Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(tmp, salt, (int)GetXIterations(), HashAlgorithmName.SHA512);
byte[] xBytes = rfc.GetBytes(SHA512.HashSizeInBytes);
BigInteger x = new(xBytes, true, true);
if ((xBytes[0] & 0x80) != 0)
{
byte[] fix = new byte[65];
fix[64] = 1;
x -= new BigInteger(fix, true);
}
return x % (N - 1);
}
public override BigInteger GetN() { return N; }
public override BigInteger Getg() { return g; }
public override byte GetVersion() { return 2; }
public override uint GetXIterations() { return 15000; }
}
public class BnetSRP6v1Hash256 : BnetSRP6v1Base
{
static byte[] dummyBytes = new byte[127];
public BnetSRP6v1Hash256() : base() { }
public BnetSRP6v1Hash256(string username, byte[] salt, byte[] verifier) : base(username, salt, verifier, new BigInteger(SHA256.HashData(N.ToByteArray(true, true).Combine(dummyBytes.Combine(g.ToByteArray(true, true)))), true, true)) { }
public override BigInteger CalculateU(BigInteger A)
{
return new BigInteger(SHA256.HashData(A.ToByteArray(true, true).Combine(B.ToByteArray(true, true))), true, true);
}
public override BigInteger DoCalculateEvidence(params BigInteger[] bns)
{
return DoCalculateEvidenceHash256(bns);
}
}
public class BnetSRP6v1Hash512 : BnetSRP6v1Base
{
public BnetSRP6v1Hash512() : base() { }
public BnetSRP6v1Hash512(string username, byte[] salt, byte[] verifier) : base(username, salt, verifier, new BigInteger(SHA512.HashData(N.ToByteArray(true, true).Combine(g.ToByteArray(true, true))), true, true)) { }
public override BigInteger CalculateU(BigInteger A)
{
return new BigInteger(_sha512.ComputeHash(A.ToByteArray(true, true).Combine(B.ToByteArray(true, true))), true, true);
}
public override BigInteger DoCalculateEvidence(params BigInteger[] bns)
{
return DoCalculateEvidenceHash512(bns);
}
}
public class BnetSRP6v2Hash256 : BnetSRP6v2Base
{
public BnetSRP6v2Hash256() : base() { }
public BnetSRP6v2Hash256(string username, byte[] salt, byte[] verifier) : base(username, salt, verifier, new BigInteger(_sha256.ComputeHash(N.ToByteArray(true, false).Combine(g.ToByteArray(true, false))), true)) { }
public override BigInteger CalculateU(BigInteger A)
{
return new BigInteger(_sha256.ComputeHash(A.ToByteArray(true, true).Combine(B.ToByteArray(true, true))), true, true);
}
public override BigInteger DoCalculateEvidence(params BigInteger[] bns)
{
return DoCalculateEvidenceHash256(bns);
}
}
public class BnetSRP6v2Hash512 : BnetSRP6v2Base
{
public BnetSRP6v2Hash512() : base() { }
public BnetSRP6v2Hash512(string username, byte[] salt, byte[] verifier) : base(username, salt, verifier, new BigInteger(_sha512.ComputeHash(N.ToByteArray(true, true).Combine(g.ToByteArray(true, true))), true, true)) { }
public override BigInteger CalculateU(BigInteger A)
{
return new BigInteger(_sha512.ComputeHash(A.ToByteArray(true, true).Combine(B.ToByteArray(true, true))), true, true);
}
public override BigInteger DoCalculateEvidence(params BigInteger[] bns)
{
return DoCalculateEvidenceHash512(bns);
}
}
}
@@ -7,15 +7,14 @@ namespace Framework.Database
{ {
public override void PreparedStatements() public override void PreparedStatements()
{ {
const string BnetAccountInfo = "ba.id, ba.email, ba.locked, ba.lock_country, ba.last_ip, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, bab.unbandate = bab.bandate"; const string BnetAccountInfo = "ba.id, UPPER(ba.email), ba.locked, ba.lock_country, ba.last_ip, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, bab.unbandate = bab.bandate";
const string BnetGameAccountInfo = "a.id, a.username, ab.unbandate, ab.unbandate = ab.bandate, aa.SecurityLevel"; const string BnetGameAccountInfo = "a.id, a.username, ab.unbandate, ab.unbandate = ab.bandate, aa.SecurityLevel";
PrepareStatement(LoginStatements.SEL_REALMLIST, "SELECT id, name, address, localAddress, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild, Region, Battlegroup FROM realmlist WHERE flag <> 3 ORDER BY name"); PrepareStatement(LoginStatements.SEL_REALMLIST, "SELECT id, name, address, localAddress, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild, Region, Battlegroup FROM realmlist WHERE flag <> 3 ORDER BY name");
PrepareStatement(LoginStatements.DelExpiredIpBans, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()"); PrepareStatement(LoginStatements.DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()");
PrepareStatement(LoginStatements.UpdExpiredAccountBans, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()"); PrepareStatement(LoginStatements.UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()");
PrepareStatement(LoginStatements.SelIpInfo, "SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?"); PrepareStatement(LoginStatements.SEL_IP_INFO, "SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?");
PrepareStatement(LoginStatements.INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban')");
PrepareStatement(LoginStatements.InsIpAutoBanned, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Cypher Auth', 'Failed login autoban')");
PrepareStatement(LoginStatements.SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate"); PrepareStatement(LoginStatements.SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate");
PrepareStatement(LoginStatements.SEL_IP_BANNED_BY_IP, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) AND ip LIKE CONCAT('%%', ?, '%%') ORDER BY unbandate"); PrepareStatement(LoginStatements.SEL_IP_BANNED_BY_IP, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) AND ip LIKE CONCAT('%%', ?, '%%') ORDER BY unbandate");
PrepareStatement(LoginStatements.SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id"); PrepareStatement(LoginStatements.SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id");
@@ -27,12 +26,11 @@ namespace Framework.Database
PrepareStatement(LoginStatements.UPD_LOGON, "UPDATE account SET salt = ?, verifier = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_LOGON, "UPDATE account SET salt = ?, verifier = ? WHERE id = ?");
PrepareStatement(LoginStatements.SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?"); PrepareStatement(LoginStatements.SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?");
PrepareStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?"); PrepareStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?");
PrepareStatement(LoginStatements.SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key_bnet, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.SecurityLevel, " + PrepareStatement(LoginStatements.SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key_bnet, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, a.timezone_offset, ba.id, aa.SecurityLevel, " +
"bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id " + "bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id " +
"FROM account a LEFT JOIN account r ON a.id = r.recruiter LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id " + "FROM account a LEFT JOIN account r ON a.id = r.recruiter LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id " +
"LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 " + "LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 " +
"WHERE a.username = ? AND LENGTH(a.session_key_bnet) = 64 ORDER BY aa.RealmID DESC LIMIT 1"); "WHERE a.username = ? AND LENGTH(a.session_key_bnet) = 64 ORDER BY aa.RealmID DESC LIMIT 1");
PrepareStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?"); PrepareStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?");
PrepareStatement(LoginStatements.SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?"); PrepareStatement(LoginStatements.SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?");
PrepareStatement(LoginStatements.SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?"); PrepareStatement(LoginStatements.SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?");
@@ -43,7 +41,7 @@ namespace Framework.Database
PrepareStatement(LoginStatements.DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?"); PrepareStatement(LoginStatements.DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?");
PrepareStatement(LoginStatements.REP_REALM_CHARACTERS, "REPLACE INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)"); PrepareStatement(LoginStatements.REP_REALM_CHARACTERS, "REPLACE INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)");
PrepareStatement(LoginStatements.SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?"); PrepareStatement(LoginStatements.SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?");
PrepareStatement(LoginStatements.INS_ACCOUNT, "INSERT INTO account (username, salt, verifier, reg_mail, email, joindate, battlenet_account, battlenet_index) VALUES(?, ?, ?, ?, ?, NOW(), ?, ?)"); PrepareStatement(LoginStatements.INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, reg_mail, email, joindate, battlenet_account, battlenet_index) VALUES(?, ?, ?, ?, ?, NOW(), ?, ?)");
PrepareStatement(LoginStatements.INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid = account.id WHERE acctid IS NULL"); PrepareStatement(LoginStatements.INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid = account.id WHERE acctid IS NULL");
PrepareStatement(LoginStatements.UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?");
PrepareStatement(LoginStatements.UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?");
@@ -80,14 +78,14 @@ namespace Framework.Database
PrepareStatement(LoginStatements.DEL_ACCOUNT, "DELETE FROM account WHERE id = ?"); PrepareStatement(LoginStatements.DEL_ACCOUNT, "DELETE FROM account WHERE id = ?");
PrepareStatement(LoginStatements.SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1"); PrepareStatement(LoginStatements.SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1");
PrepareStatement(LoginStatements.GET_EMAIL_BY_ID, "SELECT email FROM account WHERE id = ?"); PrepareStatement(LoginStatements.GET_EMAIL_BY_ID, "SELECT email FROM account WHERE id = ?");
// 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_AccountLoginDeLete_IP_Logging" // 0: uint32, 1: uint32, 2: uint32, 3: uint8, 4: uint32, 5: string // Complete name: "Insert_AccountLoginDeLete_IP_Logging"
PrepareStatement(LoginStatements.INS_ALDL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())"); PrepareStatement(LoginStatements.INS_ALDL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id, character_guid, realm_id, type, ip, systemnote, unixtime, time) VALUES (?, ?, ?, ?, (SELECT last_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())");
// 0: uint32, 1: uint32, 2: uint8, 3: uint32, 4: string // Complete name: "Login_Insert_FailedAccountLogin_IP_Logging" // 0: uint32, 1: uint32, 2: uint32, 3: uint8, 4: uint32, 5: string // Complete name: "Insert_FailedAccountIP_Logging"
PrepareStatement(LoginStatements.INS_FACL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, (SELECT last_attempt_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())"); PrepareStatement(LoginStatements.INS_FACL_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id, character_guid, realm_id, type, ip, systemnote, unixtime, time) VALUES (?, ?, ?, ?, (SELECT last_attempt_ip FROM account WHERE id = ?), ?, unix_timestamp(NOW()), NOW())");
// 0: uint32, 1: uint32, 2: uint8, 3: string, 4: string // Complete name: "Login_Insert_CharacterDelete_IP_Logging" // 0: uint32, 1: uint32, 2: uint32, 3: uint8, 4: string, 5: string // Complete name: "Insert_CharacterDelete_IP_Logging"
PrepareStatement(LoginStatements.INS_CHAR_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, ?, ?, ?, ?, unix_timestamp(NOW()), NOW())"); PrepareStatement(LoginStatements.INS_CHAR_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id, character_guid, realm_id, type, ip, systemnote, unixtime, time) VALUES (?, ?, ?, ?, ?, ?, unix_timestamp(NOW()), NOW())");
// 0: string, 1: string, 2: string // Complete name: "Login_Insert_Failed_Account_Login_due_password_IP_Logging" // 0: uint32, 1: string, 2: string // Complete name: "Insert_Failed_Account_due_password_IP_Logging"
PrepareStatement(LoginStatements.INS_FALP_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id,character_guid,type,ip,systemnote,unixtime,time) VALUES (?, 0, 1, ?, ?, unix_timestamp(NOW()), NOW())"); PrepareStatement(LoginStatements.INS_FALP_IP_LOGGING, "INSERT INTO logs_ip_actions (account_id, character_guid, realm_id, type, ip, systemnote, unixtime, time) VALUES (?, 0, 0, 1, ?, ?, unix_timestamp(NOW()), NOW())");
PrepareStatement(LoginStatements.SEL_ACCOUNT_ACCESS_BY_ID, "SELECT SecurityLevel, RealmID FROM account_access WHERE AccountID = ? and (RealmID = ? OR RealmID = -1) ORDER BY SecurityLevel desc"); PrepareStatement(LoginStatements.SEL_ACCOUNT_ACCESS_BY_ID, "SELECT SecurityLevel, RealmID FROM account_access WHERE AccountID = ? and (RealmID = ? OR RealmID = -1) ORDER BY SecurityLevel desc");
PrepareStatement(LoginStatements.SEL_RBAC_ACCOUNT_PERMISSIONS, "SELECT permissionId, granted FROM rbac_account_permissions WHERE accountId = ? AND (realmId = ? OR realmId = -1) ORDER BY permissionId, realmId"); PrepareStatement(LoginStatements.SEL_RBAC_ACCOUNT_PERMISSIONS, "SELECT permissionId, granted FROM rbac_account_permissions WHERE accountId = ? AND (realmId = ? OR realmId = -1) ORDER BY permissionId, realmId");
@@ -98,35 +96,46 @@ namespace Framework.Database
PrepareStatement(LoginStatements.SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC"); PrepareStatement(LoginStatements.SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC");
PrepareStatement(LoginStatements.DEL_ACCOUNT_MUTED, "DELETE FROM account_muted WHERE guid = ?"); PrepareStatement(LoginStatements.DEL_ACCOUNT_MUTED, "DELETE FROM account_muted WHERE guid = ?");
PrepareStatement(LoginStatements.SelBnetAuthentication, "SELECT ba.id, ba.sha_pass_hash, ba.failed_logins, ba.LoginTicket, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id WHERE email = ?"); PrepareStatement(LoginStatements.SEL_SECRET_DIGEST, "SELECT digest FROM secret_digest WHERE id = ?");
PrepareStatement(LoginStatements.UpdBnetAuthentication, "UPDATE battlenet_accounts SET LoginTicket = ?, LoginTicketExpiry = ? WHERE id = ?"); PrepareStatement(LoginStatements.INS_SECRET_DIGEST, "INSERT INTO secret_digest (id, digest) VALUES (?,?)");
PrepareStatement(LoginStatements.DEL_SECRET_DIGEST, "DELETE FROM secret_digest WHERE id = ?");
PrepareStatement(LoginStatements.SEL_ACCOUNT_TOTP_SECRET, "SELECT totp_secret FROM account WHERE id = ?");
PrepareStatement(LoginStatements.UPD_ACCOUNT_TOTP_SECRET, "UPDATE account SET totp_secret = ? WHERE id = ?");
PrepareStatement(LoginStatements.SEL_BNET_AUTHENTICATION, "SELECT ba.id, ba.srp_version, COALESCE(ba.salt, 0x0000000000000000000000000000000000000000000000000000000000000000), ba.verifier, ba.failed_logins, ba.LoginTicket, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id WHERE email = ?");
PrepareStatement(LoginStatements.UPD_BNET_AUTHENTICATION, "UPDATE battlenet_accounts SET LoginTicket = ?, LoginTicketExpiry = ? WHERE id = ?");
PrepareStatement(LoginStatements.SEL_BNET_EXISTING_AUTHENTICATION, "SELECT LoginTicketExpiry FROM battlenet_accounts WHERE LoginTicket = ?");
PrepareStatement(LoginStatements.SEL_BNET_EXISTING_AUTHENTICATION_BY_ID, "SELECT LoginTicket FROM battlenet_accounts WHERE id = ?"); PrepareStatement(LoginStatements.SEL_BNET_EXISTING_AUTHENTICATION_BY_ID, "SELECT LoginTicket FROM battlenet_accounts WHERE id = ?");
PrepareStatement(LoginStatements.SelBnetAccountInfo, "SELECT " + BnetAccountInfo + ", " + BnetGameAccountInfo + PrepareStatement(LoginStatements.UPD_BNET_EXISTING_AUTHENTICATION, "UPDATE battlenet_accounts SET LoginTicketExpiry = ? WHERE LoginTicket = ?");
" FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account" + PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_INFO, $"SELECT {BnetAccountInfo}, {BnetGameAccountInfo}" +
" FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account" +
" LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID = -1 WHERE ba.LoginTicket = ? ORDER BY a.id"); " LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID = -1 WHERE ba.LoginTicket = ? ORDER BY a.id");
PrepareStatement(LoginStatements.UpdBnetLastLoginInfo, "UPDATE battlenet_accounts SET last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_LAST_LOGIN_INFO, "UPDATE battlenet_accounts SET last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE id = ?");
PrepareStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET session_key_bnet = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?"); PrepareStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET session_key_bnet = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ?, timezone_offset = ? WHERE username = ?");
PrepareStatement(LoginStatements.SelBnetCharacterCountsByAccountId, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id WHERE rc.acctid = ?"); PrepareStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id WHERE rc.acctid = ?");
PrepareStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id LEFT JOIN account a ON rc.acctid = a.id WHERE a.battlenet_account = ?"); PrepareStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, "SELECT rc.acctid, rc.numchars, r.id, r.Region, r.Battlegroup FROM realmcharacters rc INNER JOIN realmlist r ON rc.realmid = r.id LEFT JOIN account a ON rc.acctid = a.id WHERE a.battlenet_account = ?");
PrepareStatement(LoginStatements.SelBnetLastPlayerCharacters, "SELECT lpc.accountId, lpc.region, lpc.battlegroup, lpc.realmId, lpc.characterName, lpc.characterGUID, lpc.lastPlayedTime FROM account_last_played_character lpc LEFT JOIN account a ON lpc.accountId = a.id WHERE a.battlenet_account = ?"); PrepareStatement(LoginStatements.SEL_BNET_LAST_PLAYER_CHARACTERS, "SELECT lpc.accountId, lpc.region, lpc.battlegroup, lpc.realmId, lpc.characterName, lpc.characterGUID, lpc.lastPlayedTime FROM account_last_played_character lpc LEFT JOIN account a ON lpc.accountId = a.id WHERE a.battlenet_account = ?");
PrepareStatement(LoginStatements.DEL_BNET_LAST_PLAYER_CHARACTERS, "DELETE FROM account_last_played_character WHERE accountId = ? AND region = ? AND battlegroup = ?"); PrepareStatement(LoginStatements.DEL_BNET_LAST_PLAYER_CHARACTERS, "DELETE FROM account_last_played_character WHERE accountId = ? AND region = ? AND battlegroup = ?");
PrepareStatement(LoginStatements.INS_BNET_LAST_PLAYER_CHARACTERS, "INSERT INTO account_last_played_character (accountId, region, battlegroup, realmId, characterName, characterGUID, lastPlayedTime) VALUES (?,?,?,?,?,?,?)"); PrepareStatement(LoginStatements.INS_BNET_LAST_PLAYER_CHARACTERS, "INSERT INTO account_last_played_character (accountId, region, battlegroup, realmId, characterName, characterGUID, lastPlayedTime) VALUES (?,?,?,?,?,?,?)");
PrepareStatement(LoginStatements.INS_BNET_ACCOUNT, "INSERT INTO battlenet_accounts (`email`,`sha_pass_hash`) VALUES (?, ?)"); PrepareStatement(LoginStatements.INS_BNET_ACCOUNT, "INSERT INTO battlenet_accounts (`email`,`srp_version`,`salt`,`verifier`) VALUES (?, ?, ?, ?)");
PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_EMAIL_BY_ID, "SELECT email FROM battlenet_accounts WHERE id = ?"); PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_EMAIL_BY_ID, "SELECT email FROM battlenet_accounts WHERE id = ?");
PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_EMAIL, "SELECT id FROM battlenet_accounts WHERE email = ?"); PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_EMAIL, "SELECT id FROM battlenet_accounts WHERE email = ?");
PrepareStatement(LoginStatements.UPD_BNET_PASSWORD, "UPDATE battlenet_accounts SET sha_pass_hash = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_LOGON, "UPDATE battlenet_accounts SET srp_version = ?, salt = ?, verifier = ? WHERE id = ?");
PrepareStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD, "SELECT 1 FROM battlenet_accounts WHERE id = ? AND sha_pass_hash = ?"); PrepareStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD, "SELECT srp_version, COALESCE(salt, 0x0000000000000000000000000000000000000000000000000000000000000000), verifier FROM battlenet_accounts WHERE id = ?");
PrepareStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD_BY_EMAIL, "SELECT srp_version, COALESCE(salt, 0x0000000000000000000000000000000000000000000000000000000000000000), verifier FROM battlenet_accounts WHERE email = ?");
PrepareStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK, "UPDATE battlenet_accounts SET locked = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK, "UPDATE battlenet_accounts SET locked = ? WHERE id = ?");
PrepareStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK_CONTRY, "UPDATE battlenet_accounts SET lock_country = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_ACCOUNT_LOCK_CONTRY, "UPDATE battlenet_accounts SET lock_country = ? WHERE id = ?");
PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT, "SELECT battlenet_account FROM account WHERE id = ?"); PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT, "SELECT battlenet_account FROM account WHERE id = ?");
PrepareStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LINK, "UPDATE account SET battlenet_account = ?, battlenet_index = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LINK, "UPDATE account SET battlenet_account = ?, battlenet_index = ? WHERE id = ?");
PrepareStatement(LoginStatements.SEL_BNET_MAX_ACCOUNT_INDEX, "SELECT MAX(battlenet_index) FROM account WHERE battlenet_account = ?"); PrepareStatement(LoginStatements.SEL_BNET_MAX_ACCOUNT_INDEX, "SELECT MAX(battlenet_index) FROM account WHERE battlenet_account = ?");
PrepareStatement(LoginStatements.SEL_BNET_GAME_ACCOUNT_LIST, "SELECT a.id, a.username FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id WHERE ba.email = ?"); PrepareStatement(LoginStatements.SEL_BNET_GAME_ACCOUNT_LIST_SMALL, "SELECT a.id, a.username FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id WHERE ba.email = ?");
PrepareStatement(LoginStatements.SEL_BNET_GAME_ACCOUNT_LIST, "SELECT a.username, a.expansion, ab.bandate, ab.unbandate, ab.banreason FROM account AS a LEFT JOIN account_banned AS ab ON a.id = ab.id AND ab.active = 1 INNER JOIN battlenet_accounts AS ba ON a.battlenet_account = ba.id WHERE ba.LoginTicket = ? ORDER BY a.id");
PrepareStatement(LoginStatements.UpdBnetFailedLogins, "UPDATE battlenet_accounts SET failed_logins = failed_logins + 1 WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_FAILED_LOGINS, "UPDATE battlenet_accounts SET failed_logins = failed_logins + 1 WHERE id = ?");
PrepareStatement(LoginStatements.InsBnetAccountAutoBanned, "INSERT INTO battlenet_account_bans(id, bandate, unbandate, bannedby, banreason) VALUES(?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban')"); PrepareStatement(LoginStatements.INS_BNET_ACCOUNT_AUTO_BANNED, "INSERT INTO battlenet_account_bans(id, bandate, unbandate, bannedby, banreason) VALUES(?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban')");
PrepareStatement(LoginStatements.DelBnetExpiredAccountBanned, "DELETE FROM battlenet_account_bans WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()"); PrepareStatement(LoginStatements.DEL_BNET_EXPIRED_ACCOUNT_BANNED, "DELETE FROM battlenet_account_bans WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()");
PrepareStatement(LoginStatements.UpdBnetResetFailedLogins, "UPDATE battlenet_accounts SET failed_logins = 0 WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_RESET_FAILED_LOGINS, "UPDATE battlenet_accounts SET failed_logins = 0 WHERE id = ?");
PrepareStatement(LoginStatements.SEL_LAST_CHAR_UNDELETE, "SELECT LastCharacterUndelete FROM battlenet_accounts WHERE Id = ?"); PrepareStatement(LoginStatements.SEL_LAST_CHAR_UNDELETE, "SELECT LastCharacterUndelete FROM battlenet_accounts WHERE Id = ?");
PrepareStatement(LoginStatements.UPD_LAST_CHAR_UNDELETE, "UPDATE battlenet_accounts SET LastCharacterUndelete = UNIX_TIMESTAMP() WHERE Id = ?"); PrepareStatement(LoginStatements.UPD_LAST_CHAR_UNDELETE, "UPDATE battlenet_accounts SET LastCharacterUndelete = UNIX_TIMESTAMP() WHERE Id = ?");
@@ -157,22 +166,24 @@ namespace Framework.Database
// Transmog collection // Transmog collection
PrepareStatement(LoginStatements.SEL_BNET_ITEM_APPEARANCES, "SELECT blobIndex, appearanceMask FROM battlenet_item_appearances WHERE battlenetAccountId = ? ORDER BY blobIndex DESC"); PrepareStatement(LoginStatements.SEL_BNET_ITEM_APPEARANCES, "SELECT blobIndex, appearanceMask FROM battlenet_item_appearances WHERE battlenetAccountId = ? ORDER BY blobIndex DESC");
PrepareStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES, "INSERT INTO battlenet_item_appearances (battlenetAccountId, blobIndex, appearanceMask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE appearanceMask = appearanceMask | VALUES(appearanceMask)"); PrepareStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES, "INSERT INTO battlenet_item_appearances (battlenetAccountId, blobIndex, appearanceMask) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE appearanceMask = appearanceMask | VALUES(appearanceMask)");
PrepareStatement(LoginStatements.SEL_BNET_ITEM_FAVORITE_APPEARANCES, "SELECT itemModifiedAppearanceId FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ?"); PrepareStatement(LoginStatements.SEL_BNET_ITEM_FAVORITE_APPEARANCES, "SELECT itemModifiedAppearanceId FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ?");
PrepareStatement(LoginStatements.INS_BNET_ITEM_FAVORITE_APPEARANCE, "INSERT INTO battlenet_item_favorite_appearances (battlenetAccountId, itemModifiedAppearanceId) VALUES (?, ?)"); PrepareStatement(LoginStatements.INS_BNET_ITEM_FAVORITE_APPEARANCE, "INSERT INTO battlenet_item_favorite_appearances (battlenetAccountId, itemModifiedAppearanceId) VALUES (?, ?)");
PrepareStatement(LoginStatements.DEL_BNET_ITEM_FAVORITE_APPEARANCE, "DELETE FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ? AND itemModifiedAppearanceId = ?"); PrepareStatement(LoginStatements.DEL_BNET_ITEM_FAVORITE_APPEARANCE, "DELETE FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ? AND itemModifiedAppearanceId = ?");
PrepareStatement(LoginStatements.SEL_BNET_TRANSMOG_ILLUSIONS, "SELECT blobIndex, illusionMask FROM battlenet_account_transmog_illusions WHERE battlenetAccountId = ? ORDER BY blobIndex DESC"); PrepareStatement(LoginStatements.SEL_BNET_TRANSMOG_ILLUSIONS, "SELECT blobIndex, illusionMask FROM battlenet_account_transmog_illusions WHERE battlenetAccountId = ? ORDER BY blobIndex DESC");
PrepareStatement(LoginStatements.INS_BNET_TRANSMOG_ILLUSIONS, "INSERT INTO battlenet_account_transmog_illusions (battlenetAccountId, blobIndex, illusionMask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE illusionMask = illusionMask | VALUES(illusionMask)"); PrepareStatement(LoginStatements.INS_BNET_TRANSMOG_ILLUSIONS, "INSERT INTO battlenet_account_transmog_illusions (battlenetAccountId, blobIndex, illusionMask) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE illusionMask = illusionMask | VALUES(illusionMask)");
} }
} }
public enum LoginStatements public enum LoginStatements
{ {
SEL_REALMLIST, SEL_REALMLIST,
DelExpiredIpBans, DEL_EXPIRED_IP_BANS,
UpdExpiredAccountBans, UPD_EXPIRED_ACCOUNT_BANS,
SelIpInfo, SEL_IP_INFO,
InsIpAutoBanned, INS_IP_AUTO_BANNED,
SEL_ACCOUNT_BANNED_ALL, SEL_ACCOUNT_BANNED_ALL,
SEL_ACCOUNT_BANNED_BY_FILTER, SEL_ACCOUNT_BANNED_BY_FILTER,
SEL_ACCOUNT_BANNED_BY_USERNAME, SEL_ACCOUNT_BANNED_BY_USERNAME,
@@ -225,12 +236,9 @@ namespace Framework.Database
SEL_ACCOUNT_INFO, SEL_ACCOUNT_INFO,
SEL_ACCOUNT_ACCESS_SECLEVEL_TEST, SEL_ACCOUNT_ACCESS_SECLEVEL_TEST,
SEL_ACCOUNT_ACCESS, SEL_ACCOUNT_ACCESS,
SEL_ACCOUNT_RECRUITER,
SEL_BANS,
SEL_ACCOUNT_WHOIS, SEL_ACCOUNT_WHOIS,
SEL_REALMLIST_SECURITY_LEVEL, SEL_REALMLIST_SECURITY_LEVEL,
DEL_ACCOUNT, DEL_ACCOUNT,
SEL_IP2NATION_COUNTRY,
SEL_AUTOBROADCAST, SEL_AUTOBROADCAST,
SEL_LAST_ATTEMPT_IP, SEL_LAST_ATTEMPT_IP,
SEL_LAST_IP, SEL_LAST_IP,
@@ -249,34 +257,44 @@ namespace Framework.Database
SEL_ACCOUNT_MUTE_INFO, SEL_ACCOUNT_MUTE_INFO,
DEL_ACCOUNT_MUTED, DEL_ACCOUNT_MUTED,
SelBnetAuthentication, SEL_SECRET_DIGEST,
UpdBnetAuthentication, INS_SECRET_DIGEST,
DEL_SECRET_DIGEST,
SEL_ACCOUNT_TOTP_SECRET,
UPD_ACCOUNT_TOTP_SECRET,
SEL_BNET_AUTHENTICATION,
UPD_BNET_AUTHENTICATION,
SEL_BNET_EXISTING_AUTHENTICATION,
SEL_BNET_EXISTING_AUTHENTICATION_BY_ID, SEL_BNET_EXISTING_AUTHENTICATION_BY_ID,
SelBnetAccountInfo, UPD_BNET_EXISTING_AUTHENTICATION,
UpdBnetLastLoginInfo, SEL_BNET_ACCOUNT_INFO,
UPD_BNET_LAST_LOGIN_INFO,
UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, UPD_BNET_GAME_ACCOUNT_LOGIN_INFO,
SelBnetCharacterCountsByAccountId, SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID,
SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID, SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID,
SelBnetLastPlayerCharacters, SEL_BNET_LAST_PLAYER_CHARACTERS,
DEL_BNET_LAST_PLAYER_CHARACTERS, DEL_BNET_LAST_PLAYER_CHARACTERS,
INS_BNET_LAST_PLAYER_CHARACTERS, INS_BNET_LAST_PLAYER_CHARACTERS,
INS_BNET_ACCOUNT, INS_BNET_ACCOUNT,
SEL_BNET_ACCOUNT_EMAIL_BY_ID, SEL_BNET_ACCOUNT_EMAIL_BY_ID,
SEL_BNET_ACCOUNT_ID_BY_EMAIL, SEL_BNET_ACCOUNT_ID_BY_EMAIL,
UPD_BNET_PASSWORD, UPD_BNET_LOGON,
SEL_BNET_ACCOUNT_SALT_BY_ID,
SEL_BNET_CHECK_PASSWORD, SEL_BNET_CHECK_PASSWORD,
SEL_BNET_CHECK_PASSWORD_BY_EMAIL,
UPD_BNET_ACCOUNT_LOCK, UPD_BNET_ACCOUNT_LOCK,
UPD_BNET_ACCOUNT_LOCK_CONTRY, UPD_BNET_ACCOUNT_LOCK_CONTRY,
SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT, SEL_BNET_ACCOUNT_ID_BY_GAME_ACCOUNT,
UPD_BNET_GAME_ACCOUNT_LINK, UPD_BNET_GAME_ACCOUNT_LINK,
SEL_BNET_MAX_ACCOUNT_INDEX, SEL_BNET_MAX_ACCOUNT_INDEX,
SEL_BNET_GAME_ACCOUNT_LIST_SMALL,
SEL_BNET_GAME_ACCOUNT_LIST, SEL_BNET_GAME_ACCOUNT_LIST,
UpdBnetFailedLogins, UPD_BNET_FAILED_LOGINS,
InsBnetAccountAutoBanned, INS_BNET_ACCOUNT_AUTO_BANNED,
DelBnetExpiredAccountBanned, DEL_BNET_EXPIRED_ACCOUNT_BANNED,
UpdBnetResetFailedLogins, UPD_BNET_RESET_FAILED_LOGINS,
SEL_LAST_CHAR_UNDELETE, SEL_LAST_CHAR_UNDELETE,
UPD_LAST_CHAR_UNDELETE, UPD_LAST_CHAR_UNDELETE,
@@ -30,6 +30,8 @@ namespace Framework.Database
{ {
commands.Add(new MySqlCommand(string.Format(sql, args))); commands.Add(new MySqlCommand(string.Format(sql, args)));
} }
public int GetSize() { return commands.Count; }
} }
class TransactionTask : ISqlOperation class TransactionTask : ISqlOperation
+2 -1
View File
@@ -382,7 +382,6 @@ public enum LogLevel
public enum LogFilter public enum LogFilter
{ {
Misc,
Achievement, Achievement,
Addon, Addon,
Ahbot, Ahbot,
@@ -404,11 +403,13 @@ public enum LogFilter
Garrison, Garrison,
Gameevent, Gameevent,
Guild, Guild,
Http,
Instance, Instance,
Lfg, Lfg,
Loot, Loot,
MapsScript, MapsScript,
Maps, Maps,
Misc,
Movement, Movement,
Network, Network,
Outdoorpvp, Outdoorpvp,
+1 -1
View File
@@ -64,7 +64,7 @@ namespace Framework.Networking
if (socket != null) if (socket != null)
{ {
T newSocket = (T)Activator.CreateInstance(typeof(T), socket); T newSocket = (T)Activator.CreateInstance(typeof(T), socket);
newSocket.Accept(); newSocket.Start();
if (!_closed) if (!_closed)
AsyncAccept<T>(); AsyncAccept<T>();
@@ -0,0 +1,118 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Constants;
using Framework.Database;
using Framework.Web;
using System;
using System.Net.Sockets;
namespace Framework.Networking.Http
{
public interface IAbstractSocket : ISocket
{
public void SendResponse(RequestContext context);
public void QueueQuery(QueryCallback queryCallback);
public string GetClientInfo();
public Guid? GetSessionId();
}
public abstract class BaseSocket<Derived> : SSLSocket, IAbstractSocket
{
public BaseSocket(Socket socket) : base(socket) { }
public async override void ReadHandler(byte[] data, int receivedLength)
{
if (!IsOpen())
return;
if (!HttpHelper.ParseRequest(data, receivedLength, out var httpRequest))
{
CloseSocket();
return;
}
if (!HandleMessage(httpRequest))
{
CloseSocket();
return;
}
await AsyncRead();
}
bool HandleMessage(HttpHeader httpRequest)
{
RequestContext context = new() { request = httpRequest };
if (_state == null)
_state = ObtainSessionState(context);
RequestHandlerResult status = RequestHandler(context);
if (status != RequestHandlerResult.Async)
SendResponse(context);
return status != RequestHandlerResult.Error;
}
public virtual RequestHandlerResult RequestHandler(RequestContext context) { return 0; }
public async void SendResponse(RequestContext context)
{
Log.outDebug(LogFilter.Http, $"{GetClientInfo()} Request {context.request.Method} {context.request.Path} done, status {context.response.Status}");
{
bool canLogRequestContent = context.handler == null || !context.handler.Flags.HasFlag(RequestHandlerFlag.DoNotLogRequestContent);
bool canLogResponseContent = context.handler == null || !context.handler.Flags.HasFlag(RequestHandlerFlag.DoNotLogResponseContent);
Log.outTrace(LogFilter.Http, $"{GetClientInfo()} Request: {(canLogRequestContent ? context.request.Content : "<REDACTED>")}");
Log.outTrace(LogFilter.Http, $"{GetClientInfo()} Response: {(canLogResponseContent ? context.response.Content : "<REDACTED>")}");
}
await AsyncWrite(HttpHelper.CreateResponse(context));
if (!context.response.KeepAlive)
CloseSocket();
}
public void QueueQuery(QueryCallback queryCallback)
{
_queryProcessor.AddCallback(queryCallback);
}
public override bool Update()
{
if (!base.Update())
return false;
this._queryProcessor.ProcessReadyCallbacks();
return true;
}
public string GetClientInfo()
{
string info = GetRemoteIpAddress().ToString();
if (_state != null)
info += $", Session Id: {_state.Id}";
info += "]";
return info;
}
public Guid? GetSessionId()
{
if (_state != null)
return _state.Id;
return null;
}
public virtual SessionState ObtainSessionState(RequestContext context) { return null; }
protected AsyncCallbackProcessor<QueryCallback> _queryProcessor = new();
protected SessionState _state;
}
}
@@ -0,0 +1,321 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Constants;
using Framework.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
namespace Framework.Networking.Http
{
public class DispatcherService
{
public DispatcherService(LogFilter logFilter)
{
_logger = logFilter;
}
public RequestHandlerResult HandleRequest(IAbstractSocket session, RequestContext context)
{
Log.outDebug(_logger, $"{session.GetClientInfo()} Starting request {context.request.Method} {context.request.Path}");
string path = new Func<string>(() =>
{
string path = context.request.Path;
int queryIndex = path.IndexOf('?');
if (queryIndex != -1)
path = path.Substring(0, queryIndex);
return path;
})();
context.handler = new Func<RequestHandler>(() =>
{
switch (context.request.Method)
{
case "GET":
case "HEAD":
return _getHandlers.LookupByKey(path);
case "POST":
return _postHandlers.LookupByKey(path);
default:
return null;
}
})();
DateTime responseDate = DateTime.Now;
//context.response.Date = responseDate - Timezone.GetSystemZoneOffsetAt(responseDate);
//context.response.Server.Add(BOOST_BEAST_VERSION_STRING);
context.response.KeepAlive = context.request.KeepAlive;
if (context.handler == null)
return HandlePathNotFound(session, context);
return context.handler.Func(session, context);
}
RequestHandlerResult HandleBadRequest(IAbstractSocket session, RequestContext context)
{
context.response.Status = HttpStatusCode.BadRequest;
return RequestHandlerResult.Handled;
}
public RequestHandlerResult HandleUnauthorized(IAbstractSocket session, RequestContext context)
{
context.response.Status = HttpStatusCode.Unauthorized;
return RequestHandlerResult.Handled;
}
RequestHandlerResult HandlePathNotFound(IAbstractSocket session, RequestContext context)
{
context.response.Status = HttpStatusCode.NotFound;
return RequestHandlerResult.Handled;
}
public void RegisterHandler(HttpMethod method, string path, Func<IAbstractSocket, RequestContext, RequestHandlerResult> handler, RequestHandlerFlag flags = RequestHandlerFlag.None)
{
var handlerMap = new Func<Dictionary<string, RequestHandler>>(() =>
{
switch (method.Method)
{
case "GET":
return _getHandlers;
case "POST":
return _postHandlers;
default:
//ABORT_MSG($"Tried to register a handler for unsupported HTTP method {method}");
return null;
}
})();
handlerMap[path] = new RequestHandler() { Func = handler, Flags = flags };
Log.outInfo(_logger, $"Registered new handler for {method} {path}");
}
Dictionary<string, RequestHandler> _getHandlers = new();
Dictionary<string, RequestHandler> _postHandlers = new();
LogFilter _logger;
}
public class SessionService
{
public SessionService(LogFilter logFilter)
{
_logger = logFilter;
}
public void InitAndStoreSessionState(SessionState state, IPAddress address)
{
state.RemoteAddress = address;
// Generate session id
lock (_sessionsMutex)
{
while (state.Id == Guid.Empty || _sessions.ContainsKey(state.Id))
state.Id = new(new byte[0].GenerateRandomKey(16));
Log.outDebug(_logger, $"Client at {address} created new session {state.Id}");
_sessions[state.Id] = state;
}
}
public void Start()
{
_inactiveSessionsKillTimer = new System.Timers.Timer(TimeSpan.FromMinutes(1));
_inactiveSessionsKillTimer.Elapsed += (_, _) => KillInactiveSessions();
_inactiveSessionsKillTimer.Start();
}
public void Stop()
{
_inactiveSessionsKillTimer = null;
lock (_sessionsMutex)
_sessions.Clear();
lock (_inactiveSessionsMutex)
_inactiveSessions.Clear();
}
public SessionState FindAndRefreshSessionState(string id, IPAddress address)
{
SessionState state;
lock (_sessionsMutex)
{
if (!_sessions.TryGetValue(Guid.Parse(id), out state))
{
Log.outDebug(_logger, $"Client at {address} attempted to use a session {id} that was expired");
return null; // no session
}
}
if (!state.RemoteAddress.Equals(address))
{
Log.outError(_logger, $"Client at {address} attempted to use a session {id} that was last accessed from {state.RemoteAddress}, denied access");
return null;
}
lock (_inactiveSessionsMutex)
{
_inactiveSessions.Remove(state.Id);
}
return state;
}
public void MarkSessionInactive(Guid id)
{
bool wasActive = true;
lock (_inactiveSessionsMutex)
{
wasActive = !_inactiveSessions.Contains(id);
if (wasActive)
_inactiveSessions.Add(id);
}
if (wasActive)
{
lock (_sessionsMutex)
{
var itr = _sessions.LookupByKey(id);
if (itr != null)
{
itr.InactiveTimestamp = DateTime.Now + TimeSpan.FromMinutes(5);
Log.outTrace(_logger, $"Session {id} marked as inactive");
}
}
}
}
void KillInactiveSessions()
{
lock (_inactiveSessionsMutex)
{
List<Guid> inactiveSessions = new(_inactiveSessions);
_inactiveSessions.Clear();
DateTime now = DateTime.Now;
int inactiveSessionsCount = inactiveSessions.Count;
lock (_sessionsMutex)
{
foreach (var guid in inactiveSessions.ToList())
{
if (!_sessions.TryGetValue(guid, out var sessionState) || sessionState.InactiveTimestamp < now)
{
_sessions.Remove(guid);
inactiveSessions.Remove(guid);
}
}
}
Log.outDebug(_logger, $"Killed {inactiveSessionsCount - inactiveSessions.Count} inactive sessions");
// restore sessions not killed to inactive queue
foreach (var guid in inactiveSessions.ToList())
{
_inactiveSessions.Add(guid);
inactiveSessions.Remove(guid);
}
}
}
object _sessionsMutex = new();
Dictionary<Guid, SessionState> _sessions = new();
object _inactiveSessionsMutex = new();
List<Guid> _inactiveSessions = new();
System.Timers.Timer _inactiveSessionsKillTimer;
LogFilter _logger;
}
public class HttpService<SessionImpl> : SocketManager<SessionImpl> where SessionImpl : IAbstractSocket
{
protected DispatcherService dispatcherService;
protected SessionService sessionService;
public HttpService(LogFilter logFilter) : base()
{
dispatcherService = new(logFilter);
sessionService = new(logFilter);
_logger = logFilter;
}
public override bool StartNetwork(string bindIp, int port, int threadCount = 1)
{
if (!base.StartNetwork(bindIp, port, threadCount))
return false;
sessionService.Start();
return true;
}
public override void StopNetwork()
{
sessionService.Stop();
base.StopNetwork();
}
// http handling
public delegate RequestHandlerResult HttpRequestHandler(SessionImpl session, RequestContext context);
public void RegisterHandler(HttpMethod method, string path, HttpRequestHandler handler, RequestHandlerFlag flags = RequestHandlerFlag.None)
{
dispatcherService.RegisterHandler(method, path, (session, context) =>
{
return handler((SessionImpl)session, context);
}, flags);
}
// session tracking
public virtual SessionState CreateNewSessionState(IPAddress address)
{
var state = new SessionState();
sessionService.InitAndStoreSessionState(state, address);
return state;
}
class Thread : NetworkThread<SessionImpl>
{
protected override void SocketRemoved(SessionImpl session)
{
var id = session.GetSessionId();
if (id.HasValue)
_service.MarkSessionInactive(id.Value);
}
public SessionService _service;
}
public NetworkThread<SessionImpl>[] CreateThreads()
{
Thread[] threads = new Thread[GetNetworkThreadCount()];
for (int i = 0; i < GetNetworkThreadCount(); ++i)
threads[i]._service = sessionService;
return threads;
}
public DispatcherService GetDispatcherService() { return dispatcherService; }
public SessionService GetSessionService() { return sessionService; }
protected LogFilter _logger;
}
public class RequestHandler
{
public Func<IAbstractSocket, RequestContext, RequestHandlerResult> Func;
public RequestHandlerFlag Flags;
}
public class RequestContext
{
public HttpHeader request = new();
public HttpHeader response = new();
public RequestHandler handler;
}
}
@@ -0,0 +1,15 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System;
using System.Net;
namespace Framework.Networking.Http
{
public class SessionState
{
public Guid Id;
public IPAddress RemoteAddress;
public DateTime InactiveTimestamp = DateTime.MaxValue;
}
}
@@ -0,0 +1,39 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using Framework.Configuration;
namespace Framework.Networking.Http
{
public class SslSocket<Derived> : BaseSocket<Derived>
{
X509Certificate2 _certificate;
public SslSocket(Socket socket) : base(socket)
{
_certificate = new X509Certificate2(ConfigMgr.GetDefaultValue("CertificatesFile", "./BNetServer.pfx"));
}
public async override void Start()
{
await AsyncHandshake(_certificate);
}
public async override Task HandshakeHandler(Exception exception = null)
{
if (exception != null)
{
Log.outError(LogFilter.Http, $"{GetClientInfo()} SSL Handshake failed {exception.Message}");
CloseSocket();
return;
}
await AsyncRead();
}
}
}
+10 -9
View File
@@ -33,16 +33,16 @@ namespace Framework.Networking
_stream.Dispose(); _stream.Dispose();
} }
public abstract void Accept(); public abstract void Start();
public virtual bool Update() public virtual bool Update()
{ {
return _socket.Connected; return _socket.Connected;
} }
public IPEndPoint GetRemoteIpEndPoint() public IPAddress GetRemoteIpAddress()
{ {
return _remoteEndPoint; return _remoteEndPoint.Address;
} }
public async Task AsyncRead() public async Task AsyncRead()
@@ -63,7 +63,7 @@ namespace Framework.Networking
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.outException(ex); Log.outDebug(LogFilter.Network, ex.Message);
} }
} }
@@ -73,16 +73,17 @@ namespace Framework.Networking
{ {
await _stream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls12, false); await _stream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls12, false);
} }
catch(Exception ex) catch (Exception ex)
{ {
Log.outException(ex); await HandshakeHandler(ex);
CloseSocket();
return; return;
} }
await AsyncRead(); await HandshakeHandler();
} }
public abstract Task HandshakeHandler(Exception ex = null);
public abstract void ReadHandler(byte[] data, int receivedLength); public abstract void ReadHandler(byte[] data, int receivedLength);
public async Task AsyncWrite(byte[] data) public async Task AsyncWrite(byte[] data)
@@ -109,7 +110,7 @@ namespace Framework.Networking
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.outDebug(LogFilter.Network, $"WorldSocket.CloseSocket: {GetRemoteIpEndPoint()} errored when shutting down socket: {ex.Message}"); Log.outDebug(LogFilter.Network, $"WorldSocket.CloseSocket: {GetRemoteIpAddress()} errored when shutting down socket: {ex.Message}");
} }
} }
+2 -2
View File
@@ -9,7 +9,7 @@ namespace Framework.Networking
{ {
public interface ISocket public interface ISocket
{ {
void Accept(); void Start();
bool Update(); bool Update();
bool IsOpen(); bool IsOpen();
void CloseSocket(); void CloseSocket();
@@ -43,7 +43,7 @@ namespace Framework.Networking
_socket.Dispose(); _socket.Dispose();
} }
public abstract void Accept(); public virtual void Start() { }
public virtual bool Update() public virtual bool Update()
{ {
+2 -4
View File
@@ -32,8 +32,6 @@ namespace Framework.Networking
_threads[i].Start(); _threads[i].Start();
} }
Acceptor.AsyncAcceptSocket(OnSocketOpen);
return true; return true;
} }
@@ -64,7 +62,7 @@ namespace Framework.Networking
try try
{ {
TSocketType newSocket = (TSocketType)Activator.CreateInstance(typeof(TSocketType), sock); TSocketType newSocket = (TSocketType)Activator.CreateInstance(typeof(TSocketType), sock);
newSocket.Accept(); newSocket.Start();
_threads[SelectThreadWithMinConnections()].AddSocket(newSocket); _threads[SelectThreadWithMinConnections()].AddSocket(newSocket);
} }
@@ -87,4 +85,4 @@ namespace Framework.Networking
return min; return min;
} }
} }
} }
+24 -11
View File
@@ -3,15 +3,18 @@
using Framework.Constants; using Framework.Constants;
using Framework.Database; using Framework.Database;
using Framework.IO;
using Framework.Realm;
using Framework.Web; using Framework.Web;
using Framework.Serialization; using Framework.Web.Rest.Realmlist;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text;
using System.Text.Json;
using System.Timers; using System.Timers;
using System.Collections.Concurrent;
using Framework.Realm;
public class RealmManager : Singleton<RealmManager> public class RealmManager : Singleton<RealmManager>
{ {
@@ -71,7 +74,7 @@ public class RealmManager : Singleton<RealmManager>
{ {
var oldRealm = _realms.LookupByKey(realm.Id); var oldRealm = _realms.LookupByKey(realm.Id);
if (oldRealm != null && oldRealm == realm) if (oldRealm != null && oldRealm == realm)
return; return;
_realms[realm.Id] = realm; _realms[realm.Id] = realm;
} }
@@ -154,7 +157,7 @@ public class RealmManager : Singleton<RealmManager>
return true; return true;
} }
public RealmBuildInfo GetBuildInfo(uint build) public RealmBuildInfo GetBuildInfo(uint build)
{ {
foreach (var clientBuild in _builds) foreach (var clientBuild in _builds)
if (clientBuild.Build == build) if (clientBuild.Build == build)
@@ -217,7 +220,10 @@ public RealmBuildInfo GetBuildInfo(uint build)
realmEntry.CfgConfigsID = (int)realm.GetConfigId(); realmEntry.CfgConfigsID = (int)realm.GetConfigId();
realmEntry.CfgLanguagesID = 1; realmEntry.CfgLanguagesID = 1;
compressed = Json.Deflate("JamJSONRealmEntry", realmEntry); var jsonData = Encoding.UTF8.GetBytes("JamJSONRealmEntry:" + JsonSerializer.Serialize(realmEntry) + "\0");
var compressedData = ZLib.Compress(jsonData);
compressed = BitConverter.GetBytes(jsonData.Length).Combine(compressedData);
} }
} }
@@ -269,10 +275,13 @@ public RealmBuildInfo GetBuildInfo(uint build)
realmList.Updates.Add(realmListUpdate); realmList.Updates.Add(realmListUpdate);
} }
return Json.Deflate("JSONRealmListUpdates", realmList); var jsonData = Encoding.UTF8.GetBytes("JSONRealmListUpdates:" + JsonSerializer.Serialize(realmList) + "\0");
var compressedData = ZLib.Compress(jsonData);
return BitConverter.GetBytes(jsonData.Length).Combine(compressedData);
} }
public BattlenetRpcErrorCode JoinRealm(uint realmAddress, uint build, IPAddress clientAddress, byte[] clientSecret, Locale locale, string os, string accountName, Bgs.Protocol.GameUtilities.V1.ClientResponse response) public BattlenetRpcErrorCode JoinRealm(uint realmAddress, uint build, IPAddress clientAddress, byte[] clientSecret, Locale locale, string os, TimeSpan timezoneOffset, string accountName, Bgs.Protocol.GameUtilities.V1.ClientResponse response)
{ {
Realm realm = GetRealm(new RealmId(realmAddress)); Realm realm = GetRealm(new RealmId(realmAddress));
if (realm != null) if (realm != null)
@@ -290,17 +299,21 @@ public RealmBuildInfo GetBuildInfo(uint build)
addressFamily.Addresses.Add(address); addressFamily.Addresses.Add(address);
serverAddresses.Families.Add(addressFamily); serverAddresses.Families.Add(addressFamily);
byte[] compressed = Json.Deflate("JSONRealmListServerIPAddresses", serverAddresses); var jsonData = Encoding.UTF8.GetBytes("JSONRealmListServerIPAddresses:" + JsonSerializer.Serialize(serverAddresses) + "\0");
var compressedData = ZLib.Compress(jsonData);
byte[] compressed = BitConverter.GetBytes(jsonData.Length).Combine(compressedData);
byte[] serverSecret = new byte[0].GenerateRandomKey(32); byte[] serverSecret = new byte[0].GenerateRandomKey(32);
byte[] keyData = clientSecret.ToArray().Combine(serverSecret); byte[] keyData = clientSecret.Combine(serverSecret);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LOGIN_INFO); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LOGIN_INFO);
stmt.AddValue(0, keyData); stmt.AddValue(0, keyData);
stmt.AddValue(1, clientAddress.ToString()); stmt.AddValue(1, clientAddress.ToString());
stmt.AddValue(2, (byte)locale); stmt.AddValue(2, (byte)locale);
stmt.AddValue(3, os); stmt.AddValue(3, os);
stmt.AddValue(4, accountName); stmt.AddValue(4, (short)timezoneOffset.TotalMinutes);
stmt.AddValue(5, accountName);
DB.Login.DirectExecute(stmt); DB.Login.DirectExecute(stmt);
Bgs.Protocol.Attribute attribute = new(); Bgs.Protocol.Attribute attribute = new();
-65
View File
@@ -1,65 +0,0 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
namespace Framework.Serialization
{
public class Json
{
public static string CreateString<T>(T dataObject)
{
return Encoding.UTF8.GetString(CreateArray(dataObject));
}
public static byte[] CreateArray<T>(T dataObject)
{
var serializer = new DataContractJsonSerializer(typeof(T));
var stream = new MemoryStream();
serializer.WriteObject(stream, dataObject);
return stream.ToArray();
}
public static T CreateObject<T>(Stream jsonData)
{
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(jsonData);
}
public static T CreateObject<T>(string jsonData, bool split = false)
{
return CreateObject<T>(Encoding.UTF8.GetBytes(split ? jsonData.Split(new[] { ':' }, 2)[1] : jsonData));
}
public static T CreateObject<T>(byte[] jsonData) => CreateObject<T>(new MemoryStream(jsonData));
public static object CreateObject(Stream jsonData, Type type)
{
var serializer = new DataContractJsonSerializer(type);
return serializer.ReadObject(jsonData);
}
public static object CreateObject(string jsonData, Type type, bool split = false)
{
return CreateObject(Encoding.UTF8.GetBytes(split ? jsonData.Split(new[] { ':' }, 2)[1] : jsonData), type);
}
public static object CreateObject(byte[] jsonData, Type type) => CreateObject(new MemoryStream(jsonData), type);
// Used for protobuf json strings.
public static byte[] Deflate<T>(string name, T data)
{
var jsonData = Encoding.UTF8.GetBytes(name + ":" + CreateString(data) + "\0");
var compressedData = IO.ZLib.Compress(jsonData);
return BitConverter.GetBytes(jsonData.Length).Combine(compressedData);
}
}
}
+8 -8
View File
@@ -6,10 +6,10 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Runtime.CompilerServices;
namespace System namespace System
{ {
@@ -29,8 +29,8 @@ namespace System
else else
return byteArray.Aggregate("", (current, b) => current + b.ToString("X2")); return byteArray.Aggregate("", (current, b) => current + b.ToString("X2"));
} }
public static byte[] ToByteArray(this string str) public static byte[] ToByteArray(this string str, bool reverse = false)
{ {
str = str.Replace(" ", String.Empty); str = str.Replace(" ", String.Empty);
@@ -40,7 +40,7 @@ namespace System
string temp = String.Concat(str[i * 2], str[i * 2 + 1]); string temp = String.Concat(str[i * 2], str[i * 2 + 1]);
res[i] = Convert.ToByte(temp, 16); res[i] = Convert.ToByte(temp, 16);
} }
return res; return reverse ? res.Reverse().ToArray() : res;
} }
public static byte[] ToByteArray(this string value, char separator) public static byte[] ToByteArray(this string value, char separator)
@@ -124,7 +124,7 @@ namespace System
public static uint[] SerializeObject<T>(this T obj) public static uint[] SerializeObject<T>(this T obj)
{ {
//if (obj.GetType()<StructLayoutAttribute>() == null) //if (obj.GetType()<StructLayoutAttribute>() == null)
//return null; //return null;
var size = Marshal.SizeOf(typeof(T)); var size = Marshal.SizeOf(typeof(T));
var ptr = Marshal.AllocHGlobal(size); var ptr = Marshal.AllocHGlobal(size);
@@ -233,7 +233,7 @@ namespace System
float invSqrt = 1.0f / MathF.Sqrt(lenSquared); float invSqrt = 1.0f / MathF.Sqrt(lenSquared);
return new Vector3(vector.X * invSqrt, vector.Y * invSqrt, vector.Z * invSqrt); return new Vector3(vector.X * invSqrt, vector.Y * invSqrt, vector.Z * invSqrt);
} }
public static Vector3 directionOrZero(this Vector3 vector) public static Vector3 directionOrZero(this Vector3 vector)
{ {
float mag = vector.LengthSquared(); float mag = vector.LengthSquared();
@@ -282,7 +282,7 @@ namespace System
x = 0.0f; x = 0.0f;
} }
} }
public static Matrix4x4 fromEulerAnglesZYX(float fYAngle, float fPAngle, float fRAngle) public static Matrix4x4 fromEulerAnglesZYX(float fYAngle, float fPAngle, float fRAngle)
{ {
float fCos = MathF.Cos(fYAngle); float fCos = MathF.Cos(fYAngle);
@@ -318,7 +318,7 @@ namespace System
public static string ConvertFormatSyntax(this string str) public static string ConvertFormatSyntax(this string str)
{ {
string pattern = @"(%\W*\d*[a-zA-Z]*)"; string pattern = @"(%\W*\d*[a-zA-Z]*)";
int count = 0; int count = 0;
string result = Regex.Replace(str, pattern, m => string.Concat("{", count++, "}")); string result = Regex.Replace(str, pattern, m => string.Concat("{", count++, "}"));
+7
View File
@@ -84,6 +84,13 @@ public class RandomHelper
rand.NextBytes(buffer); rand.NextBytes(buffer);
} }
public static byte[] GetRandomBytes(int length)
{
byte[] buffer = new byte[length];
rand.NextBytes(buffer);
return buffer;
}
public static T RAND<T>(params T[] args) public static T RAND<T>(params T[] args)
{ {
int randIndex = IRand(0, args.Length - 1); int randIndex = IRand(0, args.Length - 1);
-10
View File
@@ -1,10 +0,0 @@
using System;
namespace Framework.Web.API
{
public class ApiRequest<T>
{
public uint? SearchId { get; set; }
public Func<T, bool> SearchFunc { get; set; }
}
}
+23 -23
View File
@@ -1,10 +1,12 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Networking.Http;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
@@ -15,14 +17,14 @@ namespace Framework.Web
public string Method { get; set; } public string Method { get; set; }
public string Path { get; set; } public string Path { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string Host { get; set; } public string Authorization { get; set; }
public string DeviceId { get; set; }
public string ContentType { get; set; } public string ContentType { get; set; }
public int ContentLength { get; set; } public int ContentLength { get; set; }
public string AcceptLanguage { get; set; } public string Content { get; set; } = "";
public string Accept { get; set; } public HttpStatusCode Status { get; set; } = HttpStatusCode.OK;
public string UserAgent { get; set; } public string Cookie { get; set; }
public string Content { get; set; } public bool KeepAlive { get; set; }
public string Host { get; set; }
} }
public enum HttpCode public enum HttpCode
@@ -35,43 +37,41 @@ namespace Framework.Web
public class HttpHelper public class HttpHelper
{ {
public static byte[] CreateResponse(HttpCode httpCode, string content, bool closeConnection = false) public static byte[] CreateResponse(RequestContext requestContext)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
using (var sw = new StringWriter(sb)) using (var sw = new StringWriter(sb))
{ {
sw.WriteLine($"HTTP/1.1 {(int)httpCode} {httpCode}"); sw.WriteLine($"HTTP/1.1 {(int)requestContext.response.Status} {requestContext.response.Status}");
sw.WriteLine($"Content-Length: {requestContext.response.Content.Length}");
//sw.WriteLine($"Date: {DateTime.Now.ToUniversalTime():r}"); if (!requestContext.response.KeepAlive)
//sw.WriteLine("Server: Arctium-Emulation");
//sw.WriteLine("Retry-After: 600");
sw.WriteLine($"Content-Length: {content.Length}");
//sw.WriteLine("Vary: Accept-Encoding");
if (closeConnection)
sw.WriteLine("Connection: close"); sw.WriteLine("Connection: close");
sw.WriteLine("Content-Type: application/json;charset=UTF-8"); if (!requestContext.response.Cookie.IsEmpty())
sw.WriteLine($"Set-Cookie: {requestContext.response.Cookie}");
sw.WriteLine($"Content-Type: {requestContext.response.ContentType}");
sw.WriteLine(); sw.WriteLine();
sw.WriteLine(content); sw.WriteLine(requestContext.response.Content);
} }
return Encoding.UTF8.GetBytes(sb.ToString()); return Encoding.UTF8.GetBytes(sb.ToString());
} }
public static HttpHeader ParseRequest(byte[] data, int length) public static bool ParseRequest(byte[] data, int length, out HttpHeader httpHeader)
{ {
var headerValues = new Dictionary<string, object>(); var headerValues = new Dictionary<string, object>();
var header = new HttpHeader(); httpHeader = new HttpHeader();
using (var sr = new StreamReader(new MemoryStream(data, 0, length))) using (var sr = new StreamReader(new MemoryStream(data, 0, length)))
{ {
var info = sr.ReadLine().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); var info = sr.ReadLine().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
if (info.Length != 3) if (info.Length != 3)
return null; return false;
headerValues.Add("method", info[0]); headerValues.Add("method", info[0]);
headerValues.Add("path", info[1]); headerValues.Add("path", info[1]);
@@ -113,13 +113,13 @@ namespace Framework.Web
if (headerValues.TryGetValue(f.Name.ToLower(), out val)) if (headerValues.TryGetValue(f.Name.ToLower(), out val))
{ {
if (f.PropertyType == typeof(int)) if (f.PropertyType == typeof(int))
f.SetValue(header, Convert.ChangeType(Convert.ToInt32(val), f.PropertyType)); f.SetValue(httpHeader, Convert.ChangeType(Convert.ToInt32(val), f.PropertyType));
else else
f.SetValue(header, Convert.ChangeType(val, f.PropertyType)); f.SetValue(httpHeader, Convert.ChangeType(val, f.PropertyType));
} }
} }
return header; return true;
} }
} }
} }
@@ -1,27 +0,0 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace Framework.Web
{
[DataContract]
public class LogonData
{
public string this[string inputId] => Inputs.SingleOrDefault(i => i.Id == inputId)?.Value;
[DataMember(Name = "version")]
public string Version { get; set; }
[DataMember(Name = "program_id")]
public string Program { get; set; }
[DataMember(Name = "platform_id")]
public string Platform { get; set; }
[DataMember(Name = "inputs")]
public List<FormInputValue> Inputs { get; set; } = new List<FormInputValue>();
}
}
@@ -1,23 +1,22 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Login
{ {
[DataContract]
public class FormInput public class FormInput
{ {
[DataMember(Name = "input_id")] [JsonPropertyName("input_id")]
public string Id { get; set; } public string Id { get; set; }
[DataMember(Name = "type")] [JsonPropertyName("type")]
public string Type { get; set; } public string Type { get; set; }
[DataMember(Name = "label")] [JsonPropertyName("label")]
public string Label { get; set; } public string Label { get; set; }
[DataMember(Name = "max_length")] [JsonPropertyName("max_length")]
public int MaxLength { get; set; } public int MaxLength { get; set; }
} }
} }
@@ -1,17 +1,16 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Login
{ {
[DataContract]
public class FormInputValue public class FormInputValue
{ {
[DataMember(Name = "input_id")] [JsonPropertyName("input_id")]
public string Id { get; set; } public string Id { get; set; }
[DataMember(Name = "value")] [JsonPropertyName("value")]
public string Value { get; set; } public string Value { get; set; }
} }
} }
@@ -2,20 +2,22 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Login
{ {
[DataContract]
public class FormInputs public class FormInputs
{ {
[DataMember(Name = "type")] [JsonPropertyName("type")]
public string Type { get; set; } public string Type { get; set; }
[DataMember(Name = "prompt")] [JsonPropertyName("inputs")]
public string Prompt { get; set; }
[DataMember(Name = "inputs")]
public List<FormInput> Inputs { get; set; } = new List<FormInput>(); public List<FormInput> Inputs { get; set; } = new List<FormInput>();
[JsonPropertyName("srp_url")]
public string SrpUrl { get; set; }
[JsonPropertyName("srp_js")]
public string SrpJs { get; set; }
} }
} }
@@ -0,0 +1,28 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Text.Json.Serialization;
namespace Framework.Web.Rest.Login
{
public class GameAccountInfo
{
[JsonPropertyName("display_name")]
public string DisplayName { get; set; }
[JsonPropertyName("expansion")]
public int Expansion { get; set; }
[JsonPropertyName("is_suspended")]
public bool IsSuspended { get; set; }
[JsonPropertyName("is_banned")]
public bool IsBanned { get; set; }
[JsonPropertyName("suspension_expires")]
public long SuspensionExpires { get; set; }
[JsonPropertyName("suspension_reason")]
public string SuspensionReason { get; set; }
}
}
@@ -0,0 +1,14 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Framework.Web.Rest.Login
{
public class GameAccountList
{
[JsonPropertyName("game_accounts")]
public List<GameAccountInfo> GameAccounts { get; set; }
}
}
@@ -0,0 +1,23 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Framework.Web.Rest.Login
{
public class LoginForm
{
[JsonPropertyName("platform_id")]
public string PlatformId { get; set; }
[JsonPropertyName("program_id")]
public string ProgramId { get; set; }
[JsonPropertyName("version")]
public string Version { get; set; }
[JsonPropertyName("inputs")]
public List<FormInputValue> Inputs { get; set; } = new List<FormInputValue>();
}
}
@@ -0,0 +1,16 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Text.Json.Serialization;
namespace Framework.Web.Rest.Login
{
public class LoginRefreshResult
{
[JsonPropertyName("login_ticket_expiry")]
public long LoginTicketExpiry { get; set; }
[JsonPropertyName("is_expired")]
public bool IsExpired { get; set; }
}
}
@@ -1,30 +1,29 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Login
{ {
[DataContract] public class LoginResult
public class LogonResult
{ {
[DataMember(Name = "authentication_state")] [JsonPropertyName("authentication_state")]
public string AuthenticationState { get; set; } public string AuthenticationState { get; set; }
[DataMember(Name = "login_ticket")] [JsonPropertyName("error_code")]
public string LoginTicket { get; set; }
[DataMember(Name = "error_code")]
public string ErrorCode { get; set; } public string ErrorCode { get; set; }
[DataMember(Name = "error_message")] [JsonPropertyName("error_message")]
public string ErrorMessage { get; set; } public string ErrorMessage { get; set; }
[DataMember(Name = "support_error_code")] [JsonPropertyName("url")]
public string SupportErrorCode { get; set; } public string Url { get; set; }
[DataMember(Name = "authenticator_form")] [JsonPropertyName("login_ticket")]
public FormInputs AuthenticatorForm { get; set; } = new FormInputs(); public string LoginTicket { get; set; }
[JsonPropertyName("server_evidence_M2")]
public string ServerEvidenceM2 { get; set; }
} }
public enum AuthenticationState public enum AuthenticationState
@@ -0,0 +1,37 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Text.Json.Serialization;
namespace Framework.Web.Rest.Login
{
public class SrpLoginChallenge
{
[JsonPropertyName("version")]
public int Version { get; set; }
[JsonPropertyName("iterations")]
public int Iterations { get; set; }
[JsonPropertyName("modulus")]
public string Modulus { get; set; }
[JsonPropertyName("generator")]
public string Generator { get; set; }
[JsonPropertyName("hash_function")]
public string HashFunction { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("salt")]
public string Salt { get; set; }
[JsonPropertyName("public_B")]
public string PublicB { get; set; }
[JsonPropertyName("eligible_credential_upgrade")]
public bool EligibleCredentialUpgrade { get; set; }
}
}
@@ -1,17 +1,16 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Realmlist
{ {
[DataContract]
public class Address public class Address
{ {
[DataMember(Name = "ip")] [JsonPropertyName("ip")]
public string Ip { get; set; } public string Ip { get; set; }
[DataMember(Name = "port")] [JsonPropertyName("port")]
public int Port { get; set; } public int Port { get; set; }
} }
} }
@@ -2,17 +2,16 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Realmlist
{ {
[DataContract]
public class AddressFamily public class AddressFamily
{ {
[DataMember(Name = "family")] [JsonPropertyName("family")]
public int Id { get; set; } public int Id { get; set; }
[DataMember(Name = "addresses")] [JsonPropertyName("addresses")]
public IList<Address> Addresses { get; set; } = new List<Address>(); public IList<Address> Addresses { get; set; } = new List<Address>();
} }
} }
@@ -2,53 +2,53 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Text.Json.Serialization;
using Framework.Web.Rest.Realmlist;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract] public class ClientInformation
public class RealmListTicketInformation
{ {
[DataMember(Name = "platform")] [JsonPropertyName("platform")]
public int Platform { get; set; } public int Platform { get; set; }
[DataMember(Name = "buildVariant")] [JsonPropertyName("buildVariant")]
public string BuildVariant { get; set; } public string BuildVariant { get; set; }
[DataMember(Name = "type")] [JsonPropertyName("type")]
public int Type { get; set; } public int Type { get; set; }
[DataMember(Name = "timeZone")] [JsonPropertyName("timeZone")]
public string Timezone { get; set; } public string Timezone { get; set; }
[DataMember(Name = "currentTime")] [JsonPropertyName("currentTime")]
public int CurrentTime { get; set; } public int CurrentTime { get; set; }
[DataMember(Name = "textLocale")] [JsonPropertyName("textLocale")]
public int TextLocale { get; set; } public int TextLocale { get; set; }
[DataMember(Name = "audioLocale")] [JsonPropertyName("audioLocale")]
public int AudioLocale { get; set; } public int AudioLocale { get; set; }
[DataMember(Name = "versionDataBuild")] [JsonPropertyName("versionDataBuild")]
public int VersionDataBuild { get; set; } public int VersionDataBuild { get; set; }
[DataMember(Name = "version")] [JsonPropertyName("version")]
public ClientVersion ClientVersion { get; set; } = new ClientVersion(); public ClientVersion ClientVersion { get; set; } = new ClientVersion();
[DataMember(Name = "secret")] [JsonPropertyName("secret")]
public List<int> Secret { get; set; } public List<int> Secret { get; set; }
[DataMember(Name = "clientArch")] [JsonPropertyName("clientArch")]
public int ClientArch { get; set; } public int ClientArch { get; set; }
[DataMember(Name = "systemVersion")] [JsonPropertyName("systemVersion")]
public string SystemVersion { get; set; } public string SystemVersion { get; set; }
[DataMember(Name = "platformType")] [JsonPropertyName("platformType")]
public int PlatformType { get; set; } public int PlatformType { get; set; }
[DataMember(Name = "systemArch")] [JsonPropertyName("systemArch")]
public int SystemArch { get; set; } public int SystemArch { get; set; }
} }
} }
@@ -1,23 +1,22 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Realmlist
{ {
[DataContract]
public class ClientVersion public class ClientVersion
{ {
[DataMember(Name = "versionMajor")] [JsonPropertyName("versionMajor")]
public int Major { get; set; } public int Major { get; set; }
[DataMember(Name = "versionBuild")] [JsonPropertyName("versionBuild")]
public int Build { get; set; } public int Build { get; set; }
[DataMember(Name = "versionMinor")] [JsonPropertyName("versionMinor")]
public int Minor { get; set; } public int Minor { get; set; }
[DataMember(Name = "versionRevision")] [JsonPropertyName("versionRevision")]
public int Revision { get; set; } public int Revision { get; set; }
} }
} }
@@ -1,17 +1,16 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web.Rest.Realmlist
{ {
[DataContract]
public class RealmCharacterCountEntry public class RealmCharacterCountEntry
{ {
[DataMember(Name = "wowRealmAddress")] [JsonPropertyName("wowRealmAddress")]
public int WowRealmAddress { get; set; } public int WowRealmAddress { get; set; }
[DataMember(Name = "count")] [JsonPropertyName("count")]
public int Count { get; set; } public int Count { get; set; }
} }
} }
@@ -2,14 +2,14 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Text.Json.Serialization;
using Framework.Web.Rest.Realmlist;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract]
public class RealmCharacterCountList public class RealmCharacterCountList
{ {
[DataMember(Name = "counts")] [JsonPropertyName("counts")]
public IList<RealmCharacterCountEntry> Counts { get; set; } = new List<RealmCharacterCountEntry>(); public IList<RealmCharacterCountEntry> Counts { get; set; } = new List<RealmCharacterCountEntry>();
} }
} }
@@ -1,42 +1,41 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
using Framework.Web.Rest.Realmlist;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract]
public class RealmEntry public class RealmEntry
{ {
[JsonPropertyName("wowRealmAddress")]
[DataMember(Name = "wowRealmAddress")]
public int WowRealmAddress { get; set; } public int WowRealmAddress { get; set; }
[DataMember(Name = "cfgTimezonesID")] [JsonPropertyName("cfgTimezonesID")]
public int CfgTimezonesID { get; set; } public int CfgTimezonesID { get; set; }
[DataMember(Name = "populationState")] [JsonPropertyName("populationState")]
public int PopulationState { get; set; } public int PopulationState { get; set; }
[DataMember(Name = "cfgCategoriesID")] [JsonPropertyName("cfgCategoriesID")]
public int CfgCategoriesID { get; set; } public int CfgCategoriesID { get; set; }
[DataMember(Name = "version")] [JsonPropertyName("version")]
public ClientVersion Version { get; set; } = new ClientVersion(); public ClientVersion Version { get; set; } = new ClientVersion();
[DataMember(Name = "cfgRealmsID")] [JsonPropertyName("cfgRealmsID")]
public int CfgRealmsID { get; set; } public int CfgRealmsID { get; set; }
[DataMember(Name = "flags")] [JsonPropertyName("flags")]
public int Flags { get; set; } public int Flags { get; set; }
[DataMember(Name = "name")] [JsonPropertyName("name")]
public string Name { get; set; } public string Name { get; set; }
[DataMember(Name = "cfgConfigsID")] [JsonPropertyName("cfgConfigsID")]
public int CfgConfigsID { get; set; } public int CfgConfigsID { get; set; }
[DataMember(Name = "cfgLanguagesID")] [JsonPropertyName("cfgLanguagesID")]
public int CfgLanguagesID { get; set; } public int CfgLanguagesID { get; set; }
} }
} }
@@ -2,14 +2,14 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Text.Json.Serialization;
using Framework.Web.Rest.Realmlist;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract]
public class RealmListServerIPAddresses public class RealmListServerIPAddresses
{ {
[DataMember(Name = "families")] [JsonPropertyName("families")]
public IList<AddressFamily> Families { get; set; } = new List<AddressFamily>(); public IList<AddressFamily> Families { get; set; } = new List<AddressFamily>();
} }
} }
@@ -1,14 +1,13 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract]
public class RealmListTicketClientInformation public class RealmListTicketClientInformation
{ {
[DataMember(Name = "info")] [JsonPropertyName("info")]
public RealmListTicketInformation Info { get; set; } = new RealmListTicketInformation(); public ClientInformation Info { get; set; } = new ClientInformation();
} }
} }
@@ -1,17 +1,16 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract]
public class RealmListTicketIdentity public class RealmListTicketIdentity
{ {
[DataMember(Name = "gameAccountID")] [JsonPropertyName("gameAccountID")]
public int GameAccountId { get; set; } public int GameAccountId { get; set; }
[DataMember(Name = "gameAccountRegion")] [JsonPropertyName("gameAccountRegion")]
public int GameAccountRegion { get; set; } public int GameAccountRegion { get; set; }
} }
} }
@@ -1,17 +1,16 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract]
public class RealmListUpdate public class RealmListUpdate
{ {
[DataMember(Name = "update")] [JsonPropertyName("update")]
public RealmEntry Update { get; set; } = new RealmEntry(); public RealmEntry Update { get; set; } = new RealmEntry();
[DataMember(Name = "deleting")] [JsonPropertyName("deleting")]
public bool Deleting { get; set; } public bool Deleting { get; set; }
} }
} }
@@ -2,14 +2,13 @@
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Text.Json.Serialization;
namespace Framework.Web namespace Framework.Web
{ {
[DataContract]
public class RealmListUpdates public class RealmListUpdates
{ {
[DataMember(Name = "updates")] [JsonPropertyName("updates")]
public IList<RealmListUpdate> Updates { get; set; } = new List<RealmListUpdate>(); public IList<RealmListUpdate> Updates { get; set; } = new List<RealmListUpdate>();
} }
} }
+4 -4
View File
@@ -34,7 +34,7 @@ namespace Game
if (GetId(username) != 0) if (GetId(username) != 0)
return AccountOpResult.NameAlreadyExist; return AccountOpResult.NameAlreadyExist;
(byte[] salt, byte[] verifier) = SRP6.MakeRegistrationData(username, password); (byte[] salt, byte[] verifier) = SRP6.MakeAccountRegistrationData<GruntSRP6>(username, password);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_ACCOUNT); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_ACCOUNT);
stmt.AddValue(0, username); stmt.AddValue(0, username);
@@ -152,7 +152,7 @@ namespace Game
stmt.AddValue(1, accountId); stmt.AddValue(1, accountId);
DB.Login.Execute(stmt); DB.Login.Execute(stmt);
(byte[] salt, byte[] verifier) = SRP6.MakeRegistrationData(newUsername, newPassword); (byte[] salt, byte[] verifier) = SRP6.MakeAccountRegistrationData<GruntSRP6>(newUsername, newPassword);
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_LOGON); stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_LOGON);
stmt.AddValue(0, salt); stmt.AddValue(0, salt);
stmt.AddValue(1, verifier); stmt.AddValue(1, verifier);
@@ -178,7 +178,7 @@ namespace Game
return AccountOpResult.PassTooLong; return AccountOpResult.PassTooLong;
} }
(byte[] salt, byte[] verifier) = SRP6.MakeRegistrationData(username, newPassword); (byte[] salt, byte[] verifier) = SRP6.MakeAccountRegistrationData<GruntSRP6>(username, newPassword);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_LOGON); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_LOGON);
stmt.AddValue(0, salt); stmt.AddValue(0, salt);
@@ -304,7 +304,7 @@ namespace Game
{ {
byte[] salt = result.Read<byte[]>(0); byte[] salt = result.Read<byte[]>(0);
byte[] verifier = result.Read<byte[]>(1); byte[] verifier = result.Read<byte[]>(1);
if (SRP6.CheckLogin(username, password, salt, verifier)) if (new GruntSRP6(username, salt, verifier).CheckCredentials(username, password))
return true; return true;
} }
+47 -14
View File
@@ -1,6 +1,7 @@
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved. // Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Cryptography;
using Framework.Database; using Framework.Database;
using System; using System;
using System.Security.Cryptography; using System.Security.Cryptography;
@@ -10,24 +11,32 @@ namespace Game
{ {
public sealed class BNetAccountManager : Singleton<BNetAccountManager> public sealed class BNetAccountManager : Singleton<BNetAccountManager>
{ {
static int MAX_BNET_EMAIL_STR = 320;
static int MAX_BNET_PASS_STR = 128;
BNetAccountManager() { } BNetAccountManager() { }
public AccountOpResult CreateBattlenetAccount(string email, string password, bool withGameAccount, out string gameAccountName) public AccountOpResult CreateBattlenetAccount(string email, string password, bool withGameAccount, out string gameAccountName)
{ {
gameAccountName = ""; gameAccountName = "";
if (email.IsEmpty() || email.Length > 320) if (email.IsEmpty() || email.Length > MAX_BNET_EMAIL_STR)
return AccountOpResult.NameTooLong; return AccountOpResult.NameTooLong;
if (password.IsEmpty() || password.Length > 16) if (password.IsEmpty() || password.Length > MAX_BNET_PASS_STR)
return AccountOpResult.PassTooLong; return AccountOpResult.PassTooLong;
if (GetId(email) != 0) if (GetId(email) != 0)
return AccountOpResult.NameAlreadyExist; return AccountOpResult.NameAlreadyExist;
string srpUsername = GetSrpUsername(email);
var (salt, verifier) = SRP6.MakeBNetRegistrationData<BnetSRP6v2Hash256>(srpUsername, password);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_BNET_ACCOUNT); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_BNET_ACCOUNT);
stmt.AddValue(0, email); stmt.AddValue(0, email);
stmt.AddValue(1, CalculateShaPassHash(email.ToUpper(), password.ToUpper())); stmt.AddValue(1, (sbyte)SrpVersion.v2);
stmt.AddValue(2, salt);
stmt.AddValue(3, verifier);
DB.Login.DirectExecute(stmt); DB.Login.DirectExecute(stmt);
uint newAccountId = GetId(email); uint newAccountId = GetId(email);
@@ -36,7 +45,8 @@ namespace Game
if (withGameAccount) if (withGameAccount)
{ {
gameAccountName = newAccountId + "#1"; gameAccountName = newAccountId + "#1";
Global.AccountMgr.CreateAccount(gameAccountName, password, email, newAccountId, 1); string gameAccountPassword = password.Substring(0, 16);
Global.AccountMgr.CreateAccount(gameAccountName, gameAccountPassword.ToUpper(), email, newAccountId, 1);
} }
return AccountOpResult.Ok; return AccountOpResult.Ok;
@@ -48,12 +58,17 @@ namespace Game
if (!GetName(accountId, out username)) if (!GetName(accountId, out username))
return AccountOpResult.NameNotExist; return AccountOpResult.NameNotExist;
if (newPassword.Length > 16) if (newPassword.Length > MAX_BNET_PASS_STR)
return AccountOpResult.PassTooLong; return AccountOpResult.PassTooLong;
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_PASSWORD); string srpUsername = GetSrpUsername(username);
stmt.AddValue(0, CalculateShaPassHash(username, newPassword)); var (salt, verifier) = SRP6.MakeBNetRegistrationData<BnetSRP6v2Hash256>(srpUsername, newPassword);
stmt.AddValue(1, accountId);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_LOGON);
stmt.AddValue(0, (sbyte)SrpVersion.v2);
stmt.AddValue(1, salt);
stmt.AddValue(2, verifier);
stmt.AddValue(3, accountId);
DB.Login.Execute(stmt); DB.Login.Execute(stmt);
return AccountOpResult.Ok; return AccountOpResult.Ok;
@@ -67,9 +82,23 @@ namespace Game
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_CHECK_PASSWORD);
stmt.AddValue(0, accountId); stmt.AddValue(0, accountId);
stmt.AddValue(1, CalculateShaPassHash(username, password)); SQLResult result = DB.Login.Query(stmt);
if (!result.IsEmpty())
{
var salt = result.Read<byte[]>(1);
var verifier = result.Read<byte[]>(2);
switch ((SrpVersion)result.Read<sbyte>(0))
{
case SrpVersion.v1:
return new BnetSRP6v1Hash256(username, salt, verifier).CheckCredentials(username, password);
case SrpVersion.v2:
return new BnetSRP6v2Hash256(username, salt, verifier).CheckCredentials(username, password);
default:
break;
}
}
return !DB.Login.Query(stmt).IsEmpty(); return false;
} }
public AccountOpResult LinkWithGameAccount(string email, string gameAccountName) public AccountOpResult LinkWithGameAccount(string email, string gameAccountName)
@@ -165,11 +194,15 @@ namespace Game
return 0; return 0;
} }
public string CalculateShaPassHash(string name, string password) public string GetSrpUsername(string name)
{ {
SHA256 sha256 = SHA256.Create(); return SHA256.HashData(Encoding.UTF8.GetBytes(name)).ToHexString();
var i = sha256.ComputeHash(Encoding.UTF8.GetBytes(name));
return sha256.ComputeHash(Encoding.UTF8.GetBytes(i.ToHexString() + ":" + password)).ToHexString(true);
} }
} }
enum SrpVersion
{
v1 = 1, // password length limit 16 characters, case-insensitive, uses SHA256 to generate verifier
v2 = 2 // password length limit 128 characters, case-sensitive, uses PBKDF2 with SHA512 to generate verifier
}
} }
+2 -2
View File
@@ -271,7 +271,7 @@ namespace Game.Chat.Commands
[Command("account", RBACPermissions.CommandBanlistAccount, true)] [Command("account", RBACPermissions.CommandBanlistAccount, true)]
static bool HandleBanListAccountCommand(CommandHandler handler, [OptionalArg] string filter) static bool HandleBanListAccountCommand(CommandHandler handler, [OptionalArg] string filter)
{ {
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.DelExpiredIpBans); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS);
DB.Login.Execute(stmt); DB.Login.Execute(stmt);
SQLResult result; SQLResult result;
@@ -380,7 +380,7 @@ namespace Game.Chat.Commands
[Command("ip", RBACPermissions.CommandBanlistIp, true)] [Command("ip", RBACPermissions.CommandBanlistIp, true)]
static bool HandleBanListIPCommand(CommandHandler handler, [OptionalArg] string filter) static bool HandleBanListIPCommand(CommandHandler handler, [OptionalArg] string filter)
{ {
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.DelExpiredIpBans); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS);
DB.Login.Execute(stmt); DB.Login.Execute(stmt);
SQLResult result; SQLResult result;
+2 -1
View File
@@ -16,9 +16,9 @@ using Game.DungeonFinding;
using Game.Entities; using Game.Entities;
using Game.Garrisons; using Game.Garrisons;
using Game.Groups; using Game.Groups;
using Game.Guilds;
using Game.Loots; using Game.Loots;
using Game.Maps; using Game.Maps;
using Game.Networking;
using Game.PvP; using Game.PvP;
using Game.Scenarios; using Game.Scenarios;
using Game.Scripting; using Game.Scripting;
@@ -33,6 +33,7 @@ public static class Global
public static WorldManager WorldMgr { get { return WorldManager.Instance; } } public static WorldManager WorldMgr { get { return WorldManager.Instance; } }
public static RealmManager RealmMgr { get { return RealmManager.Instance; } } public static RealmManager RealmMgr { get { return RealmManager.Instance; } }
public static WorldServiceManager ServiceMgr { get { return WorldServiceManager.Instance; } } public static WorldServiceManager ServiceMgr { get { return WorldServiceManager.Instance; } }
public static WorldSocketManager WorldSocketMgr { get { return WorldSocketManager.Instance; } }
//Guild //Guild
public static PetitionManager PetitionMgr { get { return PetitionManager.Instance; } } public static PetitionManager PetitionMgr { get { return PetitionManager.Instance; } }
+10 -37
View File
@@ -29,7 +29,7 @@ namespace Game.Networking
_receiveBuffer = new byte[1024]; _receiveBuffer = new byte[1024];
} }
public void Accept() public void Start()
{ {
// wait 1 second for active connections to send negotiation request // wait 1 second for active connections to send negotiation request
for (int counter = 0; counter < 10 && _socket.Available == 0; counter++) for (int counter = 0; counter < 10 && _socket.Available == 0; counter++)
@@ -46,7 +46,7 @@ namespace Game.Networking
} }
Send("Authentication Required\r\n"); Send("Authentication Required\r\n");
Send("Email: "); Send("Username: ");
string userName = ReadString(); string userName = ReadString();
if (userName.IsEmpty()) if (userName.IsEmpty())
{ {
@@ -64,7 +64,7 @@ namespace Game.Networking
return; return;
} }
if (!CheckAccessLevelAndPassword(userName, password)) if (!CheckAccessLevel(userName) || Global.AccountMgr.CheckPassword(userName, password))
{ {
Send("Authentication failed\r\n"); Send("Authentication failed\r\n");
CloseSocket(); CloseSocket();
@@ -129,56 +129,29 @@ namespace Game.Networking
} }
} }
bool CheckAccessLevelAndPassword(string email, string password) bool CheckAccessLevel(string user)
{ {
//"SELECT a.id, a.username FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id WHERE ba.email = ?" PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_ACCESS);
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_GAME_ACCOUNT_LIST); stmt.AddValue(0, user);
stmt.AddValue(0, email);
SQLResult result = DB.Login.Query(stmt); SQLResult result = DB.Login.Query(stmt);
if (result.IsEmpty()) if (result.IsEmpty())
{ {
Log.outInfo(LogFilter.CommandsRA, $"User {email} does not exist in database"); Log.outInfo(LogFilter.CommandsRA, $"User {user} does not exist in database");
return false; return false;
} }
uint accountId = result.Read<uint>(0);
string username = result.Read<string>(1);
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_ACCOUNT_ACCESS_BY_ID);
stmt.AddValue(0, accountId);
result = DB.Login.Query(stmt);
if (result.IsEmpty())
{
Log.outInfo(LogFilter.CommandsRA, $"User {email} has no privilege to login");
return false;
}
//"SELECT SecurityLevel, RealmID FROM account_access WHERE AccountID = ? and (RealmID = ? OR RealmID = -1) ORDER BY SecurityLevel desc");
if (result.Read<byte>(0) < ConfigMgr.GetDefaultValue("Ra.MinLevel", (byte)AccountTypes.Administrator)) if (result.Read<byte>(0) < ConfigMgr.GetDefaultValue("Ra.MinLevel", (byte)AccountTypes.Administrator))
{ {
Log.outInfo(LogFilter.CommandsRA, $"User {email} has no privilege to login"); Log.outInfo(LogFilter.CommandsRA, $"User {user} has no privilege to login");
return false; return false;
} }
else if (result.Read<int>(1) != -1) else if (result.Read<int>(1) != -1)
{ {
Log.outInfo(LogFilter.CommandsRA, $"User {email} has to be assigned on all realms (with RealmID = '-1')"); Log.outInfo(LogFilter.CommandsRA, $"User {user} has to be assigned on all realms (with RealmID = '-1')");
return false; return false;
} }
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_CHECK_PASSWORD); return true;
stmt.AddValue(0, accountId);
result = DB.Login.Query(stmt);
if (!result.IsEmpty())
{
var salt = result.Read<byte[]>(0);
var verifier = result.Read<byte[]>(1);
if (SRP6.CheckLogin(username, password, salt, verifier))
return true;
}
Log.outInfo(LogFilter.CommandsRA, $"Wrong password for user: {email}");
return false;
} }
bool ProcessCommand(string command) bool ProcessCommand(string command)
+20 -18
View File
@@ -69,11 +69,11 @@ namespace Game.Networking
base.Dispose(); base.Dispose();
} }
public override void Accept() public override void Start()
{ {
string ip_address = GetRemoteIpAddress().ToString(); string ip_address = GetRemoteIpAddress().ToString();
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelIpInfo); PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_IP_INFO);
stmt.AddValue(0, ip_address); stmt.AddValue(0, ip_address);
stmt.AddValue(1, BitConverter.ToUInt32(GetRemoteIpAddress().Address.GetAddressBytes(), 0)); stmt.AddValue(1, BitConverter.ToUInt32(GetRemoteIpAddress().Address.GetAddressBytes(), 0));
@@ -489,7 +489,7 @@ namespace Game.Networking
var address = GetRemoteIpAddress(); var address = GetRemoteIpAddress();
Sha256 digestKeyHash = new(); Sha256 digestKeyHash = new();
digestKeyHash.Process(account.game.SessionKey, account.game.SessionKey.Length); digestKeyHash.Process(account.game.KeyData, account.game.KeyData.Length);
if (account.game.OS == "Wn64") if (account.game.OS == "Wn64")
digestKeyHash.Finish(buildInfo.Win64AuthSeed); digestKeyHash.Finish(buildInfo.Win64AuthSeed);
else if (account.game.OS == "Mc64") else if (account.game.OS == "Mc64")
@@ -515,7 +515,7 @@ namespace Game.Networking
} }
Sha256 keyData = new(); Sha256 keyData = new();
keyData.Finish(account.game.SessionKey); keyData.Finish(account.game.KeyData);
HmacSha256 sessionKeyHmac = new(keyData.Digest); HmacSha256 sessionKeyHmac = new(keyData.Digest);
sessionKeyHmac.Process(_serverChallenge, 16); sessionKeyHmac.Process(_serverChallenge, 16);
@@ -652,7 +652,7 @@ namespace Game.Networking
Global.ScriptMgr.OnAccountLogin(account.game.Id); Global.ScriptMgr.OnAccountLogin(account.game.Id);
_worldSession = new WorldSession(account.game.Id, authSession.RealmJoinTicket, account.battleNet.Id, this, account.game.Security, (Expansion)account.game.Expansion, _worldSession = new WorldSession(account.game.Id, authSession.RealmJoinTicket, account.battleNet.Id, this, account.game.Security, (Expansion)account.game.Expansion,
mutetime, account.game.OS, account.battleNet.Locale, account.game.Recruiter, account.game.IsRectuiter); mutetime, account.game.OS, account.game.TimezoneOffset, account.battleNet.Locale, account.game.Recruiter, account.game.IsRectuiter);
// Initialize Warden system only if it is enabled by config // Initialize Warden system only if it is enabled by config
//if (wardenActive) //if (wardenActive)
@@ -839,15 +839,15 @@ namespace Game.Networking
{ {
public AccountInfo(SQLFields fields) public AccountInfo(SQLFields fields)
{ {
// 0 1 2 3 4 5 6 7 8 9 10 11 // 0 1 2 3 4 5 6 7 8 9 10 11 12
// SELECT a.id, a.sessionkey, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, ba.id, aa.gmLevel, // SELECT a.id, a.session_key, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, ba.locale, a.recruiter, a.os, a.timezone_offset, ba.id, aa.SecurityLevel,
// 12 13 14 // 13 14 15
// bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id // bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id
// FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) // FROM account a LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?)
// LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id LEFT JOIN account r ON a.id = r.recruiter // LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id LEFT JOIN account r ON a.id = r.recruiter
// WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1 // WHERE a.username = ? AND LENGTH(a.session_key) = 40 ORDER BY aa.RealmID DESC LIMIT 1
game.Id = fields.Read<uint>(0); game.Id = fields.Read<uint>(0);
game.SessionKey = fields.Read<byte[]>(1); game.KeyData = fields.Read<byte[]>(1);
battleNet.LastIP = fields.Read<string>(2); battleNet.LastIP = fields.Read<string>(2);
battleNet.IsLockedToIP = fields.Read<bool>(3); battleNet.IsLockedToIP = fields.Read<bool>(3);
battleNet.LockCountry = fields.Read<string>(4); battleNet.LockCountry = fields.Read<string>(4);
@@ -856,11 +856,12 @@ namespace Game.Networking
battleNet.Locale = (Locale)fields.Read<byte>(7); battleNet.Locale = (Locale)fields.Read<byte>(7);
game.Recruiter = fields.Read<uint>(8); game.Recruiter = fields.Read<uint>(8);
game.OS = fields.Read<string>(9); game.OS = fields.Read<string>(9);
battleNet.Id = fields.Read<uint>(10); game.TimezoneOffset = TimeSpan.FromMinutes(fields.Read<short>(10));
game.Security = (AccountTypes)fields.Read<byte>(11); battleNet.Id = fields.Read<uint>(11);
battleNet.IsBanned = fields.Read<uint>(12) != 0; game.Security = (AccountTypes)fields.Read<byte>(12);
game.IsBanned = fields.Read<uint>(13) != 0; battleNet.IsBanned = fields.Read<uint>(13) != 0;
game.IsRectuiter = fields.Read<uint>(14) != 0; game.IsBanned = fields.Read<uint>(14) != 0;
game.IsRectuiter = fields.Read<uint>(15) != 0;
if (battleNet.Locale >= Locale.Total) if (battleNet.Locale >= Locale.Total)
battleNet.Locale = Locale.enUS; battleNet.Locale = Locale.enUS;
@@ -884,11 +885,12 @@ namespace Game.Networking
public struct Game public struct Game
{ {
public uint Id; public uint Id;
public byte[] SessionKey; public byte[] KeyData;
public byte Expansion; public byte Expansion;
public long MuteTime; public long MuteTime;
public string OS;
public uint Recruiter; public uint Recruiter;
public string OS;
public TimeSpan TimezoneOffset;
public bool IsRectuiter; public bool IsRectuiter;
public AccountTypes Security; public AccountTypes Security;
public bool IsBanned; public bool IsBanned;
+9 -1
View File
@@ -10,7 +10,9 @@ namespace Game.Networking
{ {
public class WorldSocketManager : SocketManager<WorldSocket> public class WorldSocketManager : SocketManager<WorldSocket>
{ {
public override bool StartNetwork(string bindIp, int port, int threadCount) public static WorldSocketManager Instance { get; } = new WorldSocketManager();
public override bool StartNetwork(string bindIp, int port, int threadCount = 1)
{ {
_tcpNoDelay = ConfigMgr.GetDefaultValue("Network.TcpNodelay", true); _tcpNoDelay = ConfigMgr.GetDefaultValue("Network.TcpNodelay", true);
@@ -29,6 +31,7 @@ namespace Game.Networking
return false; return false;
} }
Acceptor.AsyncAcceptSocket(OnSocketAccept);
_instanceAcceptor.AsyncAcceptSocket(OnSocketOpen); _instanceAcceptor.AsyncAcceptSocket(OnSocketOpen);
return true; return true;
@@ -62,6 +65,11 @@ namespace Game.Networking
base.OnSocketOpen(sock); base.OnSocketOpen(sock);
} }
static void OnSocketAccept(Socket sock)
{
Global.WorldSocketMgr.OnSocketOpen(sock);
}
AsyncAcceptor _instanceAcceptor; AsyncAcceptor _instanceAcceptor;
int _socketSendBufferSize; int _socketSendBufferSize;
bool _tcpNoDelay; bool _tcpNoDelay;
+6 -2
View File
@@ -25,7 +25,7 @@ namespace Game
{ {
public partial class WorldSession : IDisposable public partial class WorldSession : IDisposable
{ {
public WorldSession(uint id, string name, uint battlenetAccountId, WorldSocket sock, AccountTypes sec, Expansion expansion, long mute_time, string os, Locale locale, uint recruiter, bool isARecruiter) public WorldSession(uint id, string name, uint battlenetAccountId, WorldSocket sock, AccountTypes sec, Expansion expansion, long mute_time, string os, TimeSpan timezoneOffset, Locale locale, uint recruiter, bool isARecruiter)
{ {
m_muteTime = mute_time; m_muteTime = mute_time;
AntiDOS = new DosProtection(this); AntiDOS = new DosProtection(this);
@@ -39,6 +39,7 @@ namespace Game
_os = os; _os = os;
m_sessionDbcLocale = Global.WorldMgr.GetAvailableDbcLocale(locale); m_sessionDbcLocale = Global.WorldMgr.GetAvailableDbcLocale(locale);
m_sessionDbLocaleIndex = locale; m_sessionDbLocaleIndex = locale;
_timezoneOffset = timezoneOffset;
recruiterId = recruiter; recruiterId = recruiter;
isRecruiter = isARecruiter; isRecruiter = isARecruiter;
expireTime = 60000; // 1 min after socket loss, session is deleted expireTime = 60000; // 1 min after socket loss, session is deleted
@@ -897,6 +898,8 @@ namespace Game
public Locale GetSessionDbcLocale() { return m_sessionDbcLocale; } public Locale GetSessionDbcLocale() { return m_sessionDbcLocale; }
public Locale GetSessionDbLocaleIndex() { return m_sessionDbLocaleIndex; } public Locale GetSessionDbLocaleIndex() { return m_sessionDbLocaleIndex; }
public TimeSpan GetTimezoneOffset() { return _timezoneOffset; }
public uint GetLatency() { return m_latency; } public uint GetLatency() { return m_latency; }
public void SetLatency(uint latency) { m_latency = latency; } public void SetLatency(uint latency) { m_latency = latency; }
public void ResetTimeOutTime(bool onlyActive) public void ResetTimeOutTime(bool onlyActive)
@@ -956,6 +959,7 @@ namespace Game
bool m_playerSave; bool m_playerSave;
Locale m_sessionDbcLocale; Locale m_sessionDbcLocale;
Locale m_sessionDbLocaleIndex; Locale m_sessionDbLocaleIndex;
TimeSpan _timezoneOffset;
uint m_latency; uint m_latency;
AccountData[] _accountData = new AccountData[(int)AccountDataTypes.Max]; AccountData[] _accountData = new AccountData[(int)AccountDataTypes.Max];
uint[] tutorials = new uint[SharedConst.MaxAccountTutorialValues]; uint[] tutorials = new uint[SharedConst.MaxAccountTutorialValues];
@@ -1141,7 +1145,7 @@ namespace Game
stmt.AddValue(0, battlenetAccountId); stmt.AddValue(0, battlenetAccountId);
SetQuery(AccountInfoQueryLoad.Mounts, stmt); SetQuery(AccountInfoQueryLoad.Mounts, stmt);
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelBnetCharacterCountsByAccountId); stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_CHARACTER_COUNTS_BY_ACCOUNT_ID);
stmt.AddValue(0, accountId); stmt.AddValue(0, accountId);
SetQuery(AccountInfoQueryLoad.GlobalRealmCharacterCounts, stmt); SetQuery(AccountInfoQueryLoad.GlobalRealmCharacterCounts, stmt);
+12 -4
View File
@@ -3,11 +3,15 @@
using Bgs.Protocol.GameUtilities.V1; using Bgs.Protocol.GameUtilities.V1;
using Framework.Constants; using Framework.Constants;
using Framework.IO;
using Framework.Web; using Framework.Web;
using Framework.Serialization; using Framework.Web.Rest.Realmlist;
using Game.Services; using Game.Services;
using Google.Protobuf; using Google.Protobuf;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace Game namespace Game
{ {
@@ -90,7 +94,11 @@ namespace Game
countEntry.Count = characterCount.Value; countEntry.Count = characterCount.Value;
realmCharacterCounts.Counts.Add(countEntry); realmCharacterCounts.Counts.Add(countEntry);
} }
compressed = Json.Deflate("JSONRealmCharacterCountList", realmCharacterCounts);
var jsonData = Encoding.UTF8.GetBytes("JSONRealmCharacterCountList:" + JsonSerializer.Serialize(realmCharacterCounts) + "\0");
var compressedData = ZLib.Compress(jsonData);
compressed = BitConverter.GetBytes(jsonData.Length).Combine(compressedData);
attribute = new Bgs.Protocol.Attribute(); attribute = new Bgs.Protocol.Attribute();
attribute.Name = "Param_CharacterCountList"; attribute.Name = "Param_CharacterCountList";
@@ -105,9 +113,9 @@ namespace Game
var realmAddress = Params.LookupByKey("Param_RealmAddress"); var realmAddress = Params.LookupByKey("Param_RealmAddress");
if (realmAddress != null) if (realmAddress != null)
return Global.RealmMgr.JoinRealm((uint)realmAddress.UintValue, Global.WorldMgr.GetRealm().Build, System.Net.IPAddress.Parse(GetRemoteAddress()), GetRealmListSecret(), return Global.RealmMgr.JoinRealm((uint)realmAddress.UintValue, Global.WorldMgr.GetRealm().Build, System.Net.IPAddress.Parse(GetRemoteAddress()), GetRealmListSecret(),
GetSessionDbcLocale(), GetOS(), GetAccountName(), response); GetSessionDbcLocale(), GetOS(), GetTimezoneOffset(), GetAccountName(), response);
return BattlenetRpcErrorCode.Ok; return BattlenetRpcErrorCode.Ok;
} }
} }
} }
+2 -3
View File
@@ -67,8 +67,7 @@ namespace WorldServer
return; return;
} }
var WorldSocketMgr = new WorldSocketManager(); if (!Global.WorldSocketMgr.StartNetwork(worldListener, worldPort, networkThreads))
if (!WorldSocketMgr.StartNetwork(worldListener, worldPort, networkThreads))
{ {
Log.outError(LogFilter.Network, "Failed to start Realm Network"); Log.outError(LogFilter.Network, "Failed to start Realm Network");
ExitNow(); ExitNow();
@@ -104,7 +103,7 @@ namespace WorldServer
// unload Battlegroundtemplates before different singletons destroyed // unload Battlegroundtemplates before different singletons destroyed
Global.BattlegroundMgr.DeleteAllBattlegrounds(); Global.BattlegroundMgr.DeleteAllBattlegrounds();
WorldSocketMgr.StopNetwork(); Global.WorldSocketMgr.StopNetwork();
Global.MapMgr.UnloadAll(); // unload all grids (including locked in memory) Global.MapMgr.UnloadAll(); // unload all grids (including locked in memory)
Global.TerrainMgr.UnloadAll(); Global.TerrainMgr.UnloadAll();