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'; const TEST_PORT = 2525; import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: SmtpServer; tap.test('setup - start test server', async () => { testServer = await startTestServer({ port: TEST_PORT }); await plugins.smartdelay.delayFor(1000); }); tap.test('Authorization - Valid sender domain', 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 localhost\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail'; // Use valid sender domain (localhost) socket.write('MAIL FROM:\r\n'); dataBuffer = ''; } else if (step === 'mail' && dataBuffer.includes('250')) { step = 'rcpt'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt') { // Valid sender should be accepted const accepted = dataBuffer.includes('250'); console.log(`Valid sender domain ${accepted ? 'accepted' : 'rejected'}`); expect(accepted).toEqual(true); 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('Authorization - External sender domain', 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 external.com\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail'; // Use external sender domain socket.write('MAIL FROM:\r\n'); dataBuffer = ''; } else if (step === 'mail') { if (dataBuffer.includes('250')) { step = 'rcpt'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (dataBuffer.includes('530')) { // Authentication required console.log('External sender requires authentication'); expect(true).toEqual(true); socket.write('QUIT\r\n'); socket.end(); done.resolve(); } else if (dataBuffer.includes('550') || dataBuffer.includes('553')) { // Rejected for policy reasons console.log('External sender rejected by policy'); expect(true).toEqual(true); socket.write('QUIT\r\n'); socket.end(); done.resolve(); } } else if (step === 'rcpt') { // Check response const accepted = dataBuffer.includes('250'); const authRequired = dataBuffer.includes('530'); const rejected = dataBuffer.includes('550') || dataBuffer.includes('553'); console.log(`External sender: accepted=${accepted}, authRequired=${authRequired}, rejected=${rejected}`); expect(accepted || authRequired || rejected).toEqual(true); 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('Authorization - Relay attempt rejection', 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 external.com\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail'; // External sender socket.write('MAIL FROM:\r\n'); dataBuffer = ''; } else if (step === 'mail') { if (dataBuffer.includes('250')) { step = 'rcpt'; // Try to relay to another external domain (should be rejected) socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else { // MAIL FROM already rejected console.log('External sender rejected at MAIL FROM'); expect(true).toEqual(true); socket.write('QUIT\r\n'); socket.end(); done.resolve(); } } else if (step === 'rcpt') { // Relay attempt should be rejected const rejected = dataBuffer.includes('550') || dataBuffer.includes('553') || dataBuffer.includes('530') || dataBuffer.includes('554'); console.log(`Relay attempt ${rejected ? 'properly rejected' : 'unexpectedly accepted'}`); expect(rejected).toEqual(true); 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('Authorization - IP-based restrictions', 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'; // Use IP address in EHLO socket.write('EHLO [127.0.0.1]\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'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt') { // Localhost IP should typically be accepted const accepted = dataBuffer.includes('250'); const rejected = dataBuffer.includes('550') || dataBuffer.includes('553'); console.log(`IP-based authorization: ${accepted ? 'accepted' : 'rejected'}`); expect(accepted || rejected).toEqual(true); // Either is valid based on server config 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('Authorization - Case sensitivity in addresses', 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 localhost\r\n'); dataBuffer = ''; } else if (step === 'ehlo' && dataBuffer.includes('250')) { step = 'mail'; // Use mixed case in email address socket.write('MAIL FROM:\r\n'); dataBuffer = ''; } else if (step === 'mail' && dataBuffer.includes('250')) { step = 'rcpt'; // Mixed case recipient socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt') { // Email addresses should be case-insensitive const accepted = dataBuffer.includes('250'); console.log(`Mixed case addresses ${accepted ? 'accepted' : 'rejected'}`); expect(accepted).toEqual(true); 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();