/** * STARTTLS Implementation * Provides an improved implementation for STARTTLS upgrades */ import * as plugins from '../../../plugins.js'; import { SmtpLogger } from './utils/logging.js'; import { loadCertificatesFromString, createTlsOptions } from './certificate-utils.js'; import { getSocketDetails } from './utils/helpers.js'; import { SmtpState } from '../interfaces.js'; /** * Enhanced STARTTLS handler for more reliable TLS upgrades */ export async function performStartTLS(socket, options) { return new Promise((resolve) => { try { const socketDetails = getSocketDetails(socket); SmtpLogger.info('Starting enhanced STARTTLS upgrade process', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); // Create a proper socket cleanup function const cleanupSocket = () => { // Remove all listeners to prevent memory leaks socket.removeAllListeners('data'); socket.removeAllListeners('error'); socket.removeAllListeners('close'); socket.removeAllListeners('end'); socket.removeAllListeners('drain'); }; // Prepare the socket for TLS upgrade socket.setNoDelay(true); // Critical: make sure there's no pending data before TLS handshake socket.pause(); // Add error handling for the base socket const handleSocketError = (err) => { SmtpLogger.error(`Socket error during STARTTLS preparation: ${err.message}`, { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort, error: err, stack: err.stack }); if (options.onFailure) { options.onFailure(err); } // Resolve with undefined to indicate failure resolve(undefined); }; socket.once('error', handleSocketError); // Load certificates let certificates; try { certificates = loadCertificatesFromString({ key: options.key, cert: options.cert, ca: options.ca }); } catch (certError) { SmtpLogger.error(`Certificate error during STARTTLS: ${certError instanceof Error ? certError.message : String(certError)}`); if (options.onFailure) { options.onFailure(certError instanceof Error ? certError : new Error(String(certError))); } resolve(undefined); return; } // Create TLS options optimized for STARTTLS const tlsOptions = createTlsOptions(certificates, true); // Create secure context let secureContext; try { secureContext = plugins.tls.createSecureContext(tlsOptions); } catch (contextError) { SmtpLogger.error(`Failed to create secure context: ${contextError instanceof Error ? contextError.message : String(contextError)}`); if (options.onFailure) { options.onFailure(contextError instanceof Error ? contextError : new Error(String(contextError))); } resolve(undefined); return; } // Log STARTTLS upgrade attempt SmtpLogger.debug('Attempting TLS socket upgrade with options', { minVersion: tlsOptions.minVersion, maxVersion: tlsOptions.maxVersion, handshakeTimeout: tlsOptions.handshakeTimeout }); // Use a safer approach to create the TLS socket const handshakeTimeout = 30000; // 30 seconds timeout for TLS handshake let handshakeTimeoutId; // Create the TLS socket using a conservative approach for STARTTLS const tlsSocket = new plugins.tls.TLSSocket(socket, { isServer: true, secureContext, // Server-side options (simpler is more reliable for STARTTLS) requestCert: false, rejectUnauthorized: false }); // Set up error handling for the TLS socket tlsSocket.once('error', (err) => { if (handshakeTimeoutId) { clearTimeout(handshakeTimeoutId); } SmtpLogger.error(`TLS error during STARTTLS: ${err.message}`, { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort, error: err, stack: err.stack }); // Clean up socket listeners cleanupSocket(); if (options.onFailure) { options.onFailure(err); } // Destroy the socket to ensure we don't have hanging connections tlsSocket.destroy(); resolve(undefined); }); // Set up handshake timeout manually for extra safety handshakeTimeoutId = setTimeout(() => { SmtpLogger.error('TLS handshake timed out', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); // Clean up socket listeners cleanupSocket(); if (options.onFailure) { options.onFailure(new Error('TLS handshake timed out')); } // Destroy the socket to ensure we don't have hanging connections tlsSocket.destroy(); resolve(undefined); }, handshakeTimeout); // Set up handler for successful TLS negotiation tlsSocket.once('secure', () => { if (handshakeTimeoutId) { clearTimeout(handshakeTimeoutId); } const protocol = tlsSocket.getProtocol(); const cipher = tlsSocket.getCipher(); SmtpLogger.info('TLS upgrade successful via STARTTLS', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort, protocol: protocol || 'unknown', cipher: cipher?.name || 'unknown' }); // Update socket mapping in session manager if (options.sessionManager) { const socketReplaced = options.sessionManager.replaceSocket(socket, tlsSocket); if (!socketReplaced) { SmtpLogger.error('Failed to replace socket in session manager after STARTTLS', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); } } // Re-attach event handlers from connection manager if (options.connectionManager) { try { options.connectionManager.setupSocketEventHandlers(tlsSocket); SmtpLogger.debug('Successfully re-attached connection manager event handlers to TLS socket', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); } catch (handlerError) { SmtpLogger.error('Failed to re-attach event handlers to TLS socket after STARTTLS', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort, error: handlerError instanceof Error ? handlerError : new Error(String(handlerError)) }); } } // Update session if provided if (options.session) { // Update session properties to indicate TLS is active options.session.useTLS = true; options.session.secure = true; // Reset session state as required by RFC 3207 // After STARTTLS, client must issue a new EHLO if (options.updateSessionState) { options.updateSessionState(options.session, SmtpState.GREETING); } } // Call success callback if provided if (options.onSuccess) { options.onSuccess(tlsSocket); } // Success - return the TLS socket resolve(tlsSocket); }); // Resume the socket after we've set up all handlers // This allows the TLS handshake to proceed socket.resume(); } catch (error) { SmtpLogger.error(`Unexpected error in STARTTLS: ${error instanceof Error ? error.message : String(error)}`, { error: error instanceof Error ? error : new Error(String(error)), stack: error instanceof Error ? error.stack : 'No stack trace available' }); if (options.onFailure) { options.onFailure(error instanceof Error ? error : new Error(String(error))); } resolve(undefined); } }); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhcnR0bHMtaGFuZGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzL21haWwvZGVsaXZlcnkvc210cHNlcnZlci9zdGFydHRscy1oYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7R0FHRztBQUVILE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2hELE9BQU8sRUFDTCwwQkFBMEIsRUFDMUIsZ0JBQWdCLEVBRWpCLE1BQU0sd0JBQXdCLENBQUM7QUFDaEMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFdEQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdDOztHQUVHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQ25DLE1BQTBCLEVBQzFCLE9BVUM7SUFFRCxPQUFPLElBQUksT0FBTyxDQUFvQyxDQUFDLE9BQU8sRUFBRSxFQUFFO1FBQ2hFLElBQUksQ0FBQztZQUNILE1BQU0sYUFBYSxHQUFHLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBRS9DLFVBQVUsQ0FBQyxJQUFJLENBQUMsNENBQTRDLEVBQUU7Z0JBQzVELGFBQWEsRUFBRSxhQUFhLENBQUMsYUFBYTtnQkFDMUMsVUFBVSxFQUFFLGFBQWEsQ0FBQyxVQUFVO2FBQ3JDLENBQUMsQ0FBQztZQUVILDBDQUEwQztZQUMxQyxNQUFNLGFBQWEsR0FBRyxHQUFHLEVBQUU7Z0JBQ3pCLCtDQUErQztnQkFDL0MsTUFBTSxDQUFDLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUNsQyxNQUFNLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLENBQUM7Z0JBQ25DLE1BQU0sQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztnQkFDbkMsTUFBTSxDQUFDLGtCQUFrQixD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