diff --git a/Tanji.Infrastructure/Services/Implementations/ConnectionHandlerService.cs b/Tanji.Infrastructure/Services/Implementations/ConnectionHandlerService.cs index 3f45317..9c391ed 100644 --- a/Tanji.Infrastructure/Services/Implementations/ConnectionHandlerService.cs +++ b/Tanji.Infrastructure/Services/Implementations/ConnectionHandlerService.cs @@ -1,9 +1,13 @@ using System.Net; +using System.Text; +using System.Net.Sockets; using System.Collections.ObjectModel; using Tanji.Core; using Tanji.Core.Network; using Tanji.Core.Habbo.Canvas; +using Tanji.Core.Habbo.Network; +using Tanji.Infrastructure.Factories; using Tanji.Core.Habbo.Network.Buffers; using Tanji.Core.Habbo.Network.Formats; @@ -15,22 +19,26 @@ namespace Tanji.Infrastructure.Services.Implementations; public sealed class ConnectionHandlerService : IConnectionHandlerService { - private readonly PacketMiddlemanService _packetMiddleman; - private readonly ILogger _logger; private readonly IClientHandlerService _clientHandler; + private readonly IConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + public ObservableCollection Connections { get; } = []; - public ObservableCollection Connections { get; } = []; + public string? Username { get; } = null; + public string? Password { get; } = null; + public IPEndPoint? Socks5EndPoint { get; } public ConnectionHandlerService(ILogger logger, - IClientHandlerService clientHandler, - PacketMiddlemanService packetMiddleman) + IConnectionFactory connectionFactory, + IClientHandlerService clientHandler) { _logger = logger; _clientHandler = clientHandler; - _packetMiddleman = packetMiddleman; + _connectionFactory = connectionFactory; } - public async Task LaunchAndInterceptConnectionAsync(string ticket, HConnectionContext context, CancellationToken cancellationToken = default) + public async Task LaunchAndInterceptConnectionAsync(string ticket, HConnectionContext context, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(ticket)) { @@ -38,15 +46,16 @@ public async Task LaunchAndInterceptConnectionAsync(string ticket, ThrowHelper.ThrowArgumentNullException(nameof(ticket)); } - var connection = new HConnection(); - ValueTask interceptLocalConnectionTask = connection.InterceptLocalConnectionAsync(context, cancellationToken); + IHConnection connection = _connectionFactory.Create(context); + // Begin intercepting for connection attempts from the client before launching the client. + Task interceptLocalConnectionTask = connection.InterceptLocalConnectionAsync(cancellationToken); + _ = await _clientHandler.LaunchClientAsync(context.Platform, ticket, context.ClientPath).ConfigureAwait(false); - _ = _clientHandler.LaunchClient(context.Platform, ticket, context.ClientPath); + // Wait for the intercepted connection await interceptLocalConnectionTask.ConfigureAwait(false); - if (connection.Local == null || !connection.Local.IsConnected) { - _logger.LogError("Failed to intercept local client connection attempt."); + _logger.LogError("Local connection to the client has not been established."); throw new Exception("Local connection to the client has not been established."); } @@ -56,13 +65,96 @@ public async Task LaunchAndInterceptConnectionAsync(string ticket, int written = await connection.Local.ReceivePacketAsync(writer, cancellationToken).ConfigureAwait(false); IPEndPoint? remoteEndPoint = ParseRemoteEndPoint(context.SendPacketFormat, writer.WrittenSpan); - await connection.EstablishRemoteConnectionAsync(context, remoteEndPoint!, cancellationToken).ConfigureAwait(false); + if (remoteEndPoint == null) + { + _logger.LogError("Failed to parse the remote endpoint from the intercepted packet."); + throw new Exception("Failed to parse the remote endpoint from the intercepted packet."); + } + + await connection.EstablishRemoteConnectionAsync(Socks5EndPoint ?? remoteEndPoint, cancellationToken).ConfigureAwait(false); + if (Socks5EndPoint != null) + { + bool wasProxiedSuccessfully = await TryApplyProxyAsync(connection.Remote!, remoteEndPoint!, Username!, Password!, cancellationToken).ConfigureAwait(false); + } + _ = connection.AttachNodesAsync(cancellationToken); } Connections.Add(connection); return connection; } + private static async ValueTask TryApplyProxyAsync(HNode proxiedNode, IPEndPoint targetEndPoint, string? username, string? password, CancellationToken cancellationToken = default) + { + await proxiedNode.SendAsync(new byte[] + { + 0x05, // Version 5 + 0x02, // 2 Authentication Methods Present + 0x00, // No Authentication + 0x02 // Username + Password + }, cancellationToken).ConfigureAwait(false); + + var response = new byte[2]; + int received = await proxiedNode.ReceiveAsync(response, cancellationToken).ConfigureAwait(false); + if (received != 2 || response[1] == 0xFF) return false; + + int index; + byte[]? payload; + if (response[1] == 0x02) // Username + Password Required + { + index = 0; + payload = new byte[byte.MaxValue]; + payload[index++] = 0x01; + + // Username + payload[index++] = (byte)username.Length; + byte[] usernameData = Encoding.Default.GetBytes(username); + Buffer.BlockCopy(usernameData, 0, payload, index, usernameData.Length); + index += usernameData.Length; + + // Password + payload[index++] = (byte)password.Length; + byte[] passwordData = Encoding.Default.GetBytes(password); + Buffer.BlockCopy(passwordData, 0, payload, index, passwordData.Length); + index += passwordData.Length; + + await proxiedNode.SendAsync(payload.AsMemory().Slice(0, index), cancellationToken).ConfigureAwait(false); + received = await proxiedNode.ReceiveAsync(response, cancellationToken).ConfigureAwait(false); + + if (received != 2 || response[1] != 0x00) return false; + } + + index = 0; + payload = new byte[255]; + payload[index++] = 0x05; + payload[index++] = 0x01; + payload[index++] = 0x00; + + payload[index++] = (byte)(targetEndPoint.AddressFamily == AddressFamily.InterNetwork ? 0x01 : 0x04); + + // Destination Address + byte[] addressBytes = targetEndPoint.Address.GetAddressBytes(); + Buffer.BlockCopy(addressBytes, 0, payload, index, addressBytes.Length); + index += (ushort)addressBytes.Length; + + byte[] portData = BitConverter.GetBytes((ushort)targetEndPoint.Port); + if (BitConverter.IsLittleEndian) + { + // Big-Endian Byte Order + Array.Reverse(portData); + } + Buffer.BlockCopy(portData, 0, payload, index, portData.Length); + index += portData.Length; + + await proxiedNode.SendAsync(payload.AsMemory().Slice(0, index), cancellationToken).ConfigureAwait(false); + + byte[] finalResponseBuffer = new byte[byte.MaxValue]; + received = await proxiedNode.ReceiveAsync(finalResponseBuffer, cancellationToken); + + if (received < 2 || response[1] != 0x00) return false; + + return true; + } + private static IPEndPoint? ParseRemoteEndPoint(IHFormat packetFormat, ReadOnlySpan packetSpan) { var pktReader = new HPacketReader(packetFormat, packetSpan);