627 lines
22 KiB
TypeScript
627 lines
22 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-09: should handle relay restrictions correctly', async (tools) => {
|
||
|
const testId = 'CSEC-09-relay-restrictions';
|
||
|
console.log(`\n${testId}: Testing relay restriction handling...`);
|
||
|
|
||
|
let scenarioCount = 0;
|
||
|
|
||
|
// Scenario 1: Open relay prevention
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing open relay prevention`);
|
||
|
|
||
|
const allowedDomains = ['example.com', 'trusted.com'];
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 relay.example.com ESMTP\r\n');
|
||
|
|
||
|
let authenticated = false;
|
||
|
let fromAddress = '';
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-relay.example.com\r\n');
|
||
|
socket.write('250-AUTH PLAIN LOGIN\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('AUTH')) {
|
||
|
authenticated = true;
|
||
|
console.log(' [Server] User authenticated');
|
||
|
socket.write('235 2.7.0 Authentication successful\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
fromAddress = command.match(/<(.+)>/)?.[1] || '';
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('RCPT TO:')) {
|
||
|
const toAddress = command.match(/<(.+)>/)?.[1] || '';
|
||
|
const toDomain = toAddress.split('@')[1];
|
||
|
const fromDomain = fromAddress.split('@')[1];
|
||
|
|
||
|
console.log(` [Server] Relay check: from=${fromDomain}, to=${toDomain}, auth=${authenticated}`);
|
||
|
|
||
|
// Check relay permissions
|
||
|
if (authenticated) {
|
||
|
// Authenticated users can relay
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (allowedDomains.includes(toDomain)) {
|
||
|
// Accept mail for local domains
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (allowedDomains.includes(fromDomain)) {
|
||
|
// Accept mail from local domains (outbound)
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else {
|
||
|
// Reject relay attempt
|
||
|
console.log(' [Server] Rejecting relay attempt');
|
||
|
socket.write('554 5.7.1 Relay access denied\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();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Test 1: Unauthenticated relay attempt (should fail)
|
||
|
const unauthClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false
|
||
|
});
|
||
|
|
||
|
const relayEmail = new plugins.smartmail.Email({
|
||
|
from: 'external@untrusted.com',
|
||
|
to: ['recipient@another-external.com'],
|
||
|
subject: 'Relay test',
|
||
|
text: 'Testing open relay prevention'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await unauthClient.sendMail(relayEmail);
|
||
|
console.log(' Unexpected: Relay was allowed');
|
||
|
} catch (error) {
|
||
|
console.log(' Expected: Relay denied for unauthenticated user');
|
||
|
expect(error.message).toContain('Relay access denied');
|
||
|
}
|
||
|
|
||
|
// Test 2: Local delivery (should succeed)
|
||
|
const localEmail = new plugins.smartmail.Email({
|
||
|
from: 'external@untrusted.com',
|
||
|
to: ['recipient@example.com'], // Local domain
|
||
|
subject: 'Local delivery test',
|
||
|
text: 'Testing local delivery'
|
||
|
});
|
||
|
|
||
|
const localResult = await unauthClient.sendMail(localEmail);
|
||
|
console.log(' Local delivery allowed');
|
||
|
expect(localResult).toBeDefined();
|
||
|
expect(localResult.messageId).toBeDefined();
|
||
|
|
||
|
// Test 3: Authenticated relay (should succeed)
|
||
|
const authClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false,
|
||
|
auth: {
|
||
|
user: 'testuser',
|
||
|
pass: 'testpass'
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const authRelayResult = await authClient.sendMail(relayEmail);
|
||
|
console.log(' Authenticated relay allowed');
|
||
|
expect(authRelayResult).toBeDefined();
|
||
|
expect(authRelayResult.messageId).toBeDefined();
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 2: IP-based relay restrictions
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing IP-based relay restrictions`);
|
||
|
|
||
|
const trustedIPs = ['127.0.0.1', '::1', '10.0.0.0/8', '192.168.0.0/16'];
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
const clientIP = socket.remoteAddress || '';
|
||
|
console.log(` [Server] Client connected from ${clientIP}`);
|
||
|
socket.write('220 ip-relay.example.com ESMTP\r\n');
|
||
|
|
||
|
const isTrustedIP = (ip: string): boolean => {
|
||
|
// Simple check for demo (in production, use proper IP range checking)
|
||
|
return trustedIPs.some(trusted =>
|
||
|
ip === trusted ||
|
||
|
ip.includes('127.0.0.1') ||
|
||
|
ip.includes('::1')
|
||
|
);
|
||
|
};
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
console.log(` [Server] Received: ${command}`);
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-ip-relay.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:')) {
|
||
|
const toAddress = command.match(/<(.+)>/)?.[1] || '';
|
||
|
const isLocalDomain = toAddress.includes('@example.com');
|
||
|
|
||
|
if (isTrustedIP(clientIP)) {
|
||
|
console.log(' [Server] Trusted IP - allowing relay');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (isLocalDomain) {
|
||
|
console.log(' [Server] Local delivery - allowing');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else {
|
||
|
console.log(' [Server] Untrusted IP - denying relay');
|
||
|
socket.write('554 5.7.1 Relay access denied for IP\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 from localhost (trusted)
|
||
|
const email = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@external.com'],
|
||
|
subject: 'IP-based relay test',
|
||
|
text: 'Testing IP-based relay restrictions'
|
||
|
});
|
||
|
|
||
|
const result = await smtpClient.sendMail(email);
|
||
|
console.log(' Relay allowed from trusted IP (localhost)');
|
||
|
expect(result).toBeDefined();
|
||
|
expect(result.messageId).toBeDefined();
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 3: Sender domain restrictions
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing sender domain restrictions`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 sender-restrict.example.com ESMTP\r\n');
|
||
|
|
||
|
let authenticated = false;
|
||
|
let authUser = '';
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-sender-restrict.example.com\r\n');
|
||
|
socket.write('250-AUTH PLAIN\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('AUTH PLAIN')) {
|
||
|
const credentials = command.substring(11);
|
||
|
if (credentials) {
|
||
|
const decoded = Buffer.from(credentials, 'base64').toString();
|
||
|
authUser = decoded.split('\0')[1] || '';
|
||
|
authenticated = true;
|
||
|
console.log(` [Server] User authenticated: ${authUser}`);
|
||
|
socket.write('235 2.7.0 Authentication successful\r\n');
|
||
|
} else {
|
||
|
socket.write('334\r\n');
|
||
|
}
|
||
|
} else if (!command.startsWith('AUTH') && authenticated === false && command.length > 20) {
|
||
|
// PLAIN auth credentials
|
||
|
const decoded = Buffer.from(command, 'base64').toString();
|
||
|
authUser = decoded.split('\0')[1] || '';
|
||
|
authenticated = true;
|
||
|
console.log(` [Server] User authenticated: ${authUser}`);
|
||
|
socket.write('235 2.7.0 Authentication successful\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
const fromAddress = command.match(/<(.+)>/)?.[1] || '';
|
||
|
const fromDomain = fromAddress.split('@')[1];
|
||
|
|
||
|
if (!authenticated) {
|
||
|
// Unauthenticated users can only send from specific domains
|
||
|
if (fromDomain === 'example.com' || fromDomain === 'trusted.com') {
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else {
|
||
|
console.log(` [Server] Rejecting sender domain: ${fromDomain}`);
|
||
|
socket.write('553 5.7.1 Sender domain not allowed\r\n');
|
||
|
}
|
||
|
} else {
|
||
|
// Authenticated users must use their own domain
|
||
|
const expectedDomain = authUser.split('@')[1];
|
||
|
if (fromDomain === expectedDomain || fromDomain === 'example.com') {
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else {
|
||
|
console.log(` [Server] Auth user ${authUser} cannot send from ${fromDomain}`);
|
||
|
socket.write('553 5.7.1 Authenticated sender mismatch\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();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Test 1: Unauthorized sender domain
|
||
|
const unauthClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false
|
||
|
});
|
||
|
|
||
|
const unauthorizedEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@untrusted.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Unauthorized sender test',
|
||
|
text: 'Testing sender domain restrictions'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await unauthClient.sendMail(unauthorizedEmail);
|
||
|
console.log(' Unexpected: Unauthorized sender accepted');
|
||
|
} catch (error) {
|
||
|
console.log(' Expected: Unauthorized sender domain rejected');
|
||
|
expect(error.message).toContain('Sender domain not allowed');
|
||
|
}
|
||
|
|
||
|
// Test 2: Authorized sender domain
|
||
|
const authorizedEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@external.com'],
|
||
|
subject: 'Authorized sender test',
|
||
|
text: 'Testing authorized sender domain'
|
||
|
});
|
||
|
|
||
|
const result = await unauthClient.sendMail(authorizedEmail);
|
||
|
console.log(' Authorized sender domain accepted');
|
||
|
expect(result).toBeDefined();
|
||
|
|
||
|
// Test 3: Authenticated sender mismatch
|
||
|
const authClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false,
|
||
|
auth: {
|
||
|
user: 'user@example.com',
|
||
|
pass: 'testpass'
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const mismatchEmail = new plugins.smartmail.Email({
|
||
|
from: 'someone@otherdomain.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'Sender mismatch test',
|
||
|
text: 'Testing authenticated sender mismatch'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await authClient.sendMail(mismatchEmail);
|
||
|
console.log(' Unexpected: Sender mismatch accepted');
|
||
|
} catch (error) {
|
||
|
console.log(' Expected: Authenticated sender mismatch rejected');
|
||
|
expect(error.message).toContain('Authenticated sender mismatch');
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 4: Recipient count limits
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing recipient count limits`);
|
||
|
|
||
|
const maxRecipientsUnauthenticated = 5;
|
||
|
const maxRecipientsAuthenticated = 100;
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
console.log(' [Server] Client connected');
|
||
|
socket.write('220 recipient-limit.example.com ESMTP\r\n');
|
||
|
|
||
|
let authenticated = false;
|
||
|
let recipientCount = 0;
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-recipient-limit.example.com\r\n');
|
||
|
socket.write('250-AUTH PLAIN\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('AUTH')) {
|
||
|
authenticated = true;
|
||
|
socket.write('235 2.7.0 Authentication successful\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
recipientCount = 0; // Reset for new message
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('RCPT TO:')) {
|
||
|
recipientCount++;
|
||
|
const limit = authenticated ? maxRecipientsAuthenticated : maxRecipientsUnauthenticated;
|
||
|
|
||
|
console.log(` [Server] Recipient ${recipientCount}/${limit} (auth: ${authenticated})`);
|
||
|
|
||
|
if (recipientCount > limit) {
|
||
|
socket.write('452 4.5.3 Too many recipients\r\n');
|
||
|
} else {
|
||
|
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();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Test unauthenticated recipient limit
|
||
|
const unauthClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false
|
||
|
});
|
||
|
|
||
|
const manyRecipients = Array(10).fill(null).map((_, i) => `recipient${i + 1}@example.com`);
|
||
|
|
||
|
const bulkEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: manyRecipients,
|
||
|
subject: 'Recipient limit test',
|
||
|
text: 'Testing recipient count limits'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
const result = await unauthClient.sendMail(bulkEmail);
|
||
|
console.log(` Sent to ${result.accepted?.length || 0} recipients (unauthenticated)`);
|
||
|
// Some recipients should be rejected
|
||
|
expect(result.rejected?.length).toBeGreaterThan(0);
|
||
|
} catch (error) {
|
||
|
console.log(' Some recipients rejected due to limit');
|
||
|
}
|
||
|
|
||
|
// Test authenticated higher limit
|
||
|
const authClient = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false,
|
||
|
auth: {
|
||
|
user: 'testuser',
|
||
|
pass: 'testpass'
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const authResult = await authClient.sendMail(bulkEmail);
|
||
|
console.log(` Authenticated user sent to ${authResult.accepted?.length || 0} recipients`);
|
||
|
expect(authResult.accepted?.length).toBe(manyRecipients.length);
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 5: Rate-based relay restrictions
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing rate-based relay restrictions`);
|
||
|
|
||
|
const messageRates = new Map<string, { count: number; resetTime: number }>();
|
||
|
const rateLimit = 3; // 3 messages per minute
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
const clientIP = socket.remoteAddress || 'unknown';
|
||
|
console.log(` [Server] Client connected from ${clientIP}`);
|
||
|
socket.write('220 rate-limit.example.com ESMTP\r\n');
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-rate-limit.example.com\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
const now = Date.now();
|
||
|
const clientRate = messageRates.get(clientIP) || { count: 0, resetTime: now + 60000 };
|
||
|
|
||
|
if (now > clientRate.resetTime) {
|
||
|
// Reset rate limit
|
||
|
clientRate.count = 0;
|
||
|
clientRate.resetTime = now + 60000;
|
||
|
}
|
||
|
|
||
|
clientRate.count++;
|
||
|
messageRates.set(clientIP, clientRate);
|
||
|
|
||
|
console.log(` [Server] Message ${clientRate.count}/${rateLimit} from ${clientIP}`);
|
||
|
|
||
|
if (clientRate.count > rateLimit) {
|
||
|
socket.write('421 4.7.0 Rate limit exceeded, try again later\r\n');
|
||
|
socket.end();
|
||
|
} 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();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Send multiple messages to test rate limiting
|
||
|
const results: boolean[] = [];
|
||
|
|
||
|
for (let i = 0; i < 5; i++) {
|
||
|
try {
|
||
|
const client = createSmtpClient({
|
||
|
host: testServer.hostname,
|
||
|
port: testServer.port,
|
||
|
secure: false
|
||
|
});
|
||
|
|
||
|
const email = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: `Rate test ${i + 1}`,
|
||
|
text: `Testing rate limits - message ${i + 1}`
|
||
|
});
|
||
|
|
||
|
const result = await client.sendMail(email);
|
||
|
console.log(` Message ${i + 1}: Sent successfully`);
|
||
|
results.push(true);
|
||
|
} catch (error) {
|
||
|
console.log(` Message ${i + 1}: Rate limited`);
|
||
|
results.push(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// First 3 should succeed, rest should fail
|
||
|
const successCount = results.filter(r => r).length;
|
||
|
console.log(` Sent ${successCount}/${results.length} messages before rate limit`);
|
||
|
expect(successCount).toBe(rateLimit);
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
// Scenario 6: SPF-based relay restrictions
|
||
|
await (async () => {
|
||
|
scenarioCount++;
|
||
|
console.log(`\nScenario ${scenarioCount}: Testing SPF-based relay restrictions`);
|
||
|
|
||
|
const testServer = await createTestServer({
|
||
|
onConnection: async (socket) => {
|
||
|
const clientIP = socket.remoteAddress || '';
|
||
|
console.log(` [Server] Client connected from ${clientIP}`);
|
||
|
socket.write('220 spf-relay.example.com ESMTP\r\n');
|
||
|
|
||
|
const checkSPF = (domain: string, ip: string): string => {
|
||
|
// Simplified SPF check for demo
|
||
|
console.log(` [Server] Checking SPF for ${domain} from ${ip}`);
|
||
|
|
||
|
// In production, would do actual DNS lookups
|
||
|
if (domain === 'example.com' && (ip.includes('127.0.0.1') || ip.includes('::1'))) {
|
||
|
return 'pass';
|
||
|
} else if (domain === 'spf-fail.com') {
|
||
|
return 'fail';
|
||
|
} else {
|
||
|
return 'none';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
socket.on('data', (data) => {
|
||
|
const command = data.toString().trim();
|
||
|
|
||
|
if (command.startsWith('EHLO')) {
|
||
|
socket.write('250-spf-relay.example.com\r\n');
|
||
|
socket.write('250 OK\r\n');
|
||
|
} else if (command.startsWith('MAIL FROM:')) {
|
||
|
const fromAddress = command.match(/<(.+)>/)?.[1] || '';
|
||
|
const domain = fromAddress.split('@')[1];
|
||
|
|
||
|
const spfResult = checkSPF(domain, clientIP);
|
||
|
console.log(` [Server] SPF result: ${spfResult}`);
|
||
|
|
||
|
if (spfResult === 'fail') {
|
||
|
socket.write('550 5.7.1 SPF check failed\r\n');
|
||
|
} else {
|
||
|
socket.write('250 OK SPF=' + spfResult + '\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 1: SPF pass
|
||
|
const spfPassEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@example.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'SPF pass test',
|
||
|
text: 'Testing SPF-based relay - should pass'
|
||
|
});
|
||
|
|
||
|
const passResult = await smtpClient.sendMail(spfPassEmail);
|
||
|
console.log(' SPF check passed');
|
||
|
expect(passResult).toBeDefined();
|
||
|
expect(passResult.response).toContain('SPF=pass');
|
||
|
|
||
|
// Test 2: SPF fail
|
||
|
const spfFailEmail = new plugins.smartmail.Email({
|
||
|
from: 'sender@spf-fail.com',
|
||
|
to: ['recipient@example.com'],
|
||
|
subject: 'SPF fail test',
|
||
|
text: 'Testing SPF-based relay - should fail'
|
||
|
});
|
||
|
|
||
|
try {
|
||
|
await smtpClient.sendMail(spfFailEmail);
|
||
|
console.log(' Unexpected: SPF fail was accepted');
|
||
|
} catch (error) {
|
||
|
console.log(' Expected: SPF check failed');
|
||
|
expect(error.message).toContain('SPF check failed');
|
||
|
}
|
||
|
|
||
|
await testServer.server.close();
|
||
|
})();
|
||
|
|
||
|
console.log(`\n${testId}: All ${scenarioCount} relay restriction scenarios tested ✓`);
|
||
|
});
|