701 lines
25 KiB
TypeScript
701 lines
25 KiB
TypeScript
|
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';
|
||
|
|
||
|
tap.test('CSEC-10: should handle anti-spam measures correctly', async (tools) => {
|
||
|
const testId = 'CSEC-10-anti-spam-measures';
|
||
|
console.log(`\n${testId}: Testing anti-spam measure handling...`);
|
||
|
|
||
|
let scenarioCount = 0;
|
||
|
|
||
|
// Scenario 1: Reputation-based filtering
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing reputation-based filtering`);
|
||
|
|
||
|
const ipReputation = new Map([
|
||
|
['127.0.0.1', { score: 100, status: 'trusted' }],
|
||
|
['10.0.0.1', { score: 50, status: 'neutral' }],
|
||
|
['192.168.1.100', { score: 10, status: 'suspicious' }],
|
||
|
['10.10.10.10', { score: 0, status: 'blocked' }]
|
||
|
]);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
const clientIP = socket.remoteAddress || '127.0.0.1';
|
||
|
const reputation = ipReputation.get(clientIP) || { score: 50, status: 'unknown' };
|
||
|
|
||
|
console.log(` [Server] Client ${clientIP} connected (reputation: ${reputation.status})`);
|
||
|
|
||
|
if (reputation.score === 0) {
|
||
|
socket.write('554 5.7.1 Your IP has been blocked due to poor reputation\r\n');
|
||
|
socket.end();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
socket.write('220 reputation.example.com ESMTP\r\n');
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-reputation.example.com\r\n');
|
||
|
if (reputation.score < 30) {
|
||
|
// Suspicious IPs get limited features
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else {
|
||
|
socket.write('250-SIZE 10485760\r\n');
|
||
|
socket.write('250-AUTH PLAIN LOGIN\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
}
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
if (reputation.score < 30) {
|
||
|
// Add delay for suspicious IPs (tarpitting)
|
||
|
setTimeout(() => {
|
||
|
socket.write('250 OK\r\n');
|
||
|
}, 2000);
|
||
|
} else {
|
||
|
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: Message accepted (reputation score: ${reputation.score})\r\n`);
|
||
|
} else if (command === 'QUIT') {
|
||
|
socket.write('221 Bye\r\n');
|
||
|
socket.end();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Test with good reputation (localhost)
|
||
|
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: 'Reputation test',
|
||
|
text: 'Testing reputation-based filtering'
|
||
|
});
|
||
|
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(' Good reputation: Message accepted');
|
||
|
expect(result).toBeDefined();
|
||
|
expect(result.response).toContain('reputation score: 100');
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 2: Content filtering and spam scoring
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing content filtering and spam scoring`);
|
||
|
|
||
|
const spamKeywords = [
|
||
|
{ word: 'viagra', score: 5 },
|
||
|
{ word: 'lottery', score: 4 },
|
||
|
{ word: 'winner', score: 3 },
|
||
|
{ word: 'click here', score: 3 },
|
||
|
{ word: 'free money', score: 5 },
|
||
|
{ word: 'guarantee', score: 2 },
|
||
|
{ word: 'act now', score: 3 },
|
||
|
{ word: '100% free', score: 4 }
|
||
|
];
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 content-filter.example.com ESMTP\r\n');
|
||
|
|
||
|
let inData = false;
|
||
|
let messageContent = '';
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const text = data.toString();
|
||
|
|
||
|
if (inData) {
|
||
|
messageContent += text;
|
||
|
if (text.includes('\r\n.\r\n')) {
|
||
|
inData = false;
|
||
|
|
||
|
// Calculate spam score
|
||
|
let spamScore = 0;
|
||
|
const lowerContent = messageContent.toLowerCase();
|
||
|
|
||
|
spamKeywords.forEach(({ word, score }) => {
|
||
|
if (lowerContent.includes(word)) {
|
||
|
spamScore += score;
|
||
|
console.log(` [Server] Found spam keyword: "${word}" (+${score})`);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Check for suspicious patterns
|
||
|
if ((messageContent.match(/!/g) || []).length > 5) {
|
||
|
spamScore += 2;
|
||
|
console.log(' [Server] Excessive exclamation marks (+2)');
|
||
|
}
|
||
|
|
||
|
if ((messageContent.match(/\$|€|£/g) || []).length > 3) {
|
||
|
spamScore += 2;
|
||
|
console.log(' [Server] Multiple currency symbols (+2)');
|
||
|
}
|
||
|
|
||
|
if (messageContent.includes('ALL CAPS') || /[A-Z]{10,}/.test(messageContent)) {
|
||
|
spamScore += 1;
|
||
|
console.log(' [Server] Excessive capitals (+1)');
|
||
|
}
|
||
|
|
||
|
console.log(` [Server] Total spam score: ${spamScore}`);
|
||
|
|
||
|
if (spamScore >= 10) {
|
||
|
socket.write('550 5.7.1 Message rejected due to spam content\r\n');
|
||
|
} else if (spamScore >= 5) {
|
||
|
socket.write('250 OK: Message quarantined for review\r\n');
|
||
|
} else {
|
||
|
socket.write('250 OK: Message accepted\r\n');
|
||
|
}
|
||
|
|
||
|
messageContent = '';
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const command = text.trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-content-filter.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');
|
||
|
inData = true;
|
||
|
} else if (command === 'QUIT') {
|
||
|
socket.write('221 Bye\r\n');
|
||
|
socket.end();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const smtpClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false
|
||
|
});
|
||
|
|
||
|
// Test 1: Clean email
|
||
|
const cleanEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Business proposal',
|
||
|
text: 'I would like to discuss our upcoming project. Please let me know your availability.'
|
||
|
});
|
||
|
|
||
|
const cleanResult = await smtpClient.sendMail(cleanEmail);
|
||
|
console.log(' Clean email: Accepted');
|
||
|
expect(cleanResult.response).toContain('Message accepted');
|
||
|
|
||
|
// Test 2: Suspicious email
|
||
|
const suspiciousEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'You are a WINNER!',
|
||
|
text: 'Click here to claim your lottery prize! Act now! 100% guarantee!'
|
||
|
});
|
||
|
|
||
|
const suspiciousResult = await smtpClient.sendMail(suspiciousEmail);
|
||
|
console.log(' Suspicious email: Quarantined');
|
||
|
expect(suspiciousResult.response).toContain('quarantined');
|
||
|
|
||
|
// Test 3: Spam email
|
||
|
const spamEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'FREE MONEY - VIAGRA - LOTTERY WINNER!!!',
|
||
|
text: 'CLICK HERE NOW!!! 100% FREE VIAGRA!!! You are a LOTTERY WINNER!!! Act now to claim your FREE MONEY!!! $$$€€€£££'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await smtpClient.sendMail(spamEmail);
|
||
|
console.log(' Unexpected: Spam email accepted');
|
||
|
} catch (error) {
|
||
|
console.log(' Spam email: Rejected');
|
||
|
expect(error.message).toContain('spam content');
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 3: Greylisting
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing greylisting`);
|
||
|
|
||
|
const greylist = new Map<string, { firstSeen: number; attempts: number }>();
|
||
|
const greylistDuration = 2000; // 2 seconds for testing
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 greylist.example.com ESMTP\r\n');
|
||
|
|
||
|
let triplet = '';
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-greylist.example.com\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
const from = command.match(/<(.+)>/)?.[1] || '';
|
||
|
triplet = `${socket.remoteAddress}-${from}`;
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('RCPT TO:')) {
|
||
|
const to = command.match(/<(.+)>/)?.[1] || '';
|
||
|
triplet += `-${to}`;
|
||
|
|
||
|
const now = Date.now();
|
||
|
const greylistEntry = greylist.get(triplet);
|
||
|
|
||
|
if (!greylistEntry) {
|
||
|
// First time seeing this triplet
|
||
|
greylist.set(triplet, { firstSeen: now, attempts: 1 });
|
||
|
console.log(' [Server] New sender - greylisting');
|
||
|
socket.write('451 4.7.1 Greylisting in effect, please retry later\r\n');
|
||
|
} else {
|
||
|
greylistEntry.attempts++;
|
||
|
const elapsed = now - greylistEntry.firstSeen;
|
||
|
|
||
|
if (elapsed < greylistDuration) {
|
||
|
console.log(` [Server] Too soon (${elapsed}ms) - still greylisted`);
|
||
|
socket.write('451 4.7.1 Greylisting in effect, please retry later\r\n');
|
||
|
} else {
|
||
|
console.log(` [Server] Greylist passed (${greylistEntry.attempts} attempts)`);
|
||
|
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: Message accepted after greylisting\r\n');
|
||
|
} else if (command === 'QUIT') {
|
||
|
socket.write('221 Bye\r\n');
|
||
|
socket.end();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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: 'Greylist test',
|
||
|
text: 'Testing greylisting mechanism'
|
||
|
});
|
||
|
|
||
|
// First attempt - should be greylisted
|
||
|
try {
|
||
|
await smtpClient.sendMail(email);
|
||
|
console.log(' Unexpected: First attempt succeeded');
|
||
|
} catch (error) {
|
||
|
console.log(' First attempt: Greylisted as expected');
|
||
|
expect(error.message).toContain('Greylisting');
|
||
|
}
|
||
|
|
||
|
// Wait and retry
|
||
|
console.log(` Waiting ${greylistDuration}ms before retry...`);
|
||
|
await new Promise(resolve => setTimeout(resolve, greylistDuration + 100));
|
||
|
|
||
|
// Second attempt - should succeed
|
||
|
const retryResult = await smtpClient.sendMail(email);
|
||
|
console.log(' Retry attempt: Accepted after greylist period');
|
||
|
expect(retryResult).toBeDefined();
|
||
|
expect(retryResult.response).toContain('after greylisting');
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 4: DNS blacklist (DNSBL) checking
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing DNSBL checking`);
|
||
|
|
||
|
const blacklistedIPs = ['192.168.1.100', '10.0.0.50'];
|
||
|
const blacklistedDomains = ['spam-domain.com', 'phishing-site.net'];
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
const clientIP = socket.remoteAddress || '';
|
||
|
console.log(` [Server] Client connected from ${clientIP}`);
|
||
|
|
||
|
// Simulate DNSBL check
|
||
|
const isBlacklisted = blacklistedIPs.some(ip => clientIP.includes(ip));
|
||
|
|
||
|
if (isBlacklisted) {
|
||
|
console.log(' [Server] IP found in DNSBL');
|
||
|
socket.write('554 5.7.1 Your IP is listed in DNSBL\r\n');
|
||
|
socket.end();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
socket.write('220 dnsbl.example.com ESMTP\r\n');
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
const domain = command.split(' ')[1];
|
||
|
if (blacklistedDomains.includes(domain)) {
|
||
|
console.log(' [Server] HELO domain in DNSBL');
|
||
|
socket.write('554 5.7.1 Your domain is blacklisted\r\n');
|
||
|
socket.end();
|
||
|
return;
|
||
|
}
|
||
|
socket.write('250-dnsbl.example.com\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
const fromAddress = command.match(/<(.+)>/)?.[1] || '';
|
||
|
const fromDomain = fromAddress.split('@')[1];
|
||
|
|
||
|
if (blacklistedDomains.includes(fromDomain)) {
|
||
|
console.log(' [Server] Sender domain in DNSBL');
|
||
|
socket.write('554 5.7.1 Sender domain is blacklisted\r\n');
|
||
|
} else {
|
||
|
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();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const smtpClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false
|
||
|
});
|
||
|
|
||
|
// Test with clean sender
|
||
|
const cleanEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@clean-domain.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'DNSBL test',
|
||
|
text: 'Testing DNSBL checking'
|
||
|
});
|
||
|
|
||
|
const result = await smtpClient.sendMail(cleanEmail);
|
||
|
console.log(' Clean sender: Accepted');
|
||
|
expect(result).toBeDefined();
|
||
|
|
||
|
// Test with blacklisted domain
|
||
|
const blacklistedEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@spam-domain.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Blacklisted domain test',
|
||
|
text: 'Testing from blacklisted domain'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await smtpClient.sendMail(blacklistedEmail);
|
||
|
console.log(' Unexpected: Blacklisted domain accepted');
|
||
|
} catch (error) {
|
||
|
console.log(' Blacklisted domain: Rejected');
|
||
|
expect(error.message).toContain('blacklisted');
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 5: Connection behavior analysis
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing connection behavior analysis`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
|
||
|
const connectionStart = Date.now();
|
||
|
let commandCount = 0;
|
||
|
let errorCount = 0;
|
||
|
let rapidCommands = 0;
|
||
|
let lastCommandTime = Date.now();
|
||
|
|
||
|
// Set initial timeout
|
||
|
socket.setTimeout(30000); // 30 seconds
|
||
|
|
||
|
socket.write('220 behavior.example.com ESMTP\r\n');
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const now = Date.now();
|
||
|
const timeSinceLastCommand = now - lastCommandTime;
|
||
|
lastCommandTime = now;
|
||
|
|
||
|
commandCount++;
|
||
|
|
||
|
// Check for rapid-fire commands (bot behavior)
|
||
|
if (timeSinceLastCommand < 50) {
|
||
|
rapidCommands++;
|
||
|
if (rapidCommands > 5) {
|
||
|
console.log(' [Server] Detected rapid-fire commands (bot behavior)');
|
||
|
socket.write('421 4.7.0 Suspicious behavior detected\r\n');
|
||
|
socket.end();
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
rapidCommands = 0; // Reset counter
|
||
|
}
|
||
|
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command} (${timeSinceLastCommand}ms since last)`);
|
||
|
|
||
|
// Check for invalid commands (spam bot behavior)
|
||
|
if (!command.match(/^(EHLO|HELO|MAIL FROM:|RCPT TO:|DATA|QUIT|RSET|NOOP|AUTH|\.)/i)) {
|
||
|
errorCount++;
|
||
|
if (errorCount > 3) {
|
||
|
console.log(' [Server] Too many invalid commands');
|
||
|
socket.write('421 4.7.0 Too many errors\r\n');
|
||
|
socket.end();
|
||
|
return;
|
||
|
}
|
||
|
socket.write('500 5.5.1 Command not recognized\r\n');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (command.startsWith('EHLO') || command.startsWith('HELO')) {
|
||
|
socket.write('250-behavior.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 === '.') {
|
||
|
const connectionDuration = Date.now() - connectionStart;
|
||
|
console.log(` [Server] Session duration: ${connectionDuration}ms, commands: ${commandCount}`);
|
||
|
socket.write('250 OK: Message accepted\r\n');
|
||
|
} else if (command === 'QUIT') {
|
||
|
socket.write('221 Bye\r\n');
|
||
|
socket.end();
|
||
|
} else if (command === 'NOOP') {
|
||
|
socket.write('250 OK\r\n');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
socket.on('timeout', () => {
|
||
|
console.log(' [Server] Connection timeout - possible spam bot');
|
||
|
socket.write('421 4.4.2 Connection timeout\r\n');
|
||
|
socket.end();
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Test normal behavior
|
||
|
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: 'Behavior test',
|
||
|
text: 'Testing normal email sending behavior'
|
||
|
});
|
||
|
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(' Normal behavior: Accepted');
|
||
|
expect(result).toBeDefined();
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 6: Attachment and link scanning
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing attachment and link scanning`);
|
||
|
|
||
|
const dangerousExtensions = ['.exe', '.scr', '.vbs', '.com', '.bat', '.cmd', '.pif'];
|
||
|
const suspiciousLinks = ['bit.ly', 'tinyurl.com', 'short.link'];
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 scanner.example.com ESMTP\r\n');
|
||
|
|
||
|
let inData = false;
|
||
|
let messageContent = '';
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const text = data.toString();
|
||
|
|
||
|
if (inData) {
|
||
|
messageContent += text;
|
||
|
if (text.includes('\r\n.\r\n')) {
|
||
|
inData = false;
|
||
|
|
||
|
let threatLevel = 0;
|
||
|
const threats: string[] = [];
|
||
|
|
||
|
// Check for dangerous attachments
|
||
|
const attachmentMatch = messageContent.match(/filename="([^"]+)"/gi);
|
||
|
if (attachmentMatch) {
|
||
|
attachmentMatch.forEach(match => {
|
||
|
const filename = match.match(/filename="([^"]+)"/i)?.[1] || '';
|
||
|
const extension = filename.substring(filename.lastIndexOf('.')).toLowerCase();
|
||
|
|
||
|
if (dangerousExtensions.includes(extension)) {
|
||
|
threatLevel += 10;
|
||
|
threats.push(`Dangerous attachment: ${filename}`);
|
||
|
console.log(` [Server] Found dangerous attachment: ${filename}`);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Check for suspicious links
|
||
|
const urlMatch = messageContent.match(/https?:\/\/[^\s]+/gi);
|
||
|
if (urlMatch) {
|
||
|
urlMatch.forEach(url => {
|
||
|
if (suspiciousLinks.some(domain => url.includes(domain))) {
|
||
|
threatLevel += 5;
|
||
|
threats.push(`Suspicious link: ${url}`);
|
||
|
console.log(` [Server] Found suspicious link: ${url}`);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Check for phishing patterns
|
||
|
if (messageContent.includes('verify your account') && urlMatch) {
|
||
|
threatLevel += 5;
|
||
|
threats.push('Possible phishing attempt');
|
||
|
}
|
||
|
|
||
|
console.log(` [Server] Threat level: ${threatLevel}`);
|
||
|
|
||
|
if (threatLevel >= 10) {
|
||
|
socket.write(`550 5.7.1 Message rejected: ${threats.join(', ')}\r\n`);
|
||
|
} else if (threatLevel >= 5) {
|
||
|
socket.write('250 OK: Message flagged for review\r\n');
|
||
|
} else {
|
||
|
socket.write('250 OK: Message scanned and accepted\r\n');
|
||
|
}
|
||
|
|
||
|
messageContent = '';
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const command = text.trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-scanner.example.com\r\n');
|
||
|
socket.write('250-SIZE 10485760\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');
|
||
|
inData = true;
|
||
|
} else if (command === 'QUIT') {
|
||
|
socket.write('221 Bye\r\n');
|
||
|
socket.end();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const smtpClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false
|
||
|
});
|
||
|
|
||
|
// Test 1: Clean email with safe attachment
|
||
|
const safeEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Document for review',
|
||
|
text: 'Please find the attached document.',
|
||
|
attachments: [{
|
||
|
filename: 'report.pdf',
|
||
|
content: 'PDF content here'
|
||
|
}]
|
||
|
});
|
||
|
|
||
|
const safeResult = await smtpClient.sendMail(safeEmail);
|
||
|
console.log(' Safe email: Scanned and accepted');
|
||
|
expect(safeResult.response).toContain('scanned and accepted');
|
||
|
|
||
|
// Test 2: Email with suspicious link
|
||
|
const suspiciousEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Check this out',
|
||
|
text: 'Click here: https://bit.ly/abc123 to verify your account',
|
||
|
html: '<p>Click <a href="https://bit.ly/abc123">here</a> to verify your account</p>'
|
||
|
});
|
||
|
|
||
|
const suspiciousResult = await smtpClient.sendMail(suspiciousEmail);
|
||
|
console.log(' Suspicious email: Flagged for review');
|
||
|
expect(suspiciousResult.response).toContain('flagged for review');
|
||
|
|
||
|
// Test 3: Email with dangerous attachment
|
||
|
const dangerousEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Important update',
|
||
|
text: 'Please run the attached file',
|
||
|
attachments: [{
|
||
|
filename: 'update.exe',
|
||
|
content: Buffer.from('MZ\x90\x00\x03') // Fake executable header
|
||
|
}]
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await smtpClient.sendMail(dangerousEmail);
|
||
|
console.log(' Unexpected: Dangerous attachment accepted');
|
||
|
} catch (error) {
|
||
|
console.log(' Dangerous attachment: Rejected');
|
||
|
expect(error.message).toContain('Dangerous attachment');
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
console.log(`\n${testId}: All ${scenarioCount} anti-spam scenarios tested ✓`);
|
||
|
});
|