update
This commit is contained in:
@ -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');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user