import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import * as net from 'net'; import { startTestServer, stopTestServer, TEST_PORT, sendEmailWithRawSocket } from '../server.loader.js'; import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: SmtpServer; tap.test('setup - start test server', async () => { testServer = await startTestServer(); await plugins.smartdelay.delayFor(1000); }); tap.test('RFC 3461 DSN - DSN extension advertised', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); let dataBuffer = ''; socket.on('data', (data) => { dataBuffer += data.toString(); console.log('Server response:', data.toString()); if (dataBuffer.includes('220 ') && !dataBuffer.includes('EHLO')) { // Initial greeting received socket.write('EHLO testclient\r\n'); dataBuffer = ''; } else if (dataBuffer.includes('250')) { // Check if DSN extension is advertised const advertisesDsn = dataBuffer.toLowerCase().includes('dsn'); console.log('DSN extension advertised:', advertisesDsn); // Parse extensions const lines = dataBuffer.split('\r\n'); const extensions = lines .filter(line => line.startsWith('250-') || (line.startsWith('250 ') && lines.indexOf(line) > 0)) .map(line => line.substring(4).split(' ')[0].toUpperCase()); console.log('Server extensions:', extensions); socket.write('QUIT\r\n'); socket.end(); done.resolve(); } }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); await done.promise; }); tap.test('RFC 3461 DSN - MAIL FROM with DSN parameters', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); let dataBuffer = ''; let step = 'greeting'; socket.on('data', (data) => { dataBuffer += data.toString(); console.log('Server response:', data.toString()); if (step === 'greeting' && dataBuffer.includes('220 ')) { step = 'ehlo'; socket.write('EHLO testclient\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail_dsn'; // Test MAIL FROM with DSN parameters (RFC 3461) socket.write('MAIL FROM: RET=FULL ENVID=test-envelope-123\r\n'); dataBuffer = ''; } else if (step === 'mail_dsn') { // Server should either accept (250) or reject with proper error const accepted = dataBuffer.includes('250'); const properlyRejected = dataBuffer.includes('501') || dataBuffer.includes('555'); expect(accepted || properlyRejected).toBeTrue(); console.log(`DSN parameters in MAIL FROM ${accepted ? 'accepted' : 'rejected'}`); if (accepted) { // Reset to test other parameters socket.write('RSET\r\n'); step = 'reset1'; } else { socket.write('QUIT\r\n'); socket.end(); done.resolve(); } dataBuffer = ''; } else if (step === 'reset1' && dataBuffer.includes('250')) { step = 'mail_dsn_hdrs'; // Test with RET=HDRS socket.write('MAIL FROM: RET=HDRS\r\n'); dataBuffer = ''; } else if (step === 'mail_dsn_hdrs') { const accepted = dataBuffer.includes('250'); console.log(`RET=HDRS parameter ${accepted ? 'accepted' : 'rejected'}`); socket.write('QUIT\r\n'); socket.end(); done.resolve(); } }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); await done.promise; }); tap.test('RFC 3461 DSN - RCPT TO with DSN parameters', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); let dataBuffer = ''; let step = 'greeting'; socket.on('data', (data) => { dataBuffer += data.toString(); console.log('Server response:', data.toString()); if (step === 'greeting' && dataBuffer.includes('220 ')) { step = 'ehlo'; socket.write('EHLO testclient\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail'; socket.write('MAIL FROM:\r\n'); dataBuffer = ''; } else if (step === 'mail' && dataBuffer.includes('250')) { step = 'rcpt_dsn'; // Test RCPT TO with DSN parameters socket.write('RCPT TO: NOTIFY=SUCCESS,FAILURE ORCPT=rfc822;recipient@example.com\r\n'); dataBuffer = ''; } else if (step === 'rcpt_dsn') { // Server should either accept (250) or reject with proper error const accepted = dataBuffer.includes('250'); const properlyRejected = dataBuffer.includes('501') || dataBuffer.includes('555'); expect(accepted || properlyRejected).toBeTrue(); console.log(`DSN parameters in RCPT TO ${accepted ? 'accepted' : 'rejected'}`); if (accepted) { // Reset to test other notify values socket.write('RSET\r\n'); step = 'reset1'; } else { socket.write('QUIT\r\n'); socket.end(); done.resolve(); } dataBuffer = ''; } else if (step === 'reset1' && dataBuffer.includes('250')) { step = 'mail2'; socket.write('MAIL FROM:\r\n'); dataBuffer = ''; } else if (step === 'mail2' && dataBuffer.includes('250')) { step = 'rcpt_never'; // Test NOTIFY=NEVER socket.write('RCPT TO: NOTIFY=NEVER\r\n'); dataBuffer = ''; } else if (step === 'rcpt_never') { const accepted = dataBuffer.includes('250'); console.log(`NOTIFY=NEVER parameter ${accepted ? 'accepted' : 'rejected'}`); socket.write('QUIT\r\n'); socket.end(); done.resolve(); } }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); await done.promise; }); tap.test('RFC 3461 DSN - Complete DSN-enabled email', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); let dataBuffer = ''; let step = 'greeting'; socket.on('data', (data) => { dataBuffer += data.toString(); console.log('Server response:', data.toString()); if (step === 'greeting' && dataBuffer.includes('220 ')) { step = 'ehlo'; socket.write('EHLO testclient\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail'; // Try with DSN parameters, fallback to regular if not supported socket.write('MAIL FROM: RET=FULL ENVID=test123\r\n'); dataBuffer = ''; } else if (step === 'mail') { if (dataBuffer.includes('250')) { step = 'rcpt'; socket.write('RCPT TO: NOTIFY=SUCCESS,FAILURE,DELAY\r\n'); } else if (dataBuffer.includes('501') || dataBuffer.includes('555')) { // DSN not supported, try without parameters console.log('DSN parameters not supported, using plain MAIL FROM'); step = 'mail_plain'; socket.write('MAIL FROM:\r\n'); } dataBuffer = ''; } else if (step === 'mail_plain' && dataBuffer.includes('250')) { step = 'rcpt'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt') { if (dataBuffer.includes('250')) { step = 'data'; socket.write('DATA\r\n'); } else if (dataBuffer.includes('501') || dataBuffer.includes('555')) { // DSN RCPT parameters not supported, try plain console.log('DSN RCPT parameters not supported, using plain RCPT TO'); socket.write('RCPT TO:\r\n'); step = 'rcpt_plain'; } dataBuffer = ''; } else if (step === 'rcpt_plain' && dataBuffer.includes('250')) { step = 'data'; socket.write('DATA\r\n'); dataBuffer = ''; } else if (step === 'data' && dataBuffer.includes('354')) { const email = [ `From: sender@example.com`, `To: recipient@example.com`, `Subject: RFC 3461 DSN Compliance Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, '', 'This email tests RFC 3461 DSN (Delivery Status Notification) compliance.', 'The server should handle DSN parameters according to RFC 3461.', '.', '' ].join('\r\n'); socket.write(email); dataBuffer = ''; } else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted')) { console.log('DSN-enabled email accepted'); socket.write('QUIT\r\n'); socket.end(); done.resolve(); } }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); await done.promise; }); tap.test('RFC 3461 DSN - Invalid DSN parameter handling', async (tools) => { const done = tools.defer(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 30000 }); let dataBuffer = ''; let step = 'greeting'; socket.on('data', (data) => { dataBuffer += data.toString(); console.log('Server response:', data.toString()); if (step === 'greeting' && dataBuffer.includes('220 ')) { step = 'ehlo'; socket.write('EHLO testclient\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail_invalid'; // Test with invalid RET value socket.write('MAIL FROM: RET=INVALID\r\n'); dataBuffer = ''; } else if (step === 'mail_invalid') { // Should reject with 501 or similar const properlyRejected = dataBuffer.includes('501') || dataBuffer.includes('555') || dataBuffer.includes('500'); if (properlyRejected) { console.log('Invalid RET parameter properly rejected'); expect(true).toBeTrue(); } else if (dataBuffer.includes('250')) { // Server ignores unknown parameters (also acceptable) console.log('Server ignores invalid DSN parameters'); } // Reset and test invalid NOTIFY socket.write('RSET\r\n'); step = 'reset'; dataBuffer = ''; } else if (step === 'reset' && dataBuffer.includes('250')) { step = 'mail2'; socket.write('MAIL FROM:\r\n'); dataBuffer = ''; } else if (step === 'mail2' && dataBuffer.includes('250')) { step = 'rcpt_invalid'; // Test with invalid NOTIFY value socket.write('RCPT TO: NOTIFY=INVALID\r\n'); dataBuffer = ''; } else if (step === 'rcpt_invalid') { const properlyRejected = dataBuffer.includes('501') || dataBuffer.includes('555') || dataBuffer.includes('500'); if (properlyRejected) { console.log('Invalid NOTIFY parameter properly rejected'); } else if (dataBuffer.includes('250')) { console.log('Server ignores invalid NOTIFY parameter'); } socket.write('QUIT\r\n'); socket.end(); done.resolve(); } }); socket.on('error', (err) => { console.error('Socket error:', err); done.reject(err); }); await done.promise; }); tap.test('cleanup - stop test server', async () => { await stopTestServer(testServer); }); tap.start();