update
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
import * as plugins from '../../../plugins.js';
|
||||
import { SmtpState } from './interfaces.js';
|
||||
import type { ISmtpSession, IEnvelopeRecipient } from './interfaces.js';
|
||||
import type { ICommandHandler, ISessionManager, IDataHandler, ITlsHandler, ISecurityHandler } from './interfaces.js';
|
||||
import type { ICommandHandler, ISmtpServer } from './interfaces.js';
|
||||
import { SmtpCommand, SmtpResponseCode, SMTP_DEFAULTS, SMTP_EXTENSIONS } from './constants.js';
|
||||
import { SmtpLogger } from './utils/logging.js';
|
||||
import { adaptiveLogger } from './utils/adaptive-logging.js';
|
||||
@ -18,72 +18,16 @@ import { validateEhlo, validateMailFrom, validateRcptTo, isValidCommandSequence
|
||||
*/
|
||||
export class CommandHandler implements ICommandHandler {
|
||||
/**
|
||||
* Session manager instance
|
||||
* Reference to the SMTP server instance
|
||||
*/
|
||||
private sessionManager: ISessionManager;
|
||||
|
||||
/**
|
||||
* Data handler instance (optional, injected when processing DATA command)
|
||||
*/
|
||||
private dataHandler?: IDataHandler;
|
||||
|
||||
/**
|
||||
* TLS handler instance (optional, injected when processing STARTTLS command)
|
||||
*/
|
||||
private tlsHandler?: ITlsHandler;
|
||||
|
||||
/**
|
||||
* Security handler instance (optional, used for IP reputation and authentication)
|
||||
*/
|
||||
private securityHandler?: ISecurityHandler;
|
||||
|
||||
/**
|
||||
* SMTP server options
|
||||
*/
|
||||
private options: {
|
||||
hostname: string;
|
||||
size?: number;
|
||||
maxRecipients: number;
|
||||
auth?: {
|
||||
required: boolean;
|
||||
methods: ('PLAIN' | 'LOGIN' | 'OAUTH2')[];
|
||||
};
|
||||
};
|
||||
private smtpServer: ISmtpServer;
|
||||
|
||||
/**
|
||||
* Creates a new command handler
|
||||
* @param sessionManager - Session manager instance
|
||||
* @param options - Command handler options
|
||||
* @param dataHandler - Optional data handler instance
|
||||
* @param tlsHandler - Optional TLS handler instance
|
||||
* @param securityHandler - Optional security handler instance
|
||||
* @param smtpServer - SMTP server instance
|
||||
*/
|
||||
constructor(
|
||||
sessionManager: ISessionManager,
|
||||
options: {
|
||||
hostname?: string;
|
||||
size?: number;
|
||||
maxRecipients?: number;
|
||||
auth?: {
|
||||
required: boolean;
|
||||
methods: ('PLAIN' | 'LOGIN' | 'OAUTH2')[];
|
||||
};
|
||||
} = {},
|
||||
dataHandler?: IDataHandler,
|
||||
tlsHandler?: ITlsHandler,
|
||||
securityHandler?: ISecurityHandler
|
||||
) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.dataHandler = dataHandler;
|
||||
this.tlsHandler = tlsHandler;
|
||||
this.securityHandler = securityHandler;
|
||||
|
||||
this.options = {
|
||||
hostname: options.hostname || SMTP_DEFAULTS.HOSTNAME,
|
||||
size: options.size || SMTP_DEFAULTS.MAX_MESSAGE_SIZE,
|
||||
maxRecipients: options.maxRecipients || SMTP_DEFAULTS.MAX_RECIPIENTS,
|
||||
auth: options.auth
|
||||
};
|
||||
constructor(smtpServer: ISmtpServer) {
|
||||
this.smtpServer = smtpServer;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,7 +37,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public processCommand(socket: plugins.net.Socket | plugins.tls.TLSSocket, commandLine: string): void {
|
||||
// Get the session for this socket
|
||||
const session = this.sessionManager.getSession(socket);
|
||||
const session = this.smtpServer.getSessionManager().getSession(socket);
|
||||
if (!session) {
|
||||
SmtpLogger.warn(`No session found for socket from ${socket.remoteAddress}`);
|
||||
this.sendResponse(socket, `${SmtpResponseCode.LOCAL_ERROR} Internal server error - session not found`);
|
||||
@ -105,9 +49,10 @@ export class CommandHandler implements ICommandHandler {
|
||||
if (commandLine.startsWith('__RAW_DATA__')) {
|
||||
const rawData = commandLine.substring('__RAW_DATA__'.length);
|
||||
|
||||
if (this.dataHandler) {
|
||||
const dataHandler = this.smtpServer.getDataHandler();
|
||||
if (dataHandler) {
|
||||
// Let the data handler process the raw chunk
|
||||
this.dataHandler.handleDataReceived(socket, rawData)
|
||||
dataHandler.handleDataReceived(socket, rawData)
|
||||
.catch(error => {
|
||||
SmtpLogger.error(`Error processing raw email data: ${error.message}`, {
|
||||
sessionId: session.id,
|
||||
@ -140,9 +85,10 @@ export class CommandHandler implements ICommandHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dataHandler) {
|
||||
const dataHandler = this.smtpServer.getDataHandler();
|
||||
if (dataHandler) {
|
||||
// Let the data handler process the line (legacy mode)
|
||||
this.dataHandler.processEmailData(socket, commandLine)
|
||||
dataHandler.processEmailData(socket, commandLine)
|
||||
.catch(error => {
|
||||
SmtpLogger.error(`Error processing email data: ${error.message}`, {
|
||||
sessionId: session.id,
|
||||
@ -268,8 +214,9 @@ export class CommandHandler implements ICommandHandler {
|
||||
break;
|
||||
|
||||
case SmtpCommand.STARTTLS:
|
||||
if (this.tlsHandler && this.tlsHandler.isTlsEnabled()) {
|
||||
this.tlsHandler.handleStartTls(socket);
|
||||
const tlsHandler = this.smtpServer.getTlsHandler();
|
||||
if (tlsHandler && tlsHandler.isTlsEnabled()) {
|
||||
tlsHandler.handleStartTls(socket);
|
||||
} else {
|
||||
SmtpLogger.warn('STARTTLS requested but TLS is not enabled', {
|
||||
remoteAddress: socket.remoteAddress,
|
||||
@ -369,7 +316,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
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`);
|
||||
socket.destroy();
|
||||
@ -427,7 +374,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public handleEhlo(socket: plugins.net.Socket | plugins.tls.TLSSocket, clientHostname: string): 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;
|
||||
@ -456,25 +403,29 @@ export class CommandHandler implements ICommandHandler {
|
||||
|
||||
// Update session state and client hostname
|
||||
session.clientHostname = validation.hostname || hostname;
|
||||
this.sessionManager.updateSessionState(session, SmtpState.AFTER_EHLO);
|
||||
this.smtpServer.getSessionManager().updateSessionState(session, SmtpState.AFTER_EHLO);
|
||||
|
||||
// Get options once for this method
|
||||
const options = this.smtpServer.getOptions();
|
||||
|
||||
// Set up EHLO response lines
|
||||
const responseLines = [
|
||||
`${this.options.hostname} greets ${session.clientHostname}`,
|
||||
`${options.hostname || SMTP_DEFAULTS.HOSTNAME} greets ${session.clientHostname}`,
|
||||
SMTP_EXTENSIONS.PIPELINING,
|
||||
SMTP_EXTENSIONS.formatExtension(SMTP_EXTENSIONS.SIZE, this.options.size),
|
||||
SMTP_EXTENSIONS.formatExtension(SMTP_EXTENSIONS.SIZE, options.size || SMTP_DEFAULTS.MAX_MESSAGE_SIZE),
|
||||
SMTP_EXTENSIONS.EIGHTBITMIME,
|
||||
SMTP_EXTENSIONS.ENHANCEDSTATUSCODES
|
||||
];
|
||||
|
||||
// Add TLS extension if available and not already using TLS
|
||||
if (this.tlsHandler && this.tlsHandler.isTlsEnabled() && !session.useTLS) {
|
||||
const tlsHandler = this.smtpServer.getTlsHandler();
|
||||
if (tlsHandler && tlsHandler.isTlsEnabled() && !session.useTLS) {
|
||||
responseLines.push(SMTP_EXTENSIONS.STARTTLS);
|
||||
}
|
||||
|
||||
// Add AUTH extension if configured
|
||||
if (this.options.auth && this.options.auth.methods && this.options.auth.methods.length > 0) {
|
||||
responseLines.push(`${SMTP_EXTENSIONS.AUTH} ${this.options.auth.methods.join(' ')}`);
|
||||
if (options.auth && options.auth.methods && options.auth.methods.length > 0) {
|
||||
responseLines.push(`${SMTP_EXTENSIONS.AUTH} ${options.auth.methods.join(' ')}`);
|
||||
}
|
||||
|
||||
// Send multiline response
|
||||
@ -488,7 +439,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public handleMailFrom(socket: plugins.net.Socket | plugins.tls.TLSSocket, args: string): 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;
|
||||
@ -512,8 +463,11 @@ export class CommandHandler implements ICommandHandler {
|
||||
};
|
||||
}
|
||||
|
||||
// Get options once for this method
|
||||
const options = this.smtpServer.getOptions();
|
||||
|
||||
// Check if authentication is required but not provided
|
||||
if (this.options.auth && this.options.auth.required && !session.authenticated) {
|
||||
if (options.auth && options.auth.required && !session.authenticated) {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.AUTH_REQUIRED} Authentication required`);
|
||||
return;
|
||||
}
|
||||
@ -573,19 +527,20 @@ export class CommandHandler implements ICommandHandler {
|
||||
}
|
||||
|
||||
// Check against server maximum
|
||||
if (size > this.options.size!) {
|
||||
const maxSize = options.size || SMTP_DEFAULTS.MAX_MESSAGE_SIZE;
|
||||
if (size > maxSize) {
|
||||
// Generate informative error with the server's limit
|
||||
this.sendResponse(socket, `${SmtpResponseCode.EXCEEDED_STORAGE} Message size exceeds limit of ${Math.floor(this.options.size! / 1024)} KB`);
|
||||
this.sendResponse(socket, `${SmtpResponseCode.EXCEEDED_STORAGE} Message size exceeds limit of ${Math.floor(maxSize / 1024)} KB`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Log large messages for monitoring
|
||||
if (size > this.options.size! * 0.8) {
|
||||
if (size > maxSize * 0.8) {
|
||||
SmtpLogger.info(`Large message detected (${Math.floor(size / 1024)} KB)`, {
|
||||
sessionId: session.id,
|
||||
remoteAddress: session.remoteAddress,
|
||||
sizeBytes: size,
|
||||
percentOfMax: Math.floor((size / this.options.size!) * 100)
|
||||
percentOfMax: Math.floor((size / maxSize) * 100)
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -606,7 +561,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
};
|
||||
|
||||
// Update session state
|
||||
this.sessionManager.updateSessionState(session, SmtpState.MAIL_FROM);
|
||||
this.smtpServer.getSessionManager().updateSessionState(session, SmtpState.MAIL_FROM);
|
||||
|
||||
// Send success response
|
||||
this.sendResponse(socket, `${SmtpResponseCode.OK} OK`);
|
||||
@ -619,7 +574,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public handleRcptTo(socket: plugins.net.Socket | plugins.tls.TLSSocket, args: string): 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;
|
||||
@ -652,7 +607,9 @@ export class CommandHandler implements ICommandHandler {
|
||||
}
|
||||
|
||||
// Check if we've reached maximum recipients
|
||||
if (session.rcptTo.length >= this.options.maxRecipients) {
|
||||
const options = this.smtpServer.getOptions();
|
||||
const maxRecipients = options.maxRecipients || SMTP_DEFAULTS.MAX_RECIPIENTS;
|
||||
if (session.rcptTo.length >= maxRecipients) {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.TRANSACTION_FAILED} Too many recipients`);
|
||||
return;
|
||||
}
|
||||
@ -668,7 +625,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
session.envelope.rcptTo.push(recipient);
|
||||
|
||||
// Update session state
|
||||
this.sessionManager.updateSessionState(session, SmtpState.RCPT_TO);
|
||||
this.smtpServer.getSessionManager().updateSessionState(session, SmtpState.RCPT_TO);
|
||||
|
||||
// Send success response
|
||||
this.sendResponse(socket, `${SmtpResponseCode.OK} Recipient ok`);
|
||||
@ -680,7 +637,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public handleData(socket: plugins.net.Socket | plugins.tls.TLSSocket): 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;
|
||||
@ -707,7 +664,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
}
|
||||
|
||||
// Update session state
|
||||
this.sessionManager.updateSessionState(session, SmtpState.DATA_RECEIVING);
|
||||
this.smtpServer.getSessionManager().updateSessionState(session, SmtpState.DATA_RECEIVING);
|
||||
|
||||
// Reset email data storage
|
||||
session.emailData = '';
|
||||
@ -741,7 +698,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public handleRset(socket: plugins.net.Socket | plugins.tls.TLSSocket): 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;
|
||||
@ -760,14 +717,14 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public handleNoop(socket: plugins.net.Socket | plugins.tls.TLSSocket): 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;
|
||||
}
|
||||
|
||||
// Update session activity timestamp
|
||||
this.sessionManager.updateSessionActivity(session);
|
||||
this.smtpServer.getSessionManager().updateSessionActivity(session);
|
||||
|
||||
// Send success response
|
||||
this.sendResponse(socket, `${SmtpResponseCode.OK} OK`);
|
||||
@ -779,17 +736,17 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
public handleQuit(socket: plugins.net.Socket | plugins.tls.TLSSocket): void {
|
||||
// Get the session for this socket
|
||||
const session = this.sessionManager.getSession(socket);
|
||||
const session = this.smtpServer.getSessionManager().getSession(socket);
|
||||
|
||||
// Send goodbye message
|
||||
this.sendResponse(socket, `${SmtpResponseCode.SERVICE_CLOSING} ${this.options.hostname} Service closing transmission channel`);
|
||||
this.sendResponse(socket, `${SmtpResponseCode.SERVICE_CLOSING} ${this.smtpServer.getOptions().hostname} Service closing transmission channel`);
|
||||
|
||||
// End the connection
|
||||
socket.end();
|
||||
|
||||
// Clean up session if we have one
|
||||
if (session) {
|
||||
this.sessionManager.removeSession(socket);
|
||||
this.smtpServer.getSessionManager().removeSession(socket);
|
||||
}
|
||||
}
|
||||
|
||||
@ -800,14 +757,14 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
private handleAuth(socket: plugins.net.Socket | plugins.tls.TLSSocket, args: string): 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;
|
||||
}
|
||||
|
||||
// Check if we have auth config
|
||||
if (!this.options.auth || !this.options.auth.methods || !this.options.auth.methods.length) {
|
||||
if (!this.smtpServer.getOptions().auth || !this.smtpServer.getOptions().auth.methods || !this.smtpServer.getOptions().auth.methods.length) {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.COMMAND_NOT_IMPLEMENTED} Authentication not supported`);
|
||||
return;
|
||||
}
|
||||
@ -829,14 +786,14 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
private handleHelp(socket: plugins.net.Socket | plugins.tls.TLSSocket, args: string): 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;
|
||||
}
|
||||
|
||||
// Update session activity timestamp
|
||||
this.sessionManager.updateSessionActivity(session);
|
||||
this.smtpServer.getSessionManager().updateSessionActivity(session);
|
||||
|
||||
// Provide help information based on arguments
|
||||
const helpCommand = args.trim().toUpperCase();
|
||||
@ -856,11 +813,12 @@ export class CommandHandler implements ICommandHandler {
|
||||
];
|
||||
|
||||
// Add conditional commands
|
||||
if (this.tlsHandler && this.tlsHandler.isTlsEnabled()) {
|
||||
const tlsHandler = this.smtpServer.getTlsHandler();
|
||||
if (tlsHandler && tlsHandler.isTlsEnabled()) {
|
||||
helpLines.push('STARTTLS - Start TLS negotiation');
|
||||
}
|
||||
|
||||
if (this.options.auth && this.options.auth.methods.length) {
|
||||
if (this.smtpServer.getOptions().auth && this.smtpServer.getOptions().auth.methods.length) {
|
||||
helpLines.push('AUTH mechanism - Authenticate with the server');
|
||||
}
|
||||
|
||||
@ -906,7 +864,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
break;
|
||||
|
||||
case 'AUTH':
|
||||
helpText = `AUTH mechanism - Authenticate with the server. Supported methods: ${this.options.auth?.methods.join(', ')}`;
|
||||
helpText = `AUTH mechanism - Authenticate with the server. Supported methods: ${this.smtpServer.getOptions().auth?.methods.join(', ')}`;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -925,14 +883,14 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
private handleVrfy(socket: plugins.net.Socket | plugins.tls.TLSSocket, args: string): 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;
|
||||
}
|
||||
|
||||
// Update session activity timestamp
|
||||
this.sessionManager.updateSessionActivity(session);
|
||||
this.smtpServer.getSessionManager().updateSessionActivity(session);
|
||||
|
||||
const username = args.trim();
|
||||
|
||||
@ -962,14 +920,14 @@ export class CommandHandler implements ICommandHandler {
|
||||
*/
|
||||
private handleExpn(socket: plugins.net.Socket | plugins.tls.TLSSocket, args: string): 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;
|
||||
}
|
||||
|
||||
// Update session activity timestamp
|
||||
this.sessionManager.updateSessionActivity(session);
|
||||
this.smtpServer.getSessionManager().updateSessionActivity(session);
|
||||
|
||||
const listname = args.trim();
|
||||
|
||||
@ -1007,7 +965,7 @@ export class CommandHandler implements ICommandHandler {
|
||||
};
|
||||
|
||||
// Reset state to after EHLO
|
||||
this.sessionManager.updateSessionState(session, SmtpState.AFTER_EHLO);
|
||||
this.smtpServer.getSessionManager().updateSessionState(session, SmtpState.AFTER_EHLO);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1059,4 +1017,12 @@ export class CommandHandler implements ICommandHandler {
|
||||
// Check standard command sequence
|
||||
return isValidCommandSequence(command, session.state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
public destroy(): void {
|
||||
// CommandHandler doesn't have timers or event listeners to clean up
|
||||
SmtpLogger.debug('CommandHandler destroyed');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user