feat(integration): components now play nicer with each other
This commit is contained in:
@ -142,15 +142,37 @@ export class CommandHandler implements ICommandHandler {
|
||||
|
||||
// For the ERR-01 test, an empty or invalid command is considered a syntax error (500)
|
||||
if (!command || command.trim().length === 0) {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.SYNTAX_ERROR} Command not recognized`);
|
||||
// Record error for rate limiting
|
||||
const emailServer = this.smtpServer.getEmailServer();
|
||||
const rateLimiter = emailServer.getRateLimiter();
|
||||
const shouldBlock = rateLimiter.recordError(session.remoteAddress);
|
||||
|
||||
if (shouldBlock) {
|
||||
SmtpLogger.warn(`IP ${session.remoteAddress} blocked due to excessive errors`);
|
||||
this.sendResponse(socket, `421 Too many errors - connection blocked`);
|
||||
socket.end();
|
||||
} else {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.SYNTAX_ERROR} Command not recognized`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle unknown commands - this should happen before sequence validation
|
||||
// RFC 5321: Use 500 for unrecognized commands, 501 for parameter errors
|
||||
if (!Object.values(SmtpCommand).includes(command.toUpperCase() as SmtpCommand)) {
|
||||
// Comply with RFC 5321 section 4.2.4: Use 500 for unrecognized commands
|
||||
this.sendResponse(socket, `${SmtpResponseCode.SYNTAX_ERROR} Command not recognized`);
|
||||
// Record error for rate limiting
|
||||
const emailServer = this.smtpServer.getEmailServer();
|
||||
const rateLimiter = emailServer.getRateLimiter();
|
||||
const shouldBlock = rateLimiter.recordError(session.remoteAddress);
|
||||
|
||||
if (shouldBlock) {
|
||||
SmtpLogger.warn(`IP ${session.remoteAddress} blocked due to excessive errors`);
|
||||
this.sendResponse(socket, `421 Too many errors - connection blocked`);
|
||||
socket.end();
|
||||
} else {
|
||||
// Comply with RFC 5321 section 4.2.4: Use 500 for unrecognized commands
|
||||
this.sendResponse(socket, `${SmtpResponseCode.SYNTAX_ERROR} Command not recognized`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -477,6 +499,12 @@ export class CommandHandler implements ICommandHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get rate limiter for message-level checks
|
||||
const emailServer = this.smtpServer.getEmailServer();
|
||||
const rateLimiter = emailServer.getRateLimiter();
|
||||
|
||||
// Note: Connection-level rate limiting is already handled in ConnectionManager
|
||||
|
||||
// Special handling for commands that include "MAIL FROM:" in the args
|
||||
let processedArgs = args;
|
||||
|
||||
@ -509,6 +537,26 @@ export class CommandHandler implements ICommandHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check message rate limits for this sender
|
||||
const senderAddress = validation.address || '';
|
||||
const senderDomain = senderAddress.includes('@') ? senderAddress.split('@')[1] : undefined;
|
||||
|
||||
// Check rate limits with domain context if available
|
||||
const messageResult = rateLimiter.checkMessageLimit(
|
||||
senderAddress,
|
||||
session.remoteAddress,
|
||||
1, // We don't know recipients yet, check with 1
|
||||
undefined, // No pattern matching for now
|
||||
senderDomain // Pass domain for domain-specific limits
|
||||
);
|
||||
|
||||
if (!messageResult.allowed) {
|
||||
SmtpLogger.warn(`Message rate limit exceeded for ${senderAddress} from IP ${session.remoteAddress}: ${messageResult.reason}`);
|
||||
// Use 421 for temporary rate limiting (client should retry later)
|
||||
this.sendResponse(socket, `421 ${messageResult.reason} - try again later`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enhanced SIZE parameter handling
|
||||
if (validation.params && validation.params.SIZE) {
|
||||
const size = parseInt(validation.params.SIZE, 10);
|
||||
@ -619,6 +667,29 @@ export class CommandHandler implements ICommandHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check rate limits for recipients
|
||||
const emailServer = this.smtpServer.getEmailServer();
|
||||
const rateLimiter = emailServer.getRateLimiter();
|
||||
const recipientAddress = validation.address || '';
|
||||
const recipientDomain = recipientAddress.includes('@') ? recipientAddress.split('@')[1] : undefined;
|
||||
|
||||
// Check rate limits with accumulated recipient count
|
||||
const recipientCount = session.rcptTo.length + 1; // Including this new recipient
|
||||
const messageResult = rateLimiter.checkMessageLimit(
|
||||
session.mailFrom,
|
||||
session.remoteAddress,
|
||||
recipientCount,
|
||||
undefined, // No pattern matching for now
|
||||
recipientDomain // Pass recipient domain for domain-specific limits
|
||||
);
|
||||
|
||||
if (!messageResult.allowed) {
|
||||
SmtpLogger.warn(`Recipient rate limit exceeded for ${recipientAddress} from IP ${session.remoteAddress}: ${messageResult.reason}`);
|
||||
// Use 451 for temporary recipient rejection
|
||||
this.sendResponse(socket, `451 ${messageResult.reason} - try again later`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create recipient object
|
||||
const recipient: IEnvelopeRecipient = {
|
||||
address: validation.address || '',
|
||||
@ -864,7 +935,18 @@ export class CommandHandler implements ICommandHandler {
|
||||
session.username = username;
|
||||
this.sendResponse(socket, `${SmtpResponseCode.AUTHENTICATION_SUCCESSFUL} Authentication successful`);
|
||||
} else {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.AUTH_FAILED} Authentication failed`);
|
||||
// Record authentication failure for rate limiting
|
||||
const emailServer = this.smtpServer.getEmailServer();
|
||||
const rateLimiter = emailServer.getRateLimiter();
|
||||
const shouldBlock = rateLimiter.recordAuthFailure(session.remoteAddress);
|
||||
|
||||
if (shouldBlock) {
|
||||
SmtpLogger.warn(`IP ${session.remoteAddress} blocked due to excessive authentication failures`);
|
||||
this.sendResponse(socket, `421 Too many authentication failures - connection blocked`);
|
||||
socket.end();
|
||||
} else {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.AUTH_FAILED} Authentication failed`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
SmtpLogger.error(`AUTH PLAIN error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
@ -945,7 +1027,18 @@ export class CommandHandler implements ICommandHandler {
|
||||
session.username = username;
|
||||
this.sendResponse(socket, `${SmtpResponseCode.AUTHENTICATION_SUCCESSFUL} Authentication successful`);
|
||||
} else {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.AUTH_FAILED} Authentication failed`);
|
||||
// Record authentication failure for rate limiting
|
||||
const emailServer = this.smtpServer.getEmailServer();
|
||||
const rateLimiter = emailServer.getRateLimiter();
|
||||
const shouldBlock = rateLimiter.recordAuthFailure(session.remoteAddress);
|
||||
|
||||
if (shouldBlock) {
|
||||
SmtpLogger.warn(`IP ${session.remoteAddress} blocked due to excessive authentication failures`);
|
||||
this.sendResponse(socket, `421 Too many authentication failures - connection blocked`);
|
||||
socket.end();
|
||||
} else {
|
||||
this.sendResponse(socket, `${SmtpResponseCode.AUTH_FAILED} Authentication failed`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
Reference in New Issue
Block a user