submission-tls-sniff.test.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import assert from 'node:assert/strict';
  2. import { mkdtempSync, writeFileSync } from 'node:fs';
  3. import net from 'node:net';
  4. import { tmpdir } from 'node:os';
  5. import path from 'node:path';
  6. import tls from 'node:tls';
  7. import { test } from 'node:test';
  8. import { startSubmissionServer } from '../src/submission.js';
  9. const testCert = `-----BEGIN CERTIFICATE-----
  10. MIIDCTCCAfGgAwIBAgIUKFK3kjPk5eJZ2nsmLbEjfQZp5lQwDQYJKoZIhvcNAQEL
  11. BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDcwODA2MjYzMloXDTI2MDgw
  12. NzA2MjYzMlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
  13. AAOCAQ8AMIIBCgKCAQEAuUOQ0ewpBEjMNEBcyN79o8ujqetpWh7wbLcc5+aiNUaa
  14. OhmGitZaHuNzLBG/vhd44QJjBmdRV4lG8/t8NCFMczpHzmW5f8ovY+5MqeOMDXDX
  15. zeX+LjyraYTM/NxuZirUOQNjE5oq5T8TirTpp1ZycF/aYz9kyq/BlIR3bMySHV+M
  16. 39o6U1aS71tpP4NthnwOtxWs9Md5axfw5d4PcNEbEx42XfcQxnaZkmIm3O0uQ9z/
  17. E5K9TwMPsIAYVWKUff57thwB27cj/SBEJoPCAcWvIenbPddLGuIm+T9LHoGiMmfd
  18. OSW9Hx+1fKCAsIA9JuUkMCCRWAnI6aft+gI+GByFVQIDAQABo1MwUTAdBgNVHQ4E
  19. FgQUEBUJl3WRWbTyqk1gF9T0tpPQGx8wHwYDVR0jBBgwFoAUEBUJl3WRWbTyqk1g
  20. F9T0tpPQGx8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAOsr+
  21. ZBjxA+3ynvE0Ay0UHVNKWmkkxVwX9uiYdfP/KZSD8HGuEZ22c9Z4EmfLg4XxXnaK
  22. Up2A3vg/r1TA4LcHSCzCpYgUsT1mNs84jFlkg7qcSr/To3AodQIbU6290nVIHCoA
  23. LLLpDLOMm7HkrbqRRQnLJBz296PZgHZDZ5W680C+QEZhw3BlY6hixdvHikd9xPFv
  24. jBVQrMyBu0kqlhGY3ZsqhMfapn1x/pmCl+kd6KXj5jOn1gSqig4hOh8mGa7xq7D8
  25. /okZ4boyv18n95yjo2YkhpuTidocBDsSoK9U4UQrVl4ID+zydh2vVpvYbCemmBc9
  26. EZVyHdaIa3874ry3Kw==
  27. -----END CERTIFICATE-----`;
  28. const testKey = `-----BEGIN PRIVATE KEY-----
  29. MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5Q5DR7CkESMw0
  30. QFzI3v2jy6Op62laHvBstxzn5qI1Rpo6GYaK1loe43MsEb++F3jhAmMGZ1FXiUbz
  31. +3w0IUxzOkfOZbl/yi9j7kyp44wNcNfN5f4uPKtphMz83G5mKtQ5A2MTmirlPxOK
  32. tOmnVnJwX9pjP2TKr8GUhHdszJIdX4zf2jpTVpLvW2k/g22GfA63Faz0x3lrF/Dl
  33. 3g9w0RsTHjZd9xDGdpmSYibc7S5D3P8Tkr1PAw+wgBhVYpR9/nu2HAHbtyP9IEQm
  34. g8IBxa8h6ds910sa4ib5P0segaIyZ905Jb0fH7V8oICwgD0m5SQwIJFYCcjpp+36
  35. Aj4YHIVVAgMBAAECggEAEPWDqgfHiG4ejVDv/W5WQxp22KzlV7vZz+XiRkM5SY21
  36. PAjOpWJyXP3+ssPMLNtKtDRK+tDV5CC7p0V37PC4ZhrpbFYpCS7psCjmCS48iZVx
  37. F9bMMhDZMx9WQXZvP7h/dVmfRfHYp+QihpHBitExyCIqJGgu8pPssUsAAKxoWrpW
  38. z2VvWXBvIBtvWTJNjBduKgoEAWpqdJU/YlXyDyHKOtGpQEksR4A0CWeMEa4BYgog
  39. MYou8TSiKuIb4M5BKdiHBSO7RiTn6YJ3o7BoGnMVztOPQS6HjMZbAc9oevM2CXQI
  40. 4gy/BgqSRz4FFbGDp5ZscRdEgf4sxvmXiIzWQ0jqSQKBgQDg35FCOmMTIBujh0s1
  41. E0D8wmX/Gt6D63Tn1ORFn0OrOSV3qywb9xuQ3ZpSkQwX5+AwjRIcvB1HUTml5xJb
  42. cIxKom/DmCzsHl3Pi16F5U7kRLV0h++Wi4z5Inqdf1jchcPiVwPMZFd0sRwechdr
  43. 47PUJokwDb4YQuKGzMJDB/4uWQKBgQDS6G59Gc2dS04BXpSjPcY75JzQ8PIQdOgn
  44. oGdSOhr26hMHIuquHcxTmgLQVDJ1zdNMW6o+btYmGbq3kC25T0JtFn/xVN4kGGSt
  45. askSeC3wuoScRmQbC4ibriXtDI0rVVoW+q9PPxqjnCYumgAru8JPk//sF1OrOcgf
  46. fg4NQeNHXQKBgBReK5oED9U7o0U8i+NSyyAZu4NPu7fXK8+TyNlFg6uOkYY1Stl9
  47. mFGWwNOZr666oePD41AW+c/r23zbYR5HI23fKKBeuDLqzTRvOzCFhI/IaWcUqO7J
  48. 1Qr7xH9feXz/4K4vk3h/3iwDnrhjPRvYlAEzPFnN5NnttPbgtPe9UZiBAoGADHoN
  49. lz8Ah+6dhb03o9SStpZWpJGscEbKORXnJtkjITpFt+Vb5sMChGuXAQYKGif7+Qdv
  50. MdRSvNxGzHcuDUlgD54GIZu4rH/47L1leb88UIJoN+p+H492HGeX/McCTu70rmlU
  51. F6cPB2DEbXtUyUGJRDXoLOpQW8/GQ/6sDCK6tE0CgYEAqYZuCqT+mcuZLwwUzQD+
  52. 6Yucas5RNEXuBP+2/VjKLjUoNRmI1G3EvLVXtlhw5PFxXqUnNFoaQZ0LEqeNfhrh
  53. pBFgyA7DS+r2vSQNq52yjyyI6Jhe7dIc6I79e3uqSYQ0ODWm5z8IYzmQ64R/yeqb
  54. 2trVak9Z2+qu6bsdcDk6TEE=
  55. -----END PRIVATE KEY-----`;
  56. test('smtp listeners accept both plain SMTP and implicit TLS clients', async () => {
  57. const dataDir = mkdtempSync(path.join(tmpdir(), 'mailhub-submission-test-'));
  58. const certPath = path.join(dataDir, 'cert.pem');
  59. const keyPath = path.join(dataDir, 'key.pem');
  60. writeFileSync(certPath, testCert);
  61. writeFileSync(keyPath, testKey);
  62. const [server] = startSubmissionServer({
  63. enabled: true,
  64. listeners: [{ port: 0, protocol: 'smtp' }],
  65. hostname: 'localhost',
  66. allowInsecureAuth: true,
  67. tlsCertPath: certPath,
  68. tlsKeyPath: keyPath
  69. });
  70. await waitForListening(server);
  71. const port = server.address().port;
  72. try {
  73. const plainBanner = await readPlainBanner(port);
  74. assert.match(plainBanner, /^220 localhost MailHub SMTP ready/);
  75. const tlsBanner = await readImplicitTlsBanner(port);
  76. assert.match(tlsBanner, /^220 localhost MailHub SMTP ready/);
  77. } finally {
  78. await closeServer(server);
  79. }
  80. });
  81. function waitForListening(server) {
  82. if (server.listening) return Promise.resolve();
  83. return new Promise((resolve) => server.once('listening', resolve));
  84. }
  85. function closeServer(server) {
  86. return new Promise((resolve, reject) => {
  87. server.close((error) => error ? reject(error) : resolve());
  88. });
  89. }
  90. function readPlainBanner(port) {
  91. return new Promise((resolve, reject) => {
  92. const socket = net.createConnection({ host: '127.0.0.1', port });
  93. socket.setTimeout(3000);
  94. socket.once('data', (chunk) => {
  95. socket.end();
  96. resolve(chunk.toString('utf8').trim());
  97. });
  98. socket.once('error', reject);
  99. socket.once('timeout', () => reject(new Error('plain SMTP banner timed out')));
  100. });
  101. }
  102. function readImplicitTlsBanner(port) {
  103. return new Promise((resolve, reject) => {
  104. const socket = tls.connect({
  105. host: '127.0.0.1',
  106. port,
  107. rejectUnauthorized: false,
  108. servername: 'localhost'
  109. });
  110. socket.setTimeout(3000);
  111. socket.once('secureConnect', () => {
  112. socket.once('data', (chunk) => {
  113. socket.end();
  114. resolve(chunk.toString('utf8').trim());
  115. });
  116. });
  117. socket.once('error', reject);
  118. socket.once('timeout', () => reject(new Error('implicit TLS SMTP banner timed out')));
  119. });
  120. }