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 7489 DMARC - Server handles DMARC policies', 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 dmarcResults: any[] = []; // Test domains simulating different DMARC policies const dmarcTestScenarios = [ { domain: 'dmarc-reject.example.com', policy: 'reject', alignment: 'strict' }, { domain: 'dmarc-quarantine.example.com', policy: 'quarantine', alignment: 'relaxed' }, { domain: 'dmarc-none.example.com', policy: 'none', alignment: 'relaxed' } ]; // 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 DMARC support const advertisesDmarc = ehloResponse.toLowerCase().includes('dmarc'); console.log('Server advertises DMARC:', advertisesDmarc); // Test each scenario for (let i = 0; i < dmarcTestScenarios.length; i++) { const scenario = dmarcTestScenarios[i]; const testFromAddress = `dmarc-test@${scenario.domain}`; dmarcResults[i] = { domain: scenario.domain, policy: scenario.policy, mailFromAccepted: false, rcptAccepted: false }; console.log(`Testing DMARC policy: ${scenario.policy} for domain: ${scenario.domain}`); socket.write(`MAIL FROM:<${testFromAddress}>\r\n`); const mailResponse = await waitForResponse(socket); dmarcResults[i].mailFromResponse = mailResponse.trim(); if (mailResponse.includes('250')) { dmarcResults[i].mailFromAccepted = true; socket.write(`RCPT TO:\r\n`); const rcptResponse = await waitForResponse(socket); if (rcptResponse.includes('250')) { dmarcResults[i].rcptAccepted = true; // Send DATA socket.write('DATA\r\n'); await waitForResponse(socket, '354'); // Send email with DMARC-relevant headers const email = [ `From: dmarc-test@${scenario.domain}`, `To: recipient@example.com`, `Subject: DMARC RFC 7489 Compliance Test - ${scenario.policy}`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, `DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=${scenario.domain}; s=default;`, ` h=from:to:subject:date; bh=testbodyhash; b=testsignature`, `Authentication-Results: example.org; spf=pass smtp.mailfrom=${scenario.domain}`, '', `This email tests DMARC ${scenario.policy} policy compliance.`, 'The server should handle DMARC policies according to RFC 7489.', '.', '' ].join('\r\n'); socket.write(email); const dataResponse = await waitForResponse(socket, '250'); dmarcResults[i].emailAccepted = true; console.log(`DMARC ${scenario.policy} policy email accepted`); } } else if (mailResponse.includes('550') || mailResponse.includes('553')) { // DMARC policy rejection (expected for some scenarios) dmarcResults[i].dmarcRejected = true; dmarcResults[i].rejectionResponse = mailResponse.trim(); console.log(`DMARC ${scenario.policy} policy rejected as expected`); } // Reset for next test socket.write('RSET\r\n'); await waitForResponse(socket, '250'); } // All tests complete console.log('DMARC test results:', dmarcResults); // Check that server handled all scenarios const allScenariosHandled = dmarcResults.every(result => result.mailFromResponse !== undefined ); expect(allScenariosHandled).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 7489 DMARC - Alignment testing', 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 misaligned domain (envelope vs header) socket.write('MAIL FROM:\r\n'); await waitForResponse(socket, '250'); socket.write('RCPT TO:\r\n'); await waitForResponse(socket, '250'); socket.write('DATA\r\n'); await waitForResponse(socket, '354'); // Email with different header From domain (testing alignment) const email = [ `From: sender@header-domain.com`, `To: recipient@example.com`, `Subject: DMARC Alignment Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, `DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=header-domain.com; s=default;`, ` h=from:to:subject:date; bh=alignmenthash; b=alignmentsig`, '', 'Testing DMARC domain alignment (envelope vs header From).', '.', '' ].join('\r\n'); socket.write(email); const response = await waitForResponse(socket); const accepted = response.includes('250'); console.log(`Alignment test ${accepted ? 'accepted' : 'rejected due to alignment failure'}`); // 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 7489 DMARC - Subdomain policy', 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 subdomain policy inheritance socket.write('MAIL FROM:\r\n'); await waitForResponse(socket, '250'); socket.write('RCPT TO:\r\n'); await waitForResponse(socket, '250'); socket.write('DATA\r\n'); await waitForResponse(socket, '354'); // Email from subdomain to test policy inheritance const email = [ `From: sender@subdomain.dmarc-policy.com`, `To: recipient@example.com`, `Subject: DMARC Subdomain Policy Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, `DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=subdomain.dmarc-policy.com; s=default;`, ` h=from:to:subject:date; bh=subdomainhash; b=subdomainsig`, '', 'Testing DMARC subdomain policy inheritance.', '.', '' ].join('\r\n'); socket.write(email); const response = await waitForResponse(socket); const accepted = response.includes('250'); console.log(`Subdomain policy test ${accepted ? 'accepted' : 'rejected'}`); // 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 7489 DMARC - Report generation hint', 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'); socket.write('MAIL FROM:\r\n'); await waitForResponse(socket, '250'); socket.write('RCPT TO:\r\n'); await waitForResponse(socket, '250'); socket.write('DATA\r\n'); await waitForResponse(socket, '354'); // Email with DMARC report request headers const email = [ `From: dmarc-report@example.com`, `To: recipient@example.com`, `Subject: DMARC Report Generation Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, `DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=default;`, ` h=from:to:subject:date; bh=reporthash; b=reportsig`, `Authentication-Results: mta.example.com;`, ` dmarc=pass (p=none dis=none) header.from=example.com`, '', 'Testing DMARC report generation capabilities.', 'Server should log DMARC results for reporting.', '.', '' ].join('\r\n'); socket.write(email); await waitForResponse(socket, '250'); console.log('DMARC report test email accepted'); // 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();