import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../../../ts/plugins.js'; import * as net from 'net'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js' import type { ITestServer } from '../../helpers/server.loader.js'; const TEST_PORT = 2525; let testServer: ITestServer; // Helper function to wait for SMTP response const waitForResponse = (socket: net.Socket, expectedCode?: string, timeout = 5000): Promise => { return new Promise((resolve, reject) => { let buffer = ''; const timer = setTimeout(() => { socket.removeListener('data', handler); reject(new Error(`Timeout waiting for ${expectedCode || 'any'} response`)); }, timeout); const handler = (data: Buffer) => { buffer += data.toString(); const lines = buffer.split('\r\n'); // Check if we have a complete response for (const line of lines) { if (expectedCode) { if (line.startsWith(expectedCode + ' ')) { clearTimeout(timer); socket.removeListener('data', handler); resolve(buffer); return; } } else { // Any complete response line if (line.match(/^\d{3} /)) { clearTimeout(timer); socket.removeListener('data', handler); resolve(buffer); return; } } } }; socket.on('data', handler); }); }; tap.test('setup - start test server', async (toolsArg) => { testServer = await startTestServer({ port: TEST_PORT }); await toolsArg.delayFor(1000); }); tap.test('RFC 7208 SPF - Server handles SPF checks', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); socket.on('connect', async () => { try { const spfResults: any[] = []; // Test domains simulating different SPF scenarios const spfTestDomains = [ 'spf-pass.example.com', // Should have valid SPF record allowing sender 'spf-fail.example.com', // Should have SPF record that fails 'spf-neutral.example.com', // Should have neutral SPF record 'no-spf.example.com' // Should have no SPF record ]; // Wait for greeting await waitForResponse(socket, '220'); // Send EHLO socket.write('EHLO testclient\r\n'); const ehloResponse = await waitForResponse(socket, '250'); // Check if server advertises SPF support const advertisesSpf = ehloResponse.toLowerCase().includes('spf'); console.log('Server advertises SPF:', advertisesSpf); // Test each domain for (let i = 0; i < spfTestDomains.length; i++) { const domain = spfTestDomains[i]; const testEmail = `spf-test@${domain}`; spfResults[i] = { domain: domain, email: testEmail, mailFromAccepted: false, rcptAccepted: false, spfFailed: false }; console.log(`Testing SPF for domain: ${domain}`); socket.write(`MAIL FROM:<${testEmail}>\r\n`); const mailResponse = await waitForResponse(socket); spfResults[i].mailFromResponse = mailResponse.trim(); if (mailResponse.includes('250')) { // MAIL FROM accepted spfResults[i].mailFromAccepted = true; socket.write(`RCPT TO:\r\n`); const rcptResponse = await waitForResponse(socket); if (rcptResponse.includes('250')) { spfResults[i].rcptAccepted = true; } } else if (mailResponse.includes('550') || mailResponse.includes('553')) { // SPF failure (expected for some domains) spfResults[i].spfFailed = true; } // Reset for next test socket.write('RSET\r\n'); await waitForResponse(socket, '250'); } // All tests complete console.log('SPF test results:', spfResults); // Check that server handled all domains const allDomainsHandled = spfResults.every(result => result.mailFromResponse !== undefined && result.mailFromResponse !== 'pending' ); expect(allDomainsHandled).toEqual(true); // Send QUIT socket.write('QUIT\r\n'); await waitForResponse(socket, '221'); socket.end(); done.resolve(); } catch (err) { console.error('Test error:', err); socket.end(); done.reject(err); } }); await done.promise; }); tap.test('RFC 7208 SPF - SPF record syntax handling', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); socket.on('connect', async () => { try { // Wait for greeting await waitForResponse(socket, '220'); // Send EHLO socket.write('EHLO testclient\r\n'); await waitForResponse(socket, '250'); // Test with domain that might have complex SPF record socket.write('MAIL FROM:\r\n'); const mailResponse = await waitForResponse(socket); // Server should handle this appropriately (accept or reject based on SPF) const handled = mailResponse.includes('250') || mailResponse.includes('550') || mailResponse.includes('553'); expect(handled).toEqual(true); console.log('SPF handling response:', mailResponse.trim()); // Send QUIT socket.write('QUIT\r\n'); await waitForResponse(socket, '221'); socket.end(); done.resolve(); } catch (err) { console.error('Test error:', err); socket.end(); done.reject(err); } }); await done.promise; }); tap.test('RFC 7208 SPF - Received-SPF header', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); socket.on('connect', async () => { try { // Wait for greeting await waitForResponse(socket, '220'); // Send EHLO socket.write('EHLO testclient\r\n'); await waitForResponse(socket, '250'); // Send MAIL FROM socket.write('MAIL FROM:\r\n'); await waitForResponse(socket, '250'); // Send RCPT TO socket.write('RCPT TO:\r\n'); await waitForResponse(socket, '250'); // Send DATA socket.write('DATA\r\n'); await waitForResponse(socket, '354'); // Send email to check if server adds Received-SPF header const email = [ `Date: ${new Date().toUTCString()}`, `From: sender@example.com`, `To: recipient@example.com`, `Subject: SPF Header Test`, `Message-ID: <${Date.now()}@example.com>`, '', 'Testing if server adds Received-SPF header.', '.', '' ].join('\r\n'); socket.write(email); await waitForResponse(socket, '250'); console.log('Email accepted - server should process SPF'); // Send QUIT socket.write('QUIT\r\n'); await waitForResponse(socket, '221'); socket.end(); done.resolve(); } catch (err) { console.error('Test error:', err); socket.end(); done.reject(err); } }); await done.promise; }); tap.test('RFC 7208 SPF - IPv4 and IPv6 mechanism support', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); socket.on('connect', async () => { try { // Wait for greeting await waitForResponse(socket, '220'); // Test with IPv6 address representation socket.write('EHLO [::1]\r\n'); await waitForResponse(socket, '250'); // Test domain with IP-based SPF mechanisms socket.write('MAIL FROM:\r\n'); const mailResponse = await waitForResponse(socket); // Server should handle IP-based SPF mechanisms const handled = mailResponse.includes('250') || mailResponse.includes('550') || mailResponse.includes('553'); expect(handled).toEqual(true); console.log('IP mechanism SPF response:', mailResponse.trim()); // Send QUIT socket.write('QUIT\r\n'); await waitForResponse(socket, '221'); socket.end(); done.resolve(); } catch (err) { console.error('Test error:', err); socket.end(); done.reject(err); } }); await done.promise; }); tap.test('cleanup - stop test server', async () => { await stopTestServer(testServer); }); export default tap.start();