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(); 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 ✓`); });