Updated Bnet Server.
Port From (https://github.com/TrinityCore/TrinityCore)
This commit is contained in:
@@ -2,9 +2,13 @@
|
||||
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
|
||||
|
||||
using BNetServer;
|
||||
using BNetServer.Networking;
|
||||
using BNetServer.REST;
|
||||
|
||||
public static class Global
|
||||
{
|
||||
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; } }
|
||||
}
|
||||
@@ -4,13 +4,11 @@
|
||||
using BNetServer.Networking;
|
||||
using Framework.Configuration;
|
||||
using Framework.Constants;
|
||||
using Framework.Web;
|
||||
using Google.Protobuf;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
@@ -19,67 +17,12 @@ namespace BNetServer
|
||||
public class LoginServiceManager : Singleton<LoginServiceManager>
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
LoginServiceManager() { }
|
||||
|
||||
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"));
|
||||
|
||||
Assembly currentAsm = Assembly.GetExecutingAssembly();
|
||||
@@ -117,21 +60,6 @@ namespace BNetServer
|
||||
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()
|
||||
{
|
||||
return certificate;
|
||||
|
||||
@@ -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;
|
||||
build = (uint)logonRequest.ApplicationVersion;
|
||||
|
||||
var hostname = Global.LoginServiceMgr.GetHostnameForClient(GetRemoteIpEndPoint());
|
||||
|
||||
ChallengeExternalRequest externalChallenge = new();
|
||||
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);
|
||||
return BattlenetRpcErrorCode.Ok;
|
||||
@@ -55,7 +53,7 @@ namespace BNetServer.Networking
|
||||
if (verifyWebCredentialsRequest.WebCredentials.IsEmpty)
|
||||
return BattlenetRpcErrorCode.Denied;
|
||||
|
||||
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelBnetAccountInfo);
|
||||
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_ACCOUNT_INFO);
|
||||
stmt.AddValue(0, verifyWebCredentialsRequest.WebCredentials.ToStringUtf8());
|
||||
|
||||
SQLResult result = DB.Login.Query(stmt);
|
||||
@@ -81,7 +79,7 @@ namespace BNetServer.Networking
|
||||
} while (characterCountsResult.NextRow());
|
||||
}
|
||||
|
||||
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SelBnetLastPlayerCharacters);
|
||||
stmt = LoginDatabase.GetPreparedStatement(LoginStatements.SEL_BNET_LAST_PLAYER_CHARACTERS);
|
||||
stmt.AddValue(0, accountInfo.Id);
|
||||
|
||||
SQLResult lastPlayerCharactersResult = DB.Login.Query(stmt);
|
||||
@@ -102,7 +100,7 @@ namespace BNetServer.Networking
|
||||
} 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 (accountInfo.IsLockedToIP)
|
||||
|
||||
@@ -5,11 +5,14 @@ using Bgs.Protocol;
|
||||
using Bgs.Protocol.GameUtilities.V1;
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Framework.Serialization;
|
||||
using Framework.IO;
|
||||
using Framework.Web;
|
||||
using Framework.Web.Rest.Realmlist;
|
||||
using Google.Protobuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BNetServer.Networking
|
||||
{
|
||||
@@ -36,7 +39,7 @@ namespace BNetServer.Networking
|
||||
{
|
||||
Bgs.Protocol.Attribute attr = request.Attribute[i];
|
||||
if (attr.Name.Contains("Command_"))
|
||||
{
|
||||
{
|
||||
command = attr;
|
||||
Params[removeSuffix(attr.Name)] = attr.Value;
|
||||
}
|
||||
@@ -80,10 +83,15 @@ namespace BNetServer.Networking
|
||||
Variant identity = Params.LookupByKey("Param_Identity");
|
||||
if (identity != null)
|
||||
{
|
||||
var realmListTicketIdentity = Json.CreateObject<RealmListTicketIdentity>(identity.BlobValue.ToStringUtf8(), true);
|
||||
var gameAccount = accountInfo.GameAccounts.LookupByKey(realmListTicketIdentity.GameAccountId);
|
||||
if (gameAccount != null)
|
||||
gameAccountInfo = gameAccount;
|
||||
string jsonString = identity.BlobValue.ToStringUtf8().TrimEnd('\0');
|
||||
int jsonStart = jsonString.IndexOf(':');
|
||||
if (jsonStart != -1)
|
||||
{
|
||||
var realmListTicketIdentity = JsonSerializer.Deserialize<RealmListTicketIdentity>(jsonString.Substring(jsonStart + 1));
|
||||
var gameAccount = accountInfo.GameAccounts.LookupByKey(realmListTicketIdentity.GameAccountId);
|
||||
if (gameAccount != null)
|
||||
gameAccountInfo = gameAccount;
|
||||
}
|
||||
}
|
||||
|
||||
if (gameAccountInfo == null)
|
||||
@@ -98,18 +106,23 @@ namespace BNetServer.Networking
|
||||
Variant clientInfo = Params.LookupByKey("Param_ClientInfo");
|
||||
if (clientInfo != null)
|
||||
{
|
||||
var realmListTicketClientInformation = Json.CreateObject<RealmListTicketClientInformation>(clientInfo.BlobValue.ToStringUtf8(), true);
|
||||
clientInfoOk = true;
|
||||
int i = 0;
|
||||
foreach (byte b in realmListTicketClientInformation.Info.Secret)
|
||||
clientSecret[i++] = b;
|
||||
string jsonString = clientInfo.BlobValue.ToStringUtf8().Trim('\0');
|
||||
int jsonStart = jsonString.IndexOf(':');
|
||||
if (jsonStart != -1)
|
||||
{
|
||||
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)
|
||||
return BattlenetRpcErrorCode.WowServicesDeniedRealmListTicket;
|
||||
|
||||
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UpdBnetLastLoginInfo);
|
||||
stmt.AddValue(0, GetRemoteIpEndPoint().ToString());
|
||||
PreparedStatement stmt = LoginDatabase.GetPreparedStatement(LoginStatements.UPD_BNET_LAST_LOGIN_INFO);
|
||||
stmt.AddValue(0, GetRemoteIpAddress().ToString());
|
||||
stmt.AddValue(1, (byte)locale.ToEnum<Locale>());
|
||||
stmt.AddValue(2, os);
|
||||
stmt.AddValue(3, accountInfo.Id);
|
||||
@@ -119,7 +132,7 @@ namespace BNetServer.Networking
|
||||
var attribute = new Bgs.Protocol.Attribute();
|
||||
attribute.Name = "Param_RealmListTicket";
|
||||
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);
|
||||
|
||||
return BattlenetRpcErrorCode.Ok;
|
||||
@@ -197,7 +210,10 @@ namespace BNetServer.Networking
|
||||
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.Name = "Param_CharacterCountList";
|
||||
@@ -211,9 +227,9 @@ namespace BNetServer.Networking
|
||||
{
|
||||
Variant realmAddress = Params.LookupByKey("Param_RealmAddress");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BNetServer.Networking
|
||||
{
|
||||
partial class Session : SSLSocket
|
||||
public partial class Session : SSLSocket
|
||||
{
|
||||
AccountInfo accountInfo;
|
||||
GameAccountInfo gameAccountInfo;
|
||||
@@ -23,6 +24,7 @@ namespace BNetServer.Networking
|
||||
string locale;
|
||||
string os;
|
||||
uint build;
|
||||
TimeSpan _timezoneOffset;
|
||||
string ipCountry;
|
||||
|
||||
byte[] clientSecret;
|
||||
@@ -39,19 +41,19 @@ namespace BNetServer.Networking
|
||||
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.");
|
||||
|
||||
// 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(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())
|
||||
{
|
||||
@@ -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()
|
||||
{
|
||||
if (!base.Update())
|
||||
@@ -103,7 +117,7 @@ namespace BNetServer.Networking
|
||||
header.MergeFrom(data, 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;
|
||||
|
||||
if (header.ServiceId != 0xFE && header.ServiceHash != 0)
|
||||
@@ -181,7 +195,7 @@ namespace BNetServer.Networking
|
||||
{
|
||||
var size = (ushort)header.CalculateSize();
|
||||
byte[] bytes = new byte[2];
|
||||
bytes[0] = (byte)((size >> 8) & 0xff);
|
||||
bytes[0] = (byte)(size >> 8 & 0xff);
|
||||
bytes[1] = (byte)(size & 0xff);
|
||||
|
||||
var headerSizeBytes = BitConverter.GetBytes((ushort)header.CalculateSize());
|
||||
@@ -192,7 +206,7 @@ namespace BNetServer.Networking
|
||||
|
||||
public string GetClientInfo()
|
||||
{
|
||||
string stream = '[' + GetRemoteIpEndPoint().ToString();
|
||||
string stream = '[' + GetRemoteIpAddress().ToString();
|
||||
if (accountInfo != null && !accountInfo.Login.IsEmpty())
|
||||
stream += ", Account: " + accountInfo.Login;
|
||||
|
||||
@@ -206,7 +220,7 @@ namespace BNetServer.Networking
|
||||
}
|
||||
|
||||
public class AccountInfo
|
||||
{
|
||||
{
|
||||
public uint Id;
|
||||
public string Login;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -27,36 +27,36 @@ namespace BNetServer
|
||||
if (!StartDB())
|
||||
ExitNow();
|
||||
|
||||
string bindIp = ConfigMgr.GetDefaultValue("BindIP", "0.0.0.0");
|
||||
|
||||
var restSocketServer = new SocketManager<RestSession>();
|
||||
int restPort = ConfigMgr.GetDefaultValue("LoginREST.Port", 8081);
|
||||
if (restPort < 0 || restPort > 0xFFFF)
|
||||
string httpBindIp = ConfigMgr.GetDefaultValue("BindIP", "0.0.0.0");
|
||||
int httpPort = ConfigMgr.GetDefaultValue("LoginREST.Port", 8081);
|
||||
if (httpPort <= 0 || httpPort > 0xFFFF)
|
||||
{
|
||||
Log.outError(LogFilter.Network, $"Specified login service port ({restPort}) out of allowed range (1-65535), defaulting to 8081");
|
||||
restPort = 8081;
|
||||
}
|
||||
|
||||
if (!restSocketServer.StartNetwork(bindIp, restPort))
|
||||
{
|
||||
Log.outError(LogFilter.Server, "Failed to initialize Rest Socket Server");
|
||||
Log.outError(LogFilter.Server, $"Specified login service port ({httpPort}) out of allowed range (1-65535)");
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
// Get the list of realms for the server
|
||||
Global.RealmMgr.Initialize(ConfigMgr.GetDefaultValue("RealmsStateUpdateDelay", 10));
|
||||
Global.LoginServiceMgr.Initialize();
|
||||
if (!Global.LoginService.StartNetwork(httpBindIp, httpPort))
|
||||
{
|
||||
Log.outError(LogFilter.Server, "Failed to initialize login service");
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
var sessionSocketServer = new SocketManager<Session>();
|
||||
// Start the listening port (acceptor) for auth connections
|
||||
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)");
|
||||
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");
|
||||
ExitNow();
|
||||
@@ -89,9 +89,9 @@ namespace BNetServer
|
||||
|
||||
static void BanExpiryCheckTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DelExpiredIpBans));
|
||||
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.UpdExpiredAccountBans));
|
||||
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DelBnetExpiredAccountBanned));
|
||||
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DEL_EXPIRED_IP_BANS));
|
||||
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.UPD_EXPIRED_ACCOUNT_BANS));
|
||||
DB.Login.Execute(LoginDatabase.GetPreparedStatement(LoginStatements.DEL_BNET_EXPIRED_ACCOUNT_BANNED));
|
||||
}
|
||||
|
||||
static Timer _banExpiryCheckTimer;
|
||||
|
||||
Reference in New Issue
Block a user