update
This commit is contained in:
@ -1,384 +1,438 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from './plugins.js';
|
||||
import { createTestServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
|
||||
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.js';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.js';
|
||||
import * as net from 'net';
|
||||
|
||||
tap.test('CEDGE-03: should handle protocol violations gracefully', async (tools) => {
|
||||
const testId = 'CEDGE-03-protocol-violations';
|
||||
console.log(`\n${testId}: Testing protocol violation handling...`);
|
||||
let testServer: ITestServer;
|
||||
|
||||
let scenarioCount = 0;
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2572,
|
||||
tlsEnabled: false,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toEqual(2572);
|
||||
});
|
||||
|
||||
// Scenario 1: Server closes connection unexpectedly
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing unexpected connection closure`);
|
||||
tap.test('CEDGE-03: Server closes connection during MAIL FROM', async () => {
|
||||
// Create server that abruptly closes during MAIL FROM
|
||||
const abruptServer = net.createServer((socket) => {
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
let commandCount = 0;
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
let commandCount = 0;
|
||||
socket.on('data', (data) => {
|
||||
commandCount++;
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
// Abruptly close connection
|
||||
console.log(' [Server] Closing connection unexpectedly');
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Connection closure test',
|
||||
text: 'Testing unexpected disconnection'
|
||||
});
|
||||
|
||||
try {
|
||||
await smtpClient.sendMail(email);
|
||||
console.log(' Unexpected success');
|
||||
} catch (error) {
|
||||
console.log(` Expected error: ${error.message}`);
|
||||
expect(error).toBeDefined();
|
||||
}
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
// Scenario 2: Server sends data without CRLF
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing responses without proper CRLF`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
// Send greeting without CRLF
|
||||
socket.write('220 mail.example.com ESMTP');
|
||||
// Then send proper CRLF
|
||||
setTimeout(() => socket.write('\r\n'), 100);
|
||||
commandCount++;
|
||||
console.log(`Server received command ${commandCount}: "${line}"`);
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
// Mix responses with and without CRLF
|
||||
socket.write('250-mail.example.com\n'); // Just LF
|
||||
socket.write('250-SIZE 10485760\r'); // Just CR
|
||||
socket.write('250 OK\r\n'); // Proper CRLF
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (line.startsWith('EHLO')) {
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line.startsWith('MAIL FROM:')) {
|
||||
// Abruptly close connection
|
||||
console.log('Server closing connection unexpectedly');
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
abruptServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Line ending test',
|
||||
text: 'Testing non-standard line endings'
|
||||
});
|
||||
const abruptPort = (abruptServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: abruptPort,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Connection closure test',
|
||||
text: 'Testing unexpected disconnection'
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(` Result: ${result.messageId ? 'Success (handled gracefully)' : 'Failed'}`);
|
||||
expect(result).toBeDefined();
|
||||
// Should not succeed due to connection closure
|
||||
expect(result.success).toBeFalse();
|
||||
console.log('✅ Client handled abrupt connection closure gracefully');
|
||||
} catch (error) {
|
||||
// Expected to fail due to connection closure
|
||||
console.log('✅ Client threw expected error for connection closure:', error.message);
|
||||
expect(error.message).toMatch(/closed|reset|abort|end|timeout/i);
|
||||
}
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
await smtpClient.close();
|
||||
abruptServer.close();
|
||||
});
|
||||
|
||||
// Scenario 3: Server sends responses in wrong order
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing out-of-order responses`);
|
||||
tap.test('CEDGE-03: Server sends invalid response codes', async () => {
|
||||
// Create server that sends non-standard response codes
|
||||
const invalidServer = net.createServer((socket) => {
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
let inData = false;
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
const pendingResponses: Array<() => void> = [];
|
||||
console.log(`Server received: "${line}"`);
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
// Delay response
|
||||
pendingResponses.push(() => {
|
||||
socket.write('250 OK\r\n');
|
||||
});
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
// Send this response first, then the MAIL response
|
||||
socket.write('250 OK\r\n');
|
||||
if (pendingResponses.length > 0) {
|
||||
pendingResponses.forEach(fn => fn());
|
||||
pendingResponses.length = 0;
|
||||
}
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
if (inData) {
|
||||
if (line === '.') {
|
||||
socket.write('999 Invalid response code\r\n'); // Invalid 9xx code
|
||||
inData = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (line.startsWith('EHLO')) {
|
||||
socket.write('150 Intermediate response\r\n'); // Invalid for EHLO
|
||||
} else if (line.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
inData = true;
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false
|
||||
await new Promise<void>((resolve) => {
|
||||
invalidServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
const invalidPort = (invalidServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: invalidPort,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
try {
|
||||
// This will likely fail due to invalid EHLO response
|
||||
const verified = await smtpClient.verify();
|
||||
expect(verified).toBeFalse();
|
||||
console.log('✅ Client rejected invalid response codes');
|
||||
} catch (error) {
|
||||
console.log('✅ Client properly handled invalid response codes:', error.message);
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
invalidServer.close();
|
||||
});
|
||||
|
||||
tap.test('CEDGE-03: Server sends malformed multi-line responses', async () => {
|
||||
// Create server with malformed multi-line responses
|
||||
const malformedServer = net.createServer((socket) => {
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
console.log(`Server received: "${line}"`);
|
||||
|
||||
if (line.startsWith('EHLO')) {
|
||||
// Malformed multi-line response (missing final line)
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('250-PIPELINING\r\n');
|
||||
// Missing final 250 line - this violates RFC
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Response order test',
|
||||
text: 'Testing out-of-order responses'
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
malformedServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
|
||||
} catch (error) {
|
||||
console.log(` Expected possible error: ${error.message}`);
|
||||
expect(error).toBeDefined();
|
||||
const malformedPort = (malformedServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: malformedPort,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
try {
|
||||
// Should timeout or fail due to incomplete EHLO response
|
||||
const verified = await smtpClient.verify();
|
||||
console.log('Verification result:', verified);
|
||||
|
||||
// Either fails verification or times out
|
||||
if (!verified) {
|
||||
console.log('✅ Client rejected malformed multi-line response');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('✅ Client handled malformed response with error:', error.message);
|
||||
expect(error.message).toMatch(/timeout|response|parse|format/i);
|
||||
}
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
await smtpClient.close();
|
||||
malformedServer.close();
|
||||
});
|
||||
|
||||
// Scenario 4: Server sends unsolicited responses
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing unsolicited server responses`);
|
||||
tap.test('CEDGE-03: Server violates command sequence rules', async () => {
|
||||
// Create server that accepts commands out of sequence
|
||||
const sequenceServer = net.createServer((socket) => {
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
// Send unsolicited responses periodically
|
||||
const interval = setInterval(() => {
|
||||
if (!socket.destroyed) {
|
||||
console.log(' [Server] Sending unsolicited response');
|
||||
socket.write('250-NOTICE: Server status update\r\n');
|
||||
}
|
||||
}, 500);
|
||||
console.log(`Server received: "${line}"`);
|
||||
|
||||
socket.on('close', () => clearInterval(interval));
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
clearInterval(interval);
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Accept any command in any order (protocol violation)
|
||||
if (line.startsWith('EHLO')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (line === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (line === '.') {
|
||||
socket.write('250 Message accepted\r\n');
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
sequenceServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Unsolicited response test',
|
||||
text: 'Testing unsolicited server messages'
|
||||
});
|
||||
const sequencePort = (sequenceServer.address() as net.AddressInfo).port;
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
|
||||
expect(result).toBeDefined();
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: sequencePort,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
// Client should still work correctly despite server violations
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Sequence violation test',
|
||||
text: 'Testing command sequence violations'
|
||||
});
|
||||
|
||||
// Scenario 5: Server violates response code format
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing invalid response codes`);
|
||||
const result = await smtpClient.sendMail(email);
|
||||
expect(result.success).toBeTrue();
|
||||
console.log('✅ Client maintains proper sequence despite server violations');
|
||||
|
||||
await smtpClient.close();
|
||||
sequenceServer.close();
|
||||
});
|
||||
|
||||
tap.test('CEDGE-03: Server sends responses without CRLF', async () => {
|
||||
// Create server that sends responses with incorrect line endings
|
||||
const crlfServer = net.createServer((socket) => {
|
||||
socket.write('220 mail.example.com ESMTP\n'); // LF only, not CRLF
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
// Invalid response code (should be 3 digits)
|
||||
socket.write('22 mail.example.com ESMTP\r\n');
|
||||
setTimeout(() => {
|
||||
// Send correct response
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
}, 100);
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
// Mix valid and invalid response codes
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('25O-TYPO IN CODE\r\n'); // Letter O instead of zero
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('2.5.0 OK\r\n'); // Wrong format
|
||||
setTimeout(() => {
|
||||
socket.write('250 OK\r\n');
|
||||
}, 50);
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(`Server received: "${line}"`);
|
||||
|
||||
if (line.startsWith('EHLO')) {
|
||||
socket.write('250 OK\n'); // LF only
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\n'); // LF only
|
||||
socket.end();
|
||||
} else {
|
||||
socket.write('250 OK\n'); // LF only
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
crlfServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Response code test',
|
||||
text: 'Testing invalid response codes'
|
||||
});
|
||||
const crlfPort = (crlfServer.address() as net.AddressInfo).port;
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(` Result: ${result.messageId ? 'Success (handled invalid codes)' : 'Failed'}`);
|
||||
} catch (error) {
|
||||
console.log(` Expected possible error: ${error.message}`);
|
||||
expect(error).toBeDefined();
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: crlfPort,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
try {
|
||||
const verified = await smtpClient.verify();
|
||||
if (verified) {
|
||||
console.log('✅ Client handled non-CRLF responses gracefully');
|
||||
} else {
|
||||
console.log('✅ Client rejected non-CRLF responses');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('✅ Client handled CRLF violation with error:', error.message);
|
||||
}
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
await smtpClient.close();
|
||||
crlfServer.close();
|
||||
});
|
||||
|
||||
// Scenario 6: Server sends binary data
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing binary data in responses`);
|
||||
tap.test('CEDGE-03: Server sends oversized responses', async () => {
|
||||
// Create server that sends very long response lines
|
||||
const oversizeServer = net.createServer((socket) => {
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
|
||||
const testServer = await createTestServer({
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
// Send greeting with some binary data
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
socket.write(Buffer.from([0x00, 0x01, 0x02, 0x03])); // Binary data
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
// Include binary in response
|
||||
socket.write('250 OK ');
|
||||
socket.write(Buffer.from([0xFF, 0xFE]));
|
||||
socket.write('\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(`Server received: "${line}"`);
|
||||
|
||||
if (line.startsWith('EHLO')) {
|
||||
// Send an extremely long response line (over RFC limit)
|
||||
const longResponse = '250 ' + 'x'.repeat(2000) + '\r\n';
|
||||
socket.write(longResponse);
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
} else {
|
||||
socket.write('250 OK\r\n');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: false
|
||||
await new Promise<void>((resolve) => {
|
||||
oversizeServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
const oversizePort = (oversizeServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: oversizePort,
|
||||
secure: false,
|
||||
connectionTimeout: 5000,
|
||||
debug: true
|
||||
});
|
||||
|
||||
try {
|
||||
const verified = await smtpClient.verify();
|
||||
console.log(`Verification with oversized response: ${verified}`);
|
||||
console.log('✅ Client handled oversized response');
|
||||
} catch (error) {
|
||||
console.log('✅ Client handled oversized response with error:', error.message);
|
||||
}
|
||||
|
||||
await smtpClient.close();
|
||||
oversizeServer.close();
|
||||
});
|
||||
|
||||
tap.test('CEDGE-03: Server violates RFC timing requirements', async () => {
|
||||
// Create server that has excessive delays
|
||||
const slowServer = net.createServer((socket) => {
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const lines = data.toString().split('\r\n');
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line && lines[lines.length - 1] === '') return;
|
||||
|
||||
console.log(`Server received: "${line}"`);
|
||||
|
||||
if (line.startsWith('EHLO')) {
|
||||
// Extreme delay (violates RFC timing recommendations)
|
||||
setTimeout(() => {
|
||||
socket.write('250 OK\r\n');
|
||||
}, 2000); // 2 second delay
|
||||
} else if (line === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
} else {
|
||||
socket.write('250 OK\r\n');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Binary data test',
|
||||
text: 'Testing binary data handling'
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
slowServer.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
|
||||
expect(result).toBeDefined();
|
||||
} catch (error) {
|
||||
console.log(` Error handling binary data: ${error.message}`);
|
||||
expect(error).toBeDefined();
|
||||
const slowPort = (slowServer.address() as net.AddressInfo).port;
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: '127.0.0.1',
|
||||
port: slowPort,
|
||||
secure: false,
|
||||
connectionTimeout: 10000, // Allow time for slow response
|
||||
debug: true
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const verified = await smtpClient.verify();
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.log(`Verification completed in ${duration}ms`);
|
||||
if (verified) {
|
||||
console.log('✅ Client handled slow server responses');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('✅ Client handled timing violation with error:', error.message);
|
||||
}
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
await smtpClient.close();
|
||||
slowServer.close();
|
||||
});
|
||||
|
||||
console.log(`\n${testId}: All ${scenarioCount} protocol violation scenarios tested ✓`);
|
||||
});
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user