This commit is contained in:
2025-05-22 23:02:37 +00:00
parent f065a9c952
commit 50350bd78d
10 changed files with 633 additions and 779 deletions

View File

@ -8,74 +8,27 @@ import * as fs from 'fs';
import * as path from 'path';
import { SmtpState } from './interfaces.js';
import type { ISmtpSession, ISmtpTransactionResult } from './interfaces.js';
import type { IDataHandler, ISessionManager } from './interfaces.js';
import type { IDataHandler, ISmtpServer } from './interfaces.js';
import { SmtpResponseCode, SMTP_PATTERNS, SMTP_DEFAULTS } from './constants.js';
import { SmtpLogger } from './utils/logging.js';
import { detectHeaderInjection } from './utils/validation.js';
import { Email } from '../../core/classes.email.js';
import { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js';
/**
* Handles SMTP DATA command and email data processing
*/
export class DataHandler implements IDataHandler {
/**
* Session manager instance
* Reference to the SMTP server instance
*/
private sessionManager: ISessionManager;
/**
* Email server reference
*/
private emailServer: UnifiedEmailServer;
/**
* SMTP server options
*/
private options: {
size: number;
tempDir?: string;
hostname?: string;
};
private smtpServer: ISmtpServer;
/**
* Creates a new data handler
* @param sessionManager - Session manager instance
* @param emailServer - Email server reference
* @param options - Data handler options
* @param smtpServer - SMTP server instance
*/
constructor(
sessionManager: ISessionManager,
emailServer: UnifiedEmailServer,
options: {
size?: number;
tempDir?: string;
hostname?: string;
} = {}
) {
this.sessionManager = sessionManager;
this.emailServer = emailServer;
this.options = {
size: options.size || SMTP_DEFAULTS.MAX_MESSAGE_SIZE,
tempDir: options.tempDir,
hostname: options.hostname || SMTP_DEFAULTS.HOSTNAME
};
// Create temp directory if specified and doesn't exist
if (this.options.tempDir) {
try {
if (!fs.existsSync(this.options.tempDir)) {
fs.mkdirSync(this.options.tempDir, { recursive: true });
}
} catch (error) {
SmtpLogger.error(`Failed to create temp directory: ${error instanceof Error ? error.message : String(error)}`, {
error: error instanceof Error ? error : new Error(String(error)),
tempDir: this.options.tempDir
});
this.options.tempDir = undefined;
}
}
constructor(smtpServer: ISmtpServer) {
this.smtpServer = smtpServer;
}
/**
@ -86,7 +39,7 @@ export class DataHandler implements IDataHandler {
*/
public async processEmailData(socket: plugins.net.Socket | plugins.tls.TLSSocket, data: string): Promise<void> {
// Get the session for this socket
const session = this.sessionManager.getSession(socket);
const session = this.smtpServer.getSessionManager().getSession(socket);
if (!session) {
this.sendResponse(socket, `${SmtpResponseCode.LOCAL_ERROR} Internal server error - session not found`);
return;
@ -106,7 +59,7 @@ export class DataHandler implements IDataHandler {
}, SMTP_DEFAULTS.DATA_TIMEOUT);
// Update activity timestamp
this.sessionManager.updateSessionActivity(session);
this.smtpServer.getSessionManager().updateSessionActivity(session);
// Store data in chunks for better memory efficiency
if (!session.emailDataChunks) {
@ -118,14 +71,16 @@ export class DataHandler implements IDataHandler {
session.emailDataSize = (session.emailDataSize || 0) + data.length;
// Check if we've reached the max size (using incremental tracking)
if (session.emailDataSize > this.options.size) {
const options = this.smtpServer.getOptions();
const maxSize = options.size || SMTP_DEFAULTS.MAX_MESSAGE_SIZE;
if (session.emailDataSize > maxSize) {
SmtpLogger.warn(`Message size exceeds limit for session ${session.id}`, {
sessionId: session.id,
size: session.emailDataSize,
limit: this.options.size
limit: maxSize
});
this.sendResponse(socket, `${SmtpResponseCode.EXCEEDED_STORAGE} Message too big, size limit is ${this.options.size} bytes`);
this.sendResponse(socket, `${SmtpResponseCode.EXCEEDED_STORAGE} Message too big, size limit is ${maxSize} bytes`);
this.resetSession(session);
return;
}
@ -164,7 +119,7 @@ export class DataHandler implements IDataHandler {
*/
public async handleDataReceived(socket: plugins.net.Socket | plugins.tls.TLSSocket, data: string): Promise<void> {
// Get the session
const session = this.sessionManager.getSession(socket);
const session = this.smtpServer.getSessionManager().getSession(socket);
if (!session) {
this.sendResponse(socket, `${SmtpResponseCode.LOCAL_ERROR} Internal server error - session not found`);
return;
@ -293,14 +248,16 @@ export class DataHandler implements IDataHandler {
});
// Generate a message ID since queueEmail is not available
const messageId = `${Date.now()}-${Math.floor(Math.random() * 1000000)}@${this.options.hostname || 'mail.example.com'}`;
const options = this.smtpServer.getOptions();
const hostname = options.hostname || SMTP_DEFAULTS.HOSTNAME;
const messageId = `${Date.now()}-${Math.floor(Math.random() * 1000000)}@${hostname}`;
// Process the email through the emailServer
try {
// Process the email via the UnifiedEmailServer
// Pass the email object, session data, and specify the mode (mta, forward, or process)
// This connects SMTP reception to the overall email system
const processResult = await this.emailServer.processEmailByMode(email, session, 'mta');
const processResult = await this.smtpServer.getEmailServer().processEmailByMode(email, session, 'mta');
SmtpLogger.info(`Email processed through UnifiedEmailServer: ${email.getMessageId()}`, {
sessionId: session.id,
@ -350,7 +307,7 @@ export class DataHandler implements IDataHandler {
// Process the email via the UnifiedEmailServer in forward mode
try {
const processResult = await this.emailServer.processEmailByMode(email, session, 'forward');
const processResult = await this.smtpServer.getEmailServer().processEmailByMode(email, session, 'forward');
SmtpLogger.info(`Email forwarded through UnifiedEmailServer: ${email.getMessageId()}`, {
sessionId: session.id,
@ -389,7 +346,7 @@ export class DataHandler implements IDataHandler {
// Process the email via the UnifiedEmailServer in process mode
try {
const processResult = await this.emailServer.processEmailByMode(email, session, 'process');
const processResult = await this.smtpServer.getEmailServer().processEmailByMode(email, session, 'process');
SmtpLogger.info(`Email processed directly through UnifiedEmailServer: ${email.getMessageId()}`, {
sessionId: session.id,
@ -446,27 +403,11 @@ export class DataHandler implements IDataHandler {
* @param session - SMTP session
*/
public saveEmail(session: ISmtpSession): void {
if (!this.options.tempDir) {
return;
}
try {
const timestamp = Date.now();
const filename = `${session.id}-${timestamp}.eml`;
const filePath = path.join(this.options.tempDir, filename);
fs.writeFileSync(filePath, session.emailData);
SmtpLogger.debug(`Saved email to disk: ${filePath}`, {
sessionId: session.id,
filePath
});
} catch (error) {
SmtpLogger.error(`Failed to save email to disk: ${error instanceof Error ? error.message : String(error)}`, {
sessionId: session.id,
error: error instanceof Error ? error : new Error(String(error))
});
}
// Email saving to disk is currently disabled in the refactored architecture
// This functionality can be re-enabled by adding a tempDir option to ISmtpServerOptions
SmtpLogger.debug(`Email saving to disk is disabled`, {
sessionId: session.id
});
}
/**
@ -500,7 +441,7 @@ export class DataHandler implements IDataHandler {
// Get message ID or generate one
const messageId = parsed.messageId ||
headers['message-id'] ||
`<${Date.now()}.${Math.random().toString(36).substring(2)}@${this.options.hostname}>`;
`<${Date.now()}.${Math.random().toString(36).substring(2)}@${this.smtpServer.getOptions().hostname}>`;
// Get From, To, and Subject from parsed email or envelope
const from = parsed.from?.value?.[0]?.address ||
@ -618,7 +559,7 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
// Add received header
const timestamp = new Date().toUTCString();
const receivedHeader = `from ${session.clientHostname || 'unknown'} (${session.remoteAddress}) by ${this.options.hostname} with ESMTP id ${session.id}; ${timestamp}`;
const receivedHeader = `from ${session.clientHostname || 'unknown'} (${session.remoteAddress}) by ${this.smtpServer.getOptions().hostname} with ESMTP id ${session.id}; ${timestamp}`;
email.addHeader('Received', receivedHeader);
// Add all original headers
@ -781,7 +722,7 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
const subject = headers['subject'] || 'No Subject';
const from = headers['from'] || session.envelope.mailFrom.address;
const to = headers['to'] || session.envelope.rcptTo.map(r => r.address).join(', ');
const messageId = headers['message-id'] || `<${Date.now()}.${Math.random().toString(36).substring(2)}@${this.options.hostname}>`;
const messageId = headers['message-id'] || `<${Date.now()}.${Math.random().toString(36).substring(2)}@${this.smtpServer.getOptions().hostname}>`;
// Create email object
const email = new Email({
@ -804,7 +745,7 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
// Add received header
const timestamp = new Date().toUTCString();
const receivedHeader = `from ${session.clientHostname || 'unknown'} (${session.remoteAddress}) by ${this.options.hostname} with ESMTP id ${session.id}; ${timestamp}`;
const receivedHeader = `from ${session.clientHostname || 'unknown'} (${session.remoteAddress}) by ${this.smtpServer.getOptions().hostname} with ESMTP id ${session.id}; ${timestamp}`;
email.addHeader('Received', receivedHeader);
// Add all original headers
@ -1078,7 +1019,7 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
try {
// Update session state
this.sessionManager.updateSessionState(session, SmtpState.FINISHED);
this.smtpServer.getSessionManager().updateSessionState(session, SmtpState.FINISHED);
// Optionally save email to disk
this.saveEmail(session);
@ -1130,7 +1071,7 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
};
// Reset state to after EHLO
this.sessionManager.updateSessionState(session, SmtpState.AFTER_EHLO);
this.smtpServer.getSessionManager().updateSessionState(session, SmtpState.AFTER_EHLO);
}
/**
@ -1199,7 +1140,7 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
*/
private handleSocketError(socket: plugins.net.Socket | plugins.tls.TLSSocket, error: unknown, response: string): void {
// Get the session for this socket
const session = this.sessionManager.getSession(socket);
const session = this.smtpServer.getSessionManager().getSession(socket);
if (!session) {
SmtpLogger.error(`Session not found when handling socket error`);
if (!socket.destroyed) {
@ -1253,4 +1194,13 @@ SmtpLogger.debug(`Parsed email subject: ${subject}`, { subject });
}
}, 100); // Short delay before retry
}
}
}
/**
* Clean up resources
*/
public destroy(): void {
// DataHandler doesn't have timers or event listeners to clean up
SmtpLogger.debug('DataHandler destroyed');
}
}