/** * STARTTLS Implementation using Deno Native TLS * Uses Deno.startTls() for reliable TLS upgrades */ import * as plugins from '../../../plugins.ts'; import { SmtpLogger } from './utils/logging.ts'; import { getSocketDetails } from './utils/helpers.ts'; import { ConnectionWrapper } from './utils/connection-wrapper.ts'; import type { ISmtpSession, ISessionManager, IConnectionManager } from './interfaces.ts'; import { SmtpState } from '../interfaces.ts'; /** * Perform STARTTLS using Deno's native TLS implementation * This replaces the broken Node.js TLS compatibility layer */ export async function performStartTLS( socket: plugins.net.Socket, options: { key: string; cert: string; ca?: string; session?: ISmtpSession; sessionManager?: ISessionManager; connectionManager?: IConnectionManager; onSuccess?: (tlsSocket: plugins.tls.TLSSocket | ConnectionWrapper) => void; onFailure?: (error: Error) => void; updateSessionState?: (session: ISmtpSession, state: SmtpState) => void; } ): Promise { return new Promise(async (resolve) => { try { const socketDetails = getSocketDetails(socket); SmtpLogger.info('Starting Deno-native STARTTLS upgrade process', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); // Check if this is a ConnectionWrapper (Deno.Conn based) if (socket instanceof ConnectionWrapper) { SmtpLogger.info('Using Deno-native STARTTLS implementation for ConnectionWrapper'); // Get the underlying Deno.Conn const denoConn = socket.getDenoConn(); // Set up timeout for TLS handshake const handshakeTimeout = 30000; // 30 seconds const timeoutId = setTimeout(() => { const error = new Error('TLS handshake timed out'); SmtpLogger.error(error.message, { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); if (options.onFailure) { options.onFailure(error); } resolve(undefined); }, handshakeTimeout); try { // Write cert and key to temporary files for Deno.startTls() const tempDir = await Deno.makeTempDir(); const certFile = `${tempDir}/cert.pem`; const keyFile = `${tempDir}/key.pem`; try { await Deno.writeTextFile(certFile, options.cert); await Deno.writeTextFile(keyFile, options.key); // Upgrade connection to TLS using Deno's native API const tlsConn = await Deno.startTls(denoConn, { hostname: 'localhost', // Server-side TLS doesn't need hostname validation certFile, keyFile, alpnProtocols: ['smtp'], }); clearTimeout(timeoutId); SmtpLogger.info('TLS upgrade successful via Deno-native STARTTLS', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); // Replace the underlying connection in the wrapper socket.replaceConnection(tlsConn); // Update socket mapping in session manager if (options.sessionManager) { // Socket wrapper remains the same, just upgraded to TLS const socketReplaced = options.sessionManager.replaceSocket(socket as any, socket as any); if (!socketReplaced) { SmtpLogger.warn('Socket already tracked in session manager', { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); } } // Re-attach event handlers from connection manager if needed if (options.connectionManager) { try { options.connectionManager.setupSocketEventHandlers(socket as any); 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(socket); } // Success - return the wrapper with upgraded TLS connection resolve(socket); } finally { // Clean up temporary files try { await Deno.remove(tempDir, { recursive: true }); } catch { // Ignore cleanup errors } } } catch (tlsError) { clearTimeout(timeoutId); const error = tlsError instanceof Error ? tlsError : new Error(String(tlsError)); SmtpLogger.error(`Deno TLS upgrade failed: ${error.message}`, { remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort, error, stack: error.stack }); if (options.onFailure) { options.onFailure(error); } resolve(undefined); } } else { // Fallback: This should not happen since all connections are now ConnectionWrapper SmtpLogger.error('STARTTLS called on non-ConnectionWrapper socket - this should not happen', { socketType: socket.constructor.name, remoteAddress: socketDetails.remoteAddress, remotePort: socketDetails.remotePort }); const error = new Error('STARTTLS requires ConnectionWrapper (Deno.Conn based socket)'); if (options.onFailure) { options.onFailure(error); } resolve(undefined); } } catch (error) { SmtpLogger.error(`Unexpected error in Deno-native 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); } }); }