/** * SMTP Server * Core implementation for the refactored SMTP server */ import * as plugins from '../../../plugins.js'; import { SmtpState } from './interfaces.js'; import type { ISmtpServerOptions } from './interfaces.js'; import type { ISmtpServer, ISmtpServerConfig, ISessionManager, IConnectionManager, ICommandHandler, IDataHandler, ITlsHandler, ISecurityHandler } from './interfaces.js'; import { SessionManager } from './session-manager.js'; import { ConnectionManager } from './connection-manager.js'; import { CommandHandler } from './command-handler.js'; import { DataHandler } from './data-handler.js'; import { TlsHandler } from './tls-handler.js'; import { SecurityHandler } from './security-handler.js'; import { SMTP_DEFAULTS } from './constants.js'; import { mergeWithDefaults } from './utils/helpers.js'; import { SmtpLogger } from './utils/logging.js'; import { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js'; /** * SMTP Server implementation * The main server class that coordinates all components */ export class SmtpServer implements ISmtpServer { /** * Email server reference */ private emailServer: UnifiedEmailServer; /** * Session manager */ private sessionManager: ISessionManager; /** * Connection manager */ private connectionManager: IConnectionManager; /** * Command handler */ private commandHandler: ICommandHandler; /** * Data handler */ private dataHandler: IDataHandler; /** * TLS handler */ private tlsHandler: ITlsHandler; /** * Security handler */ private securityHandler: ISecurityHandler; /** * SMTP server options */ private options: ISmtpServerOptions; /** * Net server instance */ private server: plugins.net.Server | null = null; /** * Secure server instance */ private secureServer: plugins.tls.Server | null = null; /** * Whether the server is running */ private running = false; /** * Creates a new SMTP server * @param config - Server configuration */ constructor(config: ISmtpServerConfig) { this.emailServer = config.emailServer; this.options = mergeWithDefaults(config.options); // Create components or use provided ones this.sessionManager = config.sessionManager || new SessionManager({ socketTimeout: this.options.socketTimeout, connectionTimeout: this.options.connectionTimeout, cleanupInterval: this.options.cleanupInterval }); this.securityHandler = config.securityHandler || new SecurityHandler( this.emailServer, undefined, // IP reputation service this.options.auth ); this.tlsHandler = config.tlsHandler || new TlsHandler( this.sessionManager, { key: this.options.key, cert: this.options.cert, ca: this.options.ca } ); this.dataHandler = config.dataHandler || new DataHandler( this.sessionManager, this.emailServer, { size: this.options.size } ); this.commandHandler = config.commandHandler || new CommandHandler( this.sessionManager, { hostname: this.options.hostname, size: this.options.size, maxRecipients: this.options.maxRecipients, auth: this.options.auth }, this.dataHandler, this.tlsHandler, this.securityHandler ); this.connectionManager = config.connectionManager || new ConnectionManager( this.sessionManager, (socket, line) => this.commandHandler.processCommand(socket, line), { hostname: this.options.hostname, maxConnections: this.options.maxConnections, socketTimeout: this.options.socketTimeout } ); } /** * Start the SMTP server * @returns Promise that resolves when server is started */ public async listen(): Promise { if (this.running) { throw new Error('SMTP server is already running'); } try { // Create the server this.server = plugins.net.createServer((socket) => { // Check IP reputation before handling connection this.securityHandler.checkIpReputation(socket) .then(allowed => { if (allowed) { this.connectionManager.handleNewConnection(socket); } else { // Close connection if IP is not allowed socket.destroy(); } }) .catch(error => { SmtpLogger.error(`IP reputation check error: ${error instanceof Error ? error.message : String(error)}`, { remoteAddress: socket.remoteAddress, error: error instanceof Error ? error : new Error(String(error)) }); // Allow connection on error (fail open) this.connectionManager.handleNewConnection(socket); }); }); // Set up error handling this.server.on('error', (err) => { SmtpLogger.error(`SMTP server error: ${err.message}`, { error: err }); }); // Start listening await new Promise((resolve, reject) => { if (!this.server) { reject(new Error('Server not initialized')); return; } this.server.listen(this.options.port, this.options.host, () => { SmtpLogger.info(`SMTP server listening on ${this.options.host || '0.0.0.0'}:${this.options.port}`); resolve(); }); this.server.on('error', reject); }); // Start secure server if configured if (this.options.securePort && this.tlsHandler.isTlsEnabled()) { this.secureServer = this.tlsHandler.createSecureServer(); if (this.secureServer) { this.secureServer.on('secureConnection', (socket) => { // Check IP reputation before handling connection this.securityHandler.checkIpReputation(socket) .then(allowed => { if (allowed) { this.connectionManager.handleNewSecureConnection(socket); } else { // Close connection if IP is not allowed socket.destroy(); } }) .catch(error => { SmtpLogger.error(`IP reputation check error: ${error instanceof Error ? error.message : String(error)}`, { remoteAddress: socket.remoteAddress, error: error instanceof Error ? error : new Error(String(error)) }); // Allow connection on error (fail open) this.connectionManager.handleNewSecureConnection(socket); }); }); this.secureServer.on('error', (err) => { SmtpLogger.error(`SMTP secure server error: ${err.message}`, { error: err }); }); // Start listening on secure port await new Promise((resolve, reject) => { if (!this.secureServer) { reject(new Error('Secure server not initialized')); return; } this.secureServer.listen(this.options.securePort, this.options.host, () => { SmtpLogger.info(`SMTP secure server listening on ${this.options.host || '0.0.0.0'}:${this.options.securePort}`); resolve(); }); this.secureServer.on('error', reject); }); } else { SmtpLogger.warn('Failed to create secure server, TLS may not be properly configured'); } } this.running = true; } catch (error) { SmtpLogger.error(`Failed to start SMTP server: ${error instanceof Error ? error.message : String(error)}`, { error: error instanceof Error ? error : new Error(String(error)) }); // Clean up on error this.close(); throw error; } } /** * Stop the SMTP server * @returns Promise that resolves when server is stopped */ public async close(): Promise { if (!this.running) { return; } SmtpLogger.info('Stopping SMTP server'); try { // Close all active connections this.connectionManager.closeAllConnections(); // Clear all sessions this.sessionManager.clearAllSessions(); // Close servers const closePromises: Promise[] = []; if (this.server) { closePromises.push( new Promise((resolve, reject) => { if (!this.server) { resolve(); return; } this.server.close((err) => { if (err) { reject(err); } else { resolve(); } }); }) ); } if (this.secureServer) { closePromises.push( new Promise((resolve, reject) => { if (!this.secureServer) { resolve(); return; } this.secureServer.close((err) => { if (err) { reject(err); } else { resolve(); } }); }) ); } await Promise.all(closePromises); this.server = null; this.secureServer = null; this.running = false; SmtpLogger.info('SMTP server stopped'); } catch (error) { SmtpLogger.error(`Error stopping SMTP server: ${error instanceof Error ? error.message : String(error)}`, { error: error instanceof Error ? error : new Error(String(error)) }); throw error; } } /** * Get the session manager * @returns Session manager instance */ public getSessionManager(): ISessionManager { return this.sessionManager; } /** * Get the connection manager * @returns Connection manager instance */ public getConnectionManager(): IConnectionManager { return this.connectionManager; } /** * Get the command handler * @returns Command handler instance */ public getCommandHandler(): ICommandHandler { return this.commandHandler; } /** * Get the data handler * @returns Data handler instance */ public getDataHandler(): IDataHandler { return this.dataHandler; } /** * Get the TLS handler * @returns TLS handler instance */ public getTlsHandler(): ITlsHandler { return this.tlsHandler; } /** * Get the security handler * @returns Security handler instance */ public getSecurityHandler(): ISecurityHandler { return this.securityHandler; } /** * Get the server options * @returns SMTP server options */ public getOptions(): ISmtpServerOptions { return this.options; } /** * Get the email server reference * @returns Email server instance */ public getEmailServer(): UnifiedEmailServer { return this.emailServer; } /** * Check if the server is running * @returns Whether the server is running */ public isRunning(): boolean { return this.running; } }