import * as plugins from '../../plugins.js'; import { ForwardingHandler } from './base-handler.js'; import type { IForwardConfig } from '../config/forwarding-types.js'; import { ForwardingHandlerEvents } from '../config/forwarding-types.js'; /** * Handler for HTTPS passthrough (SNI forwarding without termination) */ export class HttpsPassthroughHandler extends ForwardingHandler { /** * Create a new HTTPS passthrough handler * @param config The forwarding configuration */ constructor(config: IForwardConfig) { super(config); // Validate that this is an HTTPS passthrough configuration if (config.type !== 'https-passthrough') { throw new Error(`Invalid configuration type for HttpsPassthroughHandler: ${config.type}`); } } /** * Initialize the handler * HTTPS passthrough handler doesn't need special initialization */ public async initialize(): Promise { // Basic initialization from parent class await super.initialize(); } /** * Handle a TLS/SSL socket connection by forwarding it without termination * @param clientSocket The incoming socket from the client */ public handleConnection(clientSocket: plugins.net.Socket): void { // Get the target from configuration const target = this.getTargetFromConfig(); // Log the connection const remoteAddress = clientSocket.remoteAddress || 'unknown'; const remotePort = clientSocket.remotePort || 0; this.emit(ForwardingHandlerEvents.CONNECTED, { remoteAddress, remotePort, target: `${target.host}:${target.port}` }); // Create a connection to the target server const serverSocket = plugins.net.connect(target.port, target.host); // Handle errors on the server socket serverSocket.on('error', (error) => { this.emit(ForwardingHandlerEvents.ERROR, { remoteAddress, error: `Target connection error: ${error.message}` }); // Close the client socket if it's still open if (!clientSocket.destroyed) { clientSocket.destroy(); } }); // Handle errors on the client socket clientSocket.on('error', (error) => { this.emit(ForwardingHandlerEvents.ERROR, { remoteAddress, error: `Client connection error: ${error.message}` }); // Close the server socket if it's still open if (!serverSocket.destroyed) { serverSocket.destroy(); } }); // Track data transfer for logging let bytesSent = 0; let bytesReceived = 0; // Forward data from client to server clientSocket.on('data', (data) => { bytesSent += data.length; // Check if server socket is writable if (serverSocket.writable) { const flushed = serverSocket.write(data); // Handle backpressure if (!flushed) { clientSocket.pause(); serverSocket.once('drain', () => { clientSocket.resume(); }); } } this.emit(ForwardingHandlerEvents.DATA_FORWARDED, { direction: 'outbound', bytes: data.length, total: bytesSent }); }); // Forward data from server to client serverSocket.on('data', (data) => { bytesReceived += data.length; // Check if client socket is writable if (clientSocket.writable) { const flushed = clientSocket.write(data); // Handle backpressure if (!flushed) { serverSocket.pause(); clientSocket.once('drain', () => { serverSocket.resume(); }); } } this.emit(ForwardingHandlerEvents.DATA_FORWARDED, { direction: 'inbound', bytes: data.length, total: bytesReceived }); }); // Handle connection close const handleClose = () => { if (!clientSocket.destroyed) { clientSocket.destroy(); } if (!serverSocket.destroyed) { serverSocket.destroy(); } this.emit(ForwardingHandlerEvents.DISCONNECTED, { remoteAddress, bytesSent, bytesReceived }); }; // Set up close handlers clientSocket.on('close', handleClose); serverSocket.on('close', handleClose); // Set timeouts const timeout = this.getTimeout(); clientSocket.setTimeout(timeout); serverSocket.setTimeout(timeout); // Handle timeouts clientSocket.on('timeout', () => { this.emit(ForwardingHandlerEvents.ERROR, { remoteAddress, error: 'Client connection timeout' }); handleClose(); }); serverSocket.on('timeout', () => { this.emit(ForwardingHandlerEvents.ERROR, { remoteAddress, error: 'Server connection timeout' }); handleClose(); }); } /** * Handle an HTTP request - HTTPS passthrough doesn't support HTTP * @param req The HTTP request * @param res The HTTP response */ public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { // HTTPS passthrough doesn't support HTTP requests res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('HTTP not supported for this domain'); this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { statusCode: 404, headers: { 'Content-Type': 'text/plain' }, size: 'HTTP not supported for this domain'.length }); } }