This commit is contained in:
2025-05-23 00:06:07 +00:00
parent f058b2d1e7
commit 4905595cbb
7 changed files with 351 additions and 99 deletions

View File

@@ -35,7 +35,7 @@ export class CommandHandler implements ICommandHandler {
* @param socket - Client socket * @param socket - Client socket
* @param commandLine - Command line from client * @param commandLine - Command line from client
*/ */
public processCommand(socket: plugins.net.Socket | plugins.tls.TLSSocket, commandLine: string): void { public async processCommand(socket: plugins.net.Socket | plugins.tls.TLSSocket, commandLine: string): Promise<void> {
// Get the session for this socket // Get the session for this socket
const session = this.smtpServer.getSessionManager().getSession(socket); const session = this.smtpServer.getSessionManager().getSession(socket);
if (!session) { if (!session) {
@@ -216,7 +216,7 @@ export class CommandHandler implements ICommandHandler {
case SmtpCommand.STARTTLS: case SmtpCommand.STARTTLS:
const tlsHandler = this.smtpServer.getTlsHandler(); const tlsHandler = this.smtpServer.getTlsHandler();
if (tlsHandler && tlsHandler.isTlsEnabled()) { if (tlsHandler && tlsHandler.isTlsEnabled()) {
tlsHandler.handleStartTls(socket); await tlsHandler.handleStartTls(socket, session);
} else { } else {
SmtpLogger.warn('STARTTLS requested but TLS is not enabled', { SmtpLogger.warn('STARTTLS requested but TLS is not enabled', {
remoteAddress: socket.remoteAddress, remoteAddress: socket.remoteAddress,
@@ -1018,6 +1018,48 @@ export class CommandHandler implements ICommandHandler {
return isValidCommandSequence(command, session.state); return isValidCommandSequence(command, session.state);
} }
/**
* Handle an SMTP command (interface requirement)
*/
public async handleCommand(
socket: plugins.net.Socket | plugins.tls.TLSSocket,
command: SmtpCommand,
args: string,
session: ISmtpSession
): Promise<void> {
// Delegate to processCommand for now
this.processCommand(socket, `${command} ${args}`.trim());
}
/**
* Get supported commands for current session state (interface requirement)
*/
public getSupportedCommands(session: ISmtpSession): SmtpCommand[] {
const commands: SmtpCommand[] = [SmtpCommand.NOOP, SmtpCommand.QUIT, SmtpCommand.RSET];
switch (session.state) {
case SmtpState.GREETING:
commands.push(SmtpCommand.EHLO, SmtpCommand.HELO);
break;
case SmtpState.AFTER_EHLO:
commands.push(SmtpCommand.MAIL_FROM, SmtpCommand.STARTTLS);
if (!session.authenticated) {
commands.push(SmtpCommand.AUTH);
}
break;
case SmtpState.MAIL_FROM:
commands.push(SmtpCommand.RCPT_TO);
break;
case SmtpState.RCPT_TO:
commands.push(SmtpCommand.RCPT_TO, SmtpCommand.DATA);
break;
default:
break;
}
return commands;
}
/** /**
* Clean up resources * Clean up resources
*/ */

View File

@@ -281,7 +281,7 @@ export class ConnectionManager implements IConnectionManager {
* Handle a new connection with resource management * Handle a new connection with resource management
* @param socket - Client socket * @param socket - Client socket
*/ */
public handleNewConnection(socket: plugins.net.Socket): void { public async handleNewConnection(socket: plugins.net.Socket): Promise<void> {
// Update connection stats // Update connection stats
this.connectionStats.totalConnections++; this.connectionStats.totalConnections++;
this.connectionStats.activeConnections = this.activeConnections.size + 1; this.connectionStats.activeConnections = this.activeConnections.size + 1;
@@ -437,7 +437,7 @@ export class ConnectionManager implements IConnectionManager {
* Handle a new secure TLS connection with resource management * Handle a new secure TLS connection with resource management
* @param socket - Client TLS socket * @param socket - Client TLS socket
*/ */
public handleNewSecureConnection(socket: plugins.tls.TLSSocket): void { public async handleNewSecureConnection(socket: plugins.tls.TLSSocket): Promise<void> {
// Update connection stats // Update connection stats
this.connectionStats.totalConnections++; this.connectionStats.totalConnections++;
this.connectionStats.activeConnections = this.activeConnections.size + 1; this.connectionStats.activeConnections = this.activeConnections.size + 1;
@@ -961,6 +961,24 @@ export class ConnectionManager implements IConnectionManager {
} }
} }
/**
* Handle a new connection (interface requirement)
*/
public async handleConnection(socket: plugins.net.Socket | plugins.tls.TLSSocket, secure: boolean): Promise<void> {
if (secure) {
this.handleNewSecureConnection(socket as plugins.tls.TLSSocket);
} else {
this.handleNewConnection(socket as plugins.net.Socket);
}
}
/**
* Check if accepting new connections (interface requirement)
*/
public canAcceptConnection(): boolean {
return !this.hasReachedMaxConnections();
}
/** /**
* Clean up resources * Clean up resources
*/ */
@@ -976,8 +994,18 @@ export class ConnectionManager implements IConnectionManager {
// Clear maps // Clear maps
this.activeConnections.clear(); this.activeConnections.clear();
this.connectionTimestamps.clear(); this.ipConnections.clear();
this.ipConnectionCounts.clear();
// Reset connection stats
this.connectionStats = {
totalConnections: 0,
activeConnections: 0,
peakConnections: 0,
rejectedConnections: 0,
closedConnections: 0,
erroredConnections: 0,
timedOutConnections: 0
};
SmtpLogger.debug('ConnectionManager destroyed'); SmtpLogger.debug('ConnectionManager destroyed');
} }

View File

@@ -189,46 +189,79 @@ export class DataHandler implements IDataHandler {
/** /**
* Process a complete email * Process a complete email
* @param rawData - Raw email data
* @param session - SMTP session
* @returns Promise that resolves with the Email object
*/
public async processEmail(rawData: string, session: ISmtpSession): Promise<Email> {
// Clean up the raw email data
let cleanedData = rawData;
// Remove trailing end-of-data marker: various formats
cleanedData = cleanedData
.replace(/\r\n\.\r\n$/, '')
.replace(/\n\.\r\n$/, '')
.replace(/\r\n\.\n$/, '')
.replace(/\n\.\n$/, '')
.replace(/^\.$/, ''); // Handle ONLY a lone dot as the entire content (not trailing dots)
// Remove dot-stuffing (RFC 5321, section 4.5.2)
cleanedData = cleanedData.replace(/\r\n\.\./g, '\r\n.');
try {
// Parse email into Email object using cleaned data
const email = await this.parseEmailFromData(cleanedData, session);
// Return the parsed email
return email;
} catch (error) {
SmtpLogger.error(`Failed to parse email: ${error instanceof Error ? error.message : String(error)}`, {
sessionId: session.id,
error: error instanceof Error ? error : new Error(String(error))
});
// Create a minimal email object on error
const fallbackEmail = new Email();
fallbackEmail.setFromRawData(cleanedData);
return fallbackEmail;
}
}
/**
* Parse email from raw data
* @param rawData - Raw email data
* @param session - SMTP session
* @returns Email object
*/
private async parseEmailFromData(rawData: string, session: ISmtpSession): Promise<Email> {
const email = new Email();
// Set raw data
email.setFromRawData(rawData);
// Set envelope information from session
if (session.mailFrom) {
email.setFrom(session.mailFrom);
}
if (session.rcptTo && session.rcptTo.length > 0) {
for (const recipient of session.rcptTo) {
email.addTo(recipient);
}
}
return email;
}
/**
* Process a complete email (legacy method)
* @param session - SMTP session * @param session - SMTP session
* @returns Promise that resolves with the result of the transaction * @returns Promise that resolves with the result of the transaction
*/ */
public async processEmail(session: ISmtpSession): Promise<ISmtpTransactionResult> { public async processEmailLegacy(session: ISmtpSession): Promise<ISmtpTransactionResult> {
const isLargeMessage = (session.emailDataSize || 0) > 100 * 1024; // 100KB threshold
// For large messages, process chunks efficiently to avoid memory issues
if (isLargeMessage) {
session.emailData = this.processEmailDataStreaming(session.emailDataChunks || []);
// Clear chunks immediately after processing to free memory
session.emailDataChunks = [];
session.emailDataSize = 0;
// Force garbage collection for large messages
if (global.gc) {
global.gc();
}
} else {
// For smaller messages, use the simpler approach
session.emailData = (session.emailDataChunks || []).join('');
// Remove trailing end-of-data marker: various formats
session.emailData = session.emailData
.replace(/\r\n\.\r\n$/, '')
.replace(/\n\.\r\n$/, '')
.replace(/\r\n\.\n$/, '')
.replace(/\n\.\n$/, '')
.replace(/^\.$/, ''); // Handle ONLY a lone dot as the entire content (not trailing dots)
// Remove dot-stuffing (RFC 5321, section 4.5.2)
session.emailData = session.emailData.replace(/\r\n\.\./g, '\r\n.');
// Clear chunks after processing
session.emailDataChunks = [];
}
try { try {
// Parse email into Email object // Use the email data from session
const email = await this.parseEmail(session); const email = await this.parseEmailFromData(session.emailData || '', session);
// Process the email based on the processing mode // Process the email based on the processing mode
const processingMode = session.processingMode || 'mta'; const processingMode = session.processingMode || 'mta';
@@ -1195,6 +1228,18 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
}, 100); // Short delay before retry }, 100); // Short delay before retry
} }
/**
* Handle email data (interface requirement)
*/
public async handleData(
socket: plugins.net.Socket | plugins.tls.TLSSocket,
data: string,
session: ISmtpSession
): Promise<void> {
// Delegate to existing method
await this.handleDataReceived(socket, data);
}
/** /**
* Clean up resources * Clean up resources
*/ */

View File

@@ -7,9 +7,11 @@ import * as plugins from '../../../plugins.js';
import type { Email } from '../../core/classes.email.js'; import type { Email } from '../../core/classes.email.js';
import type { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js'; import type { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js';
// Re-export types from other modules // Re-export types from other modules
export { SmtpState } from '../interfaces.js'; import { SmtpState } from '../interfaces.js';
export type { SmtpCommand } from './constants.js'; import { SmtpCommand } from './constants.js';
export { SmtpState, SmtpCommand };
export type { IEnvelopeRecipient } from '../interfaces.js';
/** /**
* Interface for components that need cleanup * Interface for components that need cleanup
@@ -104,7 +106,7 @@ export interface ISmtpSession {
/** /**
* Last activity timestamp * Last activity timestamp
*/ */
lastActivity: Date; lastActivity: number;
/** /**
* Client's IP address * Client's IP address
@@ -165,6 +167,51 @@ export interface ISmtpSession {
* TLS options for this session * TLS options for this session
*/ */
tlsOptions?: any; tlsOptions?: any;
/**
* Whether TLS is being used
*/
useTLS?: boolean;
/**
* Mail from address for this transaction
*/
mailFrom?: string;
/**
* Recipients for this transaction
*/
rcptTo?: string[];
/**
* Email data being received
*/
emailData?: string;
/**
* Chunks of email data
*/
emailDataChunks?: string[];
/**
* Timeout ID for data reception
*/
dataTimeoutId?: NodeJS.Timeout;
/**
* Whether connection has ended
*/
connectionEnded?: boolean;
/**
* Size of email data being received
*/
emailDataSize?: number;
/**
* Processing mode for this session
*/
processingMode?: string;
} }
/** /**
@@ -174,7 +221,7 @@ export interface ISessionManager extends IDestroyable {
/** /**
* Create a new session for a socket * Create a new session for a socket
*/ */
createSession(socket: plugins.net.Socket | plugins.tls.TLSSocket): ISmtpSession; createSession(socket: plugins.net.Socket | plugins.tls.TLSSocket, secure?: boolean): ISmtpSession;
/** /**
* Get session by socket * Get session by socket
@@ -184,7 +231,7 @@ export interface ISessionManager extends IDestroyable {
/** /**
* Update session state * Update session state
*/ */
updateSessionState(socket: plugins.net.Socket | plugins.tls.TLSSocket, newState: SmtpState): void; updateSessionState(session: ISmtpSession, newState: SmtpState): void;
/** /**
* Remove a session * Remove a session
@@ -215,6 +262,16 @@ export interface ISessionManager extends IDestroyable {
* Check for timed out sessions * Check for timed out sessions
*/ */
checkTimeouts(timeoutMs: number): ISmtpSession[]; checkTimeouts(timeoutMs: number): ISmtpSession[];
/**
* Update session activity timestamp
*/
updateSessionActivity(session: ISmtpSession): void;
/**
* Replace socket in session (for TLS upgrade)
*/
replaceSocket(oldSocket: plugins.net.Socket | plugins.tls.TLSSocket, newSocket: plugins.net.Socket | plugins.tls.TLSSocket): boolean;
} }
/** /**
@@ -240,6 +297,21 @@ export interface IConnectionManager extends IDestroyable {
* Check if accepting new connections * Check if accepting new connections
*/ */
canAcceptConnection(): boolean; canAcceptConnection(): boolean;
/**
* Handle new connection (legacy method name)
*/
handleNewConnection(socket: plugins.net.Socket): Promise<void>;
/**
* Handle new secure connection (legacy method name)
*/
handleNewSecureConnection(socket: plugins.tls.TLSSocket): Promise<void>;
/**
* Setup socket event handlers
*/
setupSocketEventHandlers(socket: plugins.net.Socket | plugins.tls.TLSSocket): void;
} }
/** /**
@@ -260,6 +332,11 @@ export interface ICommandHandler extends IDestroyable {
* Get supported commands for current session state * Get supported commands for current session state
*/ */
getSupportedCommands(session: ISmtpSession): SmtpCommand[]; getSupportedCommands(session: ISmtpSession): SmtpCommand[];
/**
* Process command (legacy method name)
*/
processCommand(socket: plugins.net.Socket | plugins.tls.TLSSocket, command: string): Promise<void>;
} }
/** /**
@@ -282,6 +359,16 @@ export interface IDataHandler extends IDestroyable {
rawData: string, rawData: string,
session: ISmtpSession session: ISmtpSession
): Promise<Email>; ): Promise<Email>;
/**
* Handle data received (legacy method name)
*/
handleDataReceived(socket: plugins.net.Socket | plugins.tls.TLSSocket, data: string): Promise<void>;
/**
* Process email data (legacy method name)
*/
processEmailData(socket: plugins.net.Socket | plugins.tls.TLSSocket, data: string): Promise<void>;
} }
/** /**
@@ -305,6 +392,11 @@ export interface ITlsHandler extends IDestroyable {
* Get TLS options * Get TLS options
*/ */
getTlsOptions(): plugins.tls.TlsOptions; getTlsOptions(): plugins.tls.TlsOptions;
/**
* Check if TLS is enabled
*/
isTlsEnabled(): boolean;
} }
/** /**
@@ -341,6 +433,16 @@ export interface ISmtpServerOptions {
*/ */
hostname: string; hostname: string;
/**
* Host to bind to (optional, defaults to 0.0.0.0)
*/
host?: string;
/**
* Secure port for TLS connections
*/
securePort?: number;
/** /**
* TLS/SSL private key (PEM format) * TLS/SSL private key (PEM format)
*/ */

View File

@@ -159,13 +159,11 @@ export class SecurityHandler implements ISecurityHandler {
/** /**
* Validate authentication credentials * Validate authentication credentials
* @param session - SMTP session * @param auth - Authentication credentials
* @param username - Username
* @param password - Password
* @param method - Authentication method
* @returns Promise that resolves to true if authenticated * @returns Promise that resolves to true if authenticated
*/ */
public async authenticate(session: ISmtpSession, username: string, password: string, method: string): Promise<boolean> { public async authenticate(auth: ISmtpAuth): Promise<boolean> {
const { username, password } = auth;
// Get auth options from server // Get auth options from server
const options = this.smtpServer.getOptions(); const options = this.smtpServer.getOptions();
const authOptions = options.auth; const authOptions = options.auth;
@@ -176,35 +174,14 @@ export class SecurityHandler implements ISecurityHandler {
SecurityEventType.AUTHENTICATION, SecurityEventType.AUTHENTICATION,
SecurityLogLevel.WARN, SecurityLogLevel.WARN,
'Authentication attempt when auth is disabled', 'Authentication attempt when auth is disabled',
{ username, method, sessionId: session.id, ip: session.remoteAddress } { username }
); );
return false; return false;
} }
// Check if method is supported // Note: Method validation and TLS requirement checks would need to be done
if (!authOptions.methods.includes(method as any)) { // at the caller level since the interface doesn't include session/method info
this.logSecurityEvent(
SecurityEventType.AUTHENTICATION,
SecurityLogLevel.WARN,
`Unsupported authentication method: ${method}`,
{ username, method, sessionId: session.id, ip: session.remoteAddress }
);
return false;
}
// Check if TLS is active (should be required for auth)
if (!session.useTLS) {
this.logSecurityEvent(
SecurityEventType.AUTHENTICATION,
SecurityLogLevel.WARN,
'Authentication attempt without TLS',
{ username, method, sessionId: session.id, ip: session.remoteAddress }
);
return false;
}
try { try {
let authenticated = false; let authenticated = false;
@@ -222,7 +199,7 @@ export class SecurityHandler implements ISecurityHandler {
SecurityEventType.AUTHENTICATION, SecurityEventType.AUTHENTICATION,
authenticated ? SecurityLogLevel.INFO : SecurityLogLevel.WARN, authenticated ? SecurityLogLevel.INFO : SecurityLogLevel.WARN,
authenticated ? 'Authentication successful' : 'Authentication failed', authenticated ? 'Authentication successful' : 'Authentication failed',
{ username, method, sessionId: session.id, ip: session.remoteAddress } { username }
); );
return authenticated; return authenticated;
@@ -232,7 +209,7 @@ export class SecurityHandler implements ISecurityHandler {
SecurityEventType.AUTHENTICATION, SecurityEventType.AUTHENTICATION,
SecurityLogLevel.ERROR, SecurityLogLevel.ERROR,
`Authentication error: ${error instanceof Error ? error.message : String(error)}`, `Authentication error: ${error instanceof Error ? error.message : String(error)}`,
{ username, method, sessionId: session.id, ip: session.remoteAddress, error: error instanceof Error ? error.message : String(error) } { username, error: error instanceof Error ? error.message : String(error) }
); );
return false; return false;

View File

@@ -93,6 +93,8 @@ export class SessionManager implements ISessionManager {
useTLS: secure || false, useTLS: secure || false,
connectionEnded: false, connectionEnded: false,
remoteAddress: socketDetails.remoteAddress, remoteAddress: socketDetails.remoteAddress,
remotePort: socketDetails.remotePort,
createdAt: new Date(),
secure: secure || false, secure: secure || false,
authenticated: false, authenticated: false,
envelope: { envelope: {
@@ -501,6 +503,39 @@ export class SessionManager implements ISessionManager {
return `${details.remoteAddress}:${details.remotePort}-${Date.now()}`; return `${details.remoteAddress}:${details.remotePort}-${Date.now()}`;
} }
/**
* Get all active sessions
*/
public getAllSessions(): ISmtpSession[] {
return Array.from(this.sessions.values());
}
/**
* Update last activity for a session by socket
*/
public updateLastActivity(socket: plugins.net.Socket | plugins.tls.TLSSocket): void {
const session = this.getSession(socket);
if (session) {
this.updateSessionActivity(session);
}
}
/**
* Check for timed out sessions
*/
public checkTimeouts(timeoutMs: number): ISmtpSession[] {
const now = Date.now();
const timedOutSessions: ISmtpSession[] = [];
for (const session of this.sessions.values()) {
if (now - session.lastActivity > timeoutMs) {
timedOutSessions.push(session);
}
}
return timedOutSessions;
}
/** /**
* Clean up resources * Clean up resources
*/ */

View File

@@ -4,7 +4,7 @@
*/ */
import * as plugins from '../../../plugins.js'; import * as plugins from '../../../plugins.js';
import type { ITlsHandler, ISmtpServer } from './interfaces.js'; import type { ITlsHandler, ISmtpServer, ISmtpSession } from './interfaces.js';
import { SmtpResponseCode, SecurityEventType, SecurityLogLevel } from './constants.js'; import { SmtpResponseCode, SecurityEventType, SecurityLogLevel } from './constants.js';
import { SmtpLogger } from './utils/logging.js'; import { SmtpLogger } from './utils/logging.js';
import { getSocketDetails, getTlsDetails } from './utils/helpers.js'; import { getSocketDetails, getTlsDetails } from './utils/helpers.js';
@@ -30,6 +30,11 @@ export class TlsHandler implements ITlsHandler {
*/ */
private certificates: ICertificateData; private certificates: ICertificateData;
/**
* TLS options
*/
private options: plugins.tls.TlsOptions;
/** /**
* Creates a new TLS handler * Creates a new TLS handler
* @param smtpServer - SMTP server instance * @param smtpServer - SMTP server instance
@@ -38,13 +43,13 @@ export class TlsHandler implements ITlsHandler {
this.smtpServer = smtpServer; this.smtpServer = smtpServer;
// Initialize certificates // Initialize certificates
const options = this.smtpServer.getOptions(); const serverOptions = this.smtpServer.getOptions();
try { try {
// Try to load certificates from provided options // Try to load certificates from provided options
this.certificates = loadCertificatesFromString({ this.certificates = loadCertificatesFromString({
key: options.key, key: serverOptions.key,
cert: options.cert, cert: serverOptions.cert,
ca: options.ca ca: serverOptions.ca
}); });
SmtpLogger.info('Successfully loaded TLS certificates'); SmtpLogger.info('Successfully loaded TLS certificates');
@@ -54,30 +59,27 @@ export class TlsHandler implements ITlsHandler {
// Fall back to self-signed certificates for testing // Fall back to self-signed certificates for testing
this.certificates = generateSelfSignedCertificates(); this.certificates = generateSelfSignedCertificates();
} }
// Initialize TLS options
this.options = createTlsOptions(this.certificates);
} }
/** /**
* Handle STARTTLS command * Handle STARTTLS command
* @param socket - Client socket * @param socket - Client socket
*/ */
public handleStartTls(socket: plugins.net.Socket | plugins.tls.TLSSocket): void { public async handleStartTls(socket: plugins.net.Socket, session: ISmtpSession): Promise<plugins.tls.TLSSocket | null> {
// Get the session for this socket
const session = this.smtpServer.getSessionManager().getSession(socket);
if (!session) {
this.sendResponse(socket, `${SmtpResponseCode.LOCAL_ERROR} Internal server error - session not found`);
return;
}
// Check if already using TLS // Check if already using TLS
if (session.useTLS) { if (session.useTLS) {
this.sendResponse(socket, `${SmtpResponseCode.BAD_SEQUENCE} TLS already active`); this.sendResponse(socket, `${SmtpResponseCode.BAD_SEQUENCE} TLS already active`);
return; return null;
} }
// Check if we have the necessary TLS certificates // Check if we have the necessary TLS certificates
if (!this.isTlsEnabled()) { if (!this.isTlsEnabled()) {
this.sendResponse(socket, `${SmtpResponseCode.TLS_UNAVAILABLE_TEMP} TLS not available`); this.sendResponse(socket, `${SmtpResponseCode.TLS_UNAVAILABLE_TEMP} TLS not available`);
return; return null;
} }
// Send ready for TLS response // Send ready for TLS response
@@ -85,7 +87,8 @@ export class TlsHandler implements ITlsHandler {
// Upgrade the connection to TLS // Upgrade the connection to TLS
try { try {
this.startTLS(socket); const tlsSocket = await this.startTLS(socket);
return tlsSocket;
} catch (error) { } catch (error) {
SmtpLogger.error(`STARTTLS negotiation failed: ${error instanceof Error ? error.message : String(error)}`, { SmtpLogger.error(`STARTTLS negotiation failed: ${error instanceof Error ? error.message : String(error)}`, {
sessionId: session.id, sessionId: session.id,
@@ -101,6 +104,8 @@ export class TlsHandler implements ITlsHandler {
{ error: error instanceof Error ? error.message : String(error) }, { error: error instanceof Error ? error.message : String(error) },
session.remoteAddress session.remoteAddress
); );
return null;
} }
} }
@@ -108,7 +113,7 @@ export class TlsHandler implements ITlsHandler {
* Upgrade a connection to TLS * Upgrade a connection to TLS
* @param socket - Client socket * @param socket - Client socket
*/ */
public async startTLS(socket: plugins.net.Socket): Promise<void> { public async startTLS(socket: plugins.net.Socket): Promise<plugins.tls.TLSSocket> {
// Get the session for this socket // Get the session for this socket
const session = this.smtpServer.getSessionManager().getSession(socket); const session = this.smtpServer.getSessionManager().getSession(socket);
@@ -120,11 +125,11 @@ export class TlsHandler implements ITlsHandler {
SmtpLogger.info('Using enhanced STARTTLS implementation'); SmtpLogger.info('Using enhanced STARTTLS implementation');
// Use the enhanced STARTTLS handler with better error handling and socket management // Use the enhanced STARTTLS handler with better error handling and socket management
const options = this.smtpServer.getOptions(); const serverOptions = this.smtpServer.getOptions();
const tlsSocket = await performStartTLS(socket, { const tlsSocket = await performStartTLS(socket, {
key: options.key, key: serverOptions.key,
cert: options.cert, cert: serverOptions.cert,
ca: options.ca, ca: serverOptions.ca,
session: session, session: session,
sessionManager: this.smtpServer.getSessionManager(), sessionManager: this.smtpServer.getSessionManager(),
connectionManager: this.smtpServer.getConnectionManager(), connectionManager: this.smtpServer.getConnectionManager(),
@@ -180,7 +185,10 @@ export class TlsHandler implements ITlsHandler {
sessionId: session?.id, sessionId: session?.id,
remoteAddress: socket.remoteAddress remoteAddress: socket.remoteAddress
}); });
throw new Error('Failed to create TLS socket');
} }
return tlsSocket;
} catch (error) { } catch (error) {
// Log STARTTLS failure // Log STARTTLS failure
SmtpLogger.error(`Failed to upgrade connection to TLS: ${error instanceof Error ? error.message : String(error)}`, { SmtpLogger.error(`Failed to upgrade connection to TLS: ${error instanceof Error ? error.message : String(error)}`, {
@@ -206,6 +214,7 @@ export class TlsHandler implements ITlsHandler {
// Destroy the socket on error // Destroy the socket on error
socket.destroy(); socket.destroy();
throw error;
} }
} }
@@ -312,6 +321,20 @@ export class TlsHandler implements ITlsHandler {
} }
} }
/**
* Check if TLS is available (interface requirement)
*/
public isTlsAvailable(): boolean {
return this.isTlsEnabled();
}
/**
* Get TLS options (interface requirement)
*/
public getTlsOptions(): plugins.tls.TlsOptions {
return this.options;
}
/** /**
* Clean up resources * Clean up resources
*/ */