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 6376 DKIM - Server accepts email with DKIM signature', 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'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt' && dataBuffer.includes('250')) { step = 'data'; socket.write('DATA\r\n'); dataBuffer = ''; } else if (step === 'data' && dataBuffer.includes('354')) { // Create email with DKIM signature const dkimSignature = [ 'DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;', ' d=example.com; s=default;', ' h=from:to:subject:date:message-id;', ' bh=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=;', ' b=Kt1zLCYmUVYJKEOVL9nGF2JVPJ5/k5l6yOkNBJGCrZn4E5z9Qn7TlYrG8QfBgJ4', ' CzYVLjKm5xOhUoEaDzTJ1E6C9A4hL8sKfBxQjN8oWv4kP3GdE6mFqS0wKcRjT+', ' NxOz2VcJP4LmKjFsG8XqBhYoEfCvSr3UwNmEkP6RjT9WlQzA4kJe2VoMsJ=' ].join('\r\n'); const email = [ `From: sender@example.com`, `To: recipient@example.com`, `Subject: DKIM RFC 6376 Compliance Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, dkimSignature, '', 'This email tests RFC 6376 DKIM compliance.', 'The server should properly handle DKIM signatures.', '.', '' ].join('\r\n'); socket.write(email); dataBuffer = ''; } else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted')) { console.log('Email with DKIM signature accepted'); expect(true).toBeTrue(); // Server accepts DKIM headers 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 6376 DKIM - Multiple DKIM signatures', 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'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt' && dataBuffer.includes('250')) { step = 'data'; socket.write('DATA\r\n'); dataBuffer = ''; } else if (step === 'data' && dataBuffer.includes('354')) { // Email with multiple DKIM signatures (common in forwarding scenarios) const email = [ `From: sender@example.com`, `To: recipient@example.com`, `Subject: Multiple DKIM Signatures Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, 'DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;', ' d=example.com; s=selector1;', ' h=from:to:subject:date;', ' bh=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN/XKdLCPjaYaY=;', ' b=signature1data', 'DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple;', ' d=forwarder.com; s=selector2;', ' h=from:to:subject:date:message-id;', ' bh=differentbodyhash=;', ' b=signature2data', '', 'Email with multiple DKIM signatures.', '.', '' ].join('\r\n'); socket.write(email); dataBuffer = ''; } else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted')) { console.log('Email with multiple DKIM signatures 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 6376 DKIM - Various canonicalization methods', 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'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt' && dataBuffer.includes('250')) { step = 'data'; socket.write('DATA\r\n'); dataBuffer = ''; } else if (step === 'data' && dataBuffer.includes('354')) { // Test different canonicalization methods const email = [ `From: sender@example.com`, `To: recipient@example.com`, `Subject: DKIM Canonicalization Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, 'DKIM-Signature: v=1; a=rsa-sha256; c=simple/relaxed;', ' d=example.com; s=default;', ' h=from:to:subject;', ' bh=bodyhash=;', ' b=signature', '', 'Testing different canonicalization methods.', 'Simple header canonicalization preserves whitespace.', 'Relaxed body canonicalization normalizes whitespace.', '.', '' ].join('\r\n'); socket.write(email); dataBuffer = ''; } else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted')) { console.log('Email with different canonicalization 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 6376 DKIM - Long header fields and folding', 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'; socket.write('RCPT TO:\r\n'); dataBuffer = ''; } else if (step === 'rcpt' && dataBuffer.includes('250')) { step = 'data'; socket.write('DATA\r\n'); dataBuffer = ''; } else if (step === 'data' && dataBuffer.includes('354')) { // DKIM signature with long fields that require folding const longSignature = 'b=' + 'A'.repeat(200); const email = [ `From: sender@example.com`, `To: recipient@example.com`, `Subject: DKIM Long Fields Test`, `Date: ${new Date().toUTCString()}`, `Message-ID: `, 'DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;', ' d=example.com; s=default; t=' + Math.floor(Date.now() / 1000) + ';', ' h=from:to:subject:date:message-id:content-type:mime-version;', ' bh=verylongbodyhashvalueherethatexceedsnormallength1234567890=;', ' ' + longSignature.substring(0, 70), ' ' + longSignature.substring(70, 140), ' ' + longSignature.substring(140), '', 'Testing DKIM with long header fields.', '.', '' ].join('\r\n'); socket.write(email); dataBuffer = ''; } else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted')) { console.log('Email with long DKIM fields 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 6376 DKIM - Authentication-Results header', 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')) { // Check if server advertises DKIM support const advertisesDkim = dataBuffer.toLowerCase().includes('dkim'); console.log('Server advertises DKIM:', advertisesDkim); 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' && dataBuffer.includes('250')) { step = 'data'; socket.write('DATA\r\n'); dataBuffer = ''; } else if (step === 'data' && dataBuffer.includes('354')) { // Email to test if server adds Authentication-Results header const email = [ `From: sender@example.com`, `To: recipient@example.com`, `Subject: Authentication-Results 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;', ' bh=simplehash=;', ' b=simplesignature', '', 'Testing if server adds Authentication-Results header.', '.', '' ].join('\r\n'); socket.write(email); dataBuffer = ''; } else if (dataBuffer.includes('250 ') && dataBuffer.includes('Message accepted')) { console.log('Email accepted - server should process DKIM and potentially add Authentication-Results'); 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();