/** * SMTP TLS Handler * Responsible for handling TLS-related SMTP functionality */ import * as plugins from '../../../plugins.js'; import { SmtpResponseCode, SecurityEventType, SecurityLogLevel } from './constants.js'; import { SmtpLogger } from './utils/logging.js'; import { getSocketDetails, getTlsDetails } from './utils/helpers.js'; import { loadCertificatesFromString, generateSelfSignedCertificates, createTlsOptions } from './certificate-utils.js'; import { SmtpState } from '../interfaces.js'; /** * Handles TLS functionality for SMTP server */ export class TlsHandler { /** * Reference to the SMTP server instance */ smtpServer; /** * Certificate data */ certificates; /** * TLS options */ options; /** * Creates a new TLS handler * @param smtpServer - SMTP server instance */ constructor(smtpServer) { this.smtpServer = smtpServer; // Initialize certificates const serverOptions = this.smtpServer.getOptions(); try { // Try to load certificates from provided options this.certificates = loadCertificatesFromString({ key: serverOptions.key, cert: serverOptions.cert, ca: serverOptions.ca }); SmtpLogger.info('Successfully loaded TLS certificates'); } catch (error) { SmtpLogger.warn(`Failed to load certificates from options, using self-signed: ${error instanceof Error ? error.message : String(error)}`); // Fall back to self-signed certificates for testing this.certificates = generateSelfSignedCertificates(); } // Initialize TLS options this.options = createTlsOptions(this.certificates); } /** * Handle STARTTLS command * @param socket - Client socket */ async handleStartTls(socket, session) { // Check if already using TLS if (session.useTLS) { this.sendResponse(socket, `${SmtpResponseCode.BAD_SEQUENCE} TLS already active`); return null; } // Check if we have the necessary TLS certificates if (!this.isTlsEnabled()) { this.sendResponse(socket, `${SmtpResponseCode.TLS_UNAVAILABLE_TEMP} TLS not available`); return null; } // Send ready for TLS response this.sendResponse(socket, `${SmtpResponseCode.SERVICE_READY} Ready to start TLS`); // Upgrade the connection to TLS try { const tlsSocket = await this.startTLS(socket); return tlsSocket; } catch (error) { SmtpLogger.error(`STARTTLS negotiation failed: ${error instanceof Error ? error.message : String(error)}`, { sessionId: session.id, remoteAddress: session.remoteAddress, error: error instanceof Error ? error : new Error(String(error)) }); // Log security event SmtpLogger.logSecurityEvent(SecurityLogLevel.ERROR, SecurityEventType.TLS_NEGOTIATION, 'STARTTLS negotiation failed', { error: error instanceof Error ? error.message : String(error) }, session.remoteAddress); return null; } } /** * Upgrade a connection to TLS * @param socket - Client socket */ async startTLS(socket) { // Get the session for this socket const session = this.smtpServer.getSessionManager().getSession(socket); try { // Import the enhanced STARTTLS handler // This uses a more robust approach to TLS upgrades const { performStartTLS } = await import('./starttls-handler.js'); SmtpLogger.info('Using enhanced STARTTLS implementation'); // Use the enhanced STARTTLS handler with better error handling and socket management const serverOptions = this.smtpServer.getOptions(); const tlsSocket = await performStartTLS(socket, { key: serverOptions.key, cert: serverOptions.cert, ca: serverOptions.ca, session: session, sessionManager: this.smtpServer.getSessionManager(), connectionManager: this.smtpServer.getConnectionManager(), // Callback for successful upgrade onSuccess: (secureSocket) => { SmtpLogger.info('TLS connection successfully established via enhanced STARTTLS', { remoteAddress: secureSocket.remoteAddress, remotePort: secureSocket.remotePort, protocol: secureSocket.getProtocol() || 'unknown', cipher: secureSocket.getCipher()?.name || 'unknown' }); // Log security event SmtpLogger.logSecurityEvent(SecurityLogLevel.INFO, SecurityEventType.TLS_NEGOTIATION, 'STARTTLS successful with enhanced implementation', { protocol: secureSocket.getProtocol(), cipher: secureSocket.getCipher()?.name }, secureSocket.remoteAddress, undefined, true); }, // Callback for failed upgrade onFailure: (error) => { SmtpLogger.error(`Enhanced STARTTLS failed: ${error.message}`, { sessionId: session?.id, remoteAddress: socket.remoteAddress, error }); // Log security event SmtpLogger.logSecurityEvent(SecurityLogLevel.ERROR, SecurityEventType.TLS_NEGOTIATION, 'Enhanced STARTTLS failed', { error: error.message }, socket.remoteAddress, undefined, false); }, // Function to update session state updateSessionState: this.smtpServer.getSessionManager().updateSessionState?.bind(this.smtpServer.getSessionManager()) }); // If STARTTLS failed with the enhanced implementation, log the error if (!tlsSocket) { SmtpLogger.warn('Enhanced STARTTLS implementation failed to create TLS socket', { sessionId: session?.id, remoteAddress: socket.remoteAddress }); throw new Error('Failed to create TLS socket'); } return tlsSocket; } catch (error) { // Log STARTTLS failure SmtpLogger.error(`Failed to upgrade connection to TLS: ${error instanceof Error ? error.message : String(error)}`, { remoteAddress: socket.remoteAddress, remotePort: socket.remotePort, error: error instanceof Error ? error : new Error(String(error)), stack: error instanceof Error ? error.stack : 'No stack trace available' }); // Log security event SmtpLogger.logSecurityEvent(SecurityLogLevel.ERROR, SecurityEventType.TLS_NEGOTIATION, 'Failed to upgrade connection to TLS', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : 'No stack trace available' }, socket.remoteAddress, undefined, false); // Destroy the socket on error socket.destroy(); throw error; } } /** * Create a secure server * @returns TLS server instance or undefined if TLS is not enabled */ createSecureServer() { if (!this.isTlsEnabled()) { return undefined; } try { SmtpLogger.info('Creating secure TLS server'); // Log certificate info SmtpLogger.debug('Using certificates for secure server', { keyLength: this.certificates.key.length, certLength: this.certificates.cert.length, caLength: this.certificates.ca ? this.certificates.ca.length : 0 }); // Create TLS options using our certificate utilities // This ensures proper PEM format handling and protocol negotiation const tlsOptions = createTlsOptions(this.certificates, true); // Use server options SmtpLogger.info('Creating TLS server with options', { minVersion: tlsOptions.minVersion, maxVersion: tlsOptions.maxVersion, handshakeTimeout: tlsOptions.handshakeTimeout }); // Create a server with wider TLS compatibility const server = new plugins.tls.Server(tlsOptions); // Add error handling server.on('error', (err) => { SmtpLogger.error(`TLS server error: ${err.message}`, { error: err, stack: err.stack }); }); // Log TLS details for each connection server.on('secureConnection', (socket) => { SmtpLogger.info('New secure connection established', { protocol: socket.getProtocol(), cipher: socket.getCipher()?.name, remoteAddress: socket.remoteAddress, remotePort: socket.remotePort }); }); return server; } catch (error) { SmtpLogger.error(`Failed to create secure server: ${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' }); return undefined; } } /** * Check if TLS is enabled * @returns Whether TLS is enabled */ isTlsEnabled() { const options = this.smtpServer.getOptions(); return !!(options.key && options.cert); } /** * Send a response to the client * @param socket - Client socket * @param response - Response message */ sendResponse(socket, response) { // Check if socket is still writable before attempting to write if (socket.destroyed || socket.readyState !== 'open' || !socket.writable) { SmtpLogger.debug(`Skipping response to closed/destroyed socket: ${response}`, { remoteAddress: socket.remoteAddress, remotePort: socket.remotePort, destroyed: socket.destroyed, readyState: socket.readyState, writable: socket.writable }); return; } try { socket.write(`${response}\r\n`); SmtpLogger.logResponse(response, socket); } catch (error) { SmtpLogger.error(`Error sending response: ${error instanceof Error ? error.message : String(error)}`, { response, remoteAddress: socket.remoteAddress, remotePort: socket.remotePort, error: error instanceof Error ? error : new Error(String(error)) }); socket.destroy(); } } /** * Check if TLS is available (interface requirement) */ isTlsAvailable() { return this.isTlsEnabled(); } /** * Get TLS options (interface requirement) */ getTlsOptions() { return this.options; } /** * Clean up resources */ destroy() { // Clear any cached certificates or TLS contexts // TlsHandler doesn't have timers but may have cached resources SmtpLogger.debug('TlsHandler destroyed'); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGxzLWhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9tYWlsL2RlbGl2ZXJ5L3NtdHBzZXJ2ZXIvdGxzLWhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUUvQyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsaUJBQWlCLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUN2RixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDaEQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLGFBQWEsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ3JFLE9BQU8sRUFDTCwwQkFBMEIsRUFDMUIsOEJBQThCLEVBQzlCLGdCQUFnQixFQUVqQixNQUFNLHdCQUF3QixDQUFDO0FBQ2hDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUU3Qzs7R0FFRztBQUNILE1BQU0sT0FBTyxVQUFVO0lBQ3JCOztPQUVHO0lBQ0ssVUFBVSxDQUFjO0lBRWhDOztPQUVHO0lBQ0ssWUFBWSxDQUFtQjtJQUV2Qzs7T0FFRztJQUNLLE9BQU8sQ0FBeUI7SUFFeEM7OztPQUdHO0lBQ0gsWUFBWSxVQUF1QjtRQUNqQyxJQUFJLENBQUMsVUFBVSxHQUFHLFVBQVUsQ0FBQztRQUU3QiwwQkFBMEI7UUFDMUIsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNuRCxJQUFJLENBQUM7WUFDSCxpREFBaUQ7WUFDakQsSUFBSSxDQUFDLFlBQVksR0FBRywwQkFBMEIsQ0FBQztnQkFDN0MsR0FBRyxFQUFFLGFBQWEsQ0FBQyxHQUFHO2dCQUN0QixJQUFJLEVBQUUsYUFBYSxDQUFDLElBQUk7Z0JBQ3hCLEVBQUUsRUFBRSxhQUFhLENBQUMsRUFBRTthQUNyQixDQUFDLENB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