haraka 0.0.32 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +28 -0
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +6 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1872 -62
- package/CLAUDE.md +40 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/GEMINI.md +38 -0
- package/LICENSE +2 -1
- package/Plugins.md +227 -0
- package/README.md +100 -115
- package/SECURITY.md +178 -0
- package/TODO +22 -0
- package/address.js +53 -0
- package/bin/haraka +593 -0
- package/bin/haraka_grep +32 -0
- package/config/aliases +2 -0
- package/config/auth_flat_file.ini +7 -0
- package/config/auth_vpopmaild.ini +9 -0
- package/config/connection.ini +79 -0
- package/config/delay_deny.ini +7 -0
- package/config/dhparams.pem +8 -0
- package/config/host_list +3 -0
- package/config/host_list_regex +6 -0
- package/config/http.ini +11 -0
- package/config/lmtp.ini +7 -0
- package/config/log.ini +11 -0
- package/config/me +1 -0
- package/config/outbound.bounce_message +18 -0
- package/config/outbound.bounce_message_html +36 -0
- package/config/outbound.bounce_message_image +106 -0
- package/config/outbound.ini +24 -0
- package/config/plugins +67 -0
- package/config/smtp.ini +37 -0
- package/config/smtp_bridge.ini +4 -0
- package/config/smtp_forward.ini +31 -0
- package/config/smtp_proxy.ini +27 -0
- package/config/tarpit.timeout +1 -0
- package/config/tls.ini +83 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/config/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1863 -0
- package/contrib/Haraka.cf +6 -0
- package/contrib/Haraka.pm +35 -0
- package/contrib/bad_smtp_server.pl +25 -0
- package/contrib/bsd-rc.d/haraka +61 -0
- package/contrib/debian-init.d/haraka +87 -0
- package/contrib/haraka.init +96 -0
- package/contrib/haraka.service +23 -0
- package/contrib/plugin2npm.sh +81 -0
- package/contrib/ubuntu-upstart/haraka.conf +27 -0
- package/coverage/coverage-final.json +2 -0
- package/coverage/coverage-summary.json +33 -0
- package/coverage/tmp/coverage-79131-1779241025146-0.json +1 -0
- package/coverage/tmp/coverage-79132-1779240999690-0.json +1 -0
- package/coverage/tmp/coverage-79172-1779241000095-0.json +1 -0
- package/coverage/tmp/coverage-79210-1779241000156-0.json +1 -0
- package/coverage/tmp/coverage-79211-1779241000209-0.json +1 -0
- package/coverage/tmp/coverage-79212-1779241000266-0.json +1 -0
- package/coverage/tmp/coverage-79213-1779241000441-0.json +1 -0
- package/coverage/tmp/coverage-79214-1779241000626-0.json +1 -0
- package/coverage/tmp/coverage-79215-1779241000795-0.json +1 -0
- package/coverage/tmp/coverage-79216-1779241000965-0.json +1 -0
- package/coverage/tmp/coverage-79218-1779241001013-0.json +1 -0
- package/coverage/tmp/coverage-79219-1779241001179-0.json +1 -0
- package/coverage/tmp/coverage-79220-1779241006249-0.json +1 -0
- package/coverage/tmp/coverage-79227-1779241011453-0.json +1 -0
- package/coverage/tmp/coverage-79229-1779241011537-0.json +1 -0
- package/coverage/tmp/coverage-79230-1779241011647-0.json +1 -0
- package/coverage/tmp/coverage-79231-1779241011765-0.json +1 -0
- package/coverage/tmp/coverage-79232-1779241011841-0.json +1 -0
- package/coverage/tmp/coverage-79233-1779241011909-0.json +1 -0
- package/coverage/tmp/coverage-79234-1779241011984-0.json +1 -0
- package/coverage/tmp/coverage-79235-1779241012055-0.json +1 -0
- package/coverage/tmp/coverage-79236-1779241012230-0.json +1 -0
- package/coverage/tmp/coverage-79237-1779241012300-0.json +1 -0
- package/coverage/tmp/coverage-79238-1779241012368-0.json +1 -0
- package/coverage/tmp/coverage-79239-1779241012438-0.json +1 -0
- package/coverage/tmp/coverage-79240-1779241012511-0.json +1 -0
- package/coverage/tmp/coverage-79241-1779241012582-0.json +1 -0
- package/coverage/tmp/coverage-79242-1779241012652-0.json +1 -0
- package/coverage/tmp/coverage-79243-1779241012814-0.json +1 -0
- package/coverage/tmp/coverage-79244-1779241012931-0.json +1 -0
- package/coverage/tmp/coverage-79245-1779241013007-0.json +1 -0
- package/coverage/tmp/coverage-79246-1779241013106-0.json +1 -0
- package/coverage/tmp/coverage-79247-1779241013178-0.json +1 -0
- package/coverage/tmp/coverage-79248-1779241013244-0.json +1 -0
- package/coverage/tmp/coverage-79249-1779241013409-0.json +1 -0
- package/coverage/tmp/coverage-79250-1779241013697-0.json +1 -0
- package/coverage/tmp/coverage-79251-1779241013847-0.json +1 -0
- package/coverage/tmp/coverage-79252-1779241014288-0.json +1 -0
- package/coverage/tmp/coverage-79253-1779241014378-0.json +1 -0
- package/coverage/tmp/coverage-79254-1779241014428-0.json +1 -0
- package/coverage/tmp/coverage-79255-1779241021774-0.json +1 -0
- package/coverage/tmp/coverage-80382-1779241021949-0.json +1 -0
- package/coverage/tmp/coverage-80383-1779241025019-0.json +1 -0
- package/coverage/tmp/coverage-80384-1779241025133-0.json +1 -0
- package/docs/Body.md +1 -0
- package/docs/Config.md +1 -0
- package/docs/Connection.md +153 -0
- package/docs/CoreConfig.md +96 -0
- package/docs/CustomReturnCodes.md +3 -0
- package/docs/HAProxy.md +62 -0
- package/docs/Header.md +1 -0
- package/docs/Logging.md +129 -0
- package/docs/Outbound.md +210 -0
- package/docs/Plugins.md +372 -0
- package/docs/Results.md +7 -0
- package/docs/Transaction.md +135 -0
- package/docs/Tutorial.md +183 -0
- package/docs/deprecated/access.md +3 -0
- package/docs/deprecated/backscatterer.md +9 -0
- package/docs/deprecated/connect.rdns_access.md +53 -0
- package/docs/deprecated/data.headers.md +3 -0
- package/docs/deprecated/data.nomsgid.md +7 -0
- package/docs/deprecated/data.noreceived.md +11 -0
- package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
- package/docs/deprecated/dkim_sign.md +97 -0
- package/docs/deprecated/dkim_verify.md +28 -0
- package/docs/deprecated/dnsbl.md +80 -0
- package/docs/deprecated/dnswl.md +73 -0
- package/docs/deprecated/lookup_rdns.strict.md +67 -0
- package/docs/deprecated/mail_from.access.md +52 -0
- package/docs/deprecated/mail_from.blocklist.md +18 -0
- package/docs/deprecated/mail_from.nobounces.md +8 -0
- package/docs/deprecated/rcpt_to.access.md +53 -0
- package/docs/deprecated/rcpt_to.blocklist.md +18 -0
- package/docs/deprecated/rcpt_to.routes.md +3 -0
- package/docs/deprecated/rdns.regexp.md +30 -0
- package/docs/plugins/aliases.md +3 -0
- package/docs/plugins/auth/auth_bridge.md +34 -0
- package/docs/plugins/auth/auth_ldap.md +4 -0
- package/docs/plugins/auth/auth_proxy.md +36 -0
- package/docs/plugins/auth/auth_vpopmaild.md +33 -0
- package/docs/plugins/auth/flat_file.md +40 -0
- package/docs/plugins/block_me.md +18 -0
- package/docs/plugins/data.signatures.md +11 -0
- package/docs/plugins/delay_deny.md +23 -0
- package/docs/plugins/max_unrecognized_commands.md +6 -0
- package/docs/plugins/prevent_credential_leaks.md +22 -0
- package/docs/plugins/process_title.md +42 -0
- package/docs/plugins/queue/deliver.md +3 -0
- package/docs/plugins/queue/discard.md +32 -0
- package/docs/plugins/queue/lmtp.md +24 -0
- package/docs/plugins/queue/qmail-queue.md +16 -0
- package/docs/plugins/queue/quarantine.md +87 -0
- package/docs/plugins/queue/smtp_bridge.md +32 -0
- package/docs/plugins/queue/smtp_forward.md +127 -0
- package/docs/plugins/queue/smtp_proxy.md +68 -0
- package/docs/plugins/queue/test.md +7 -0
- package/docs/plugins/rcpt_to.in_host_list.md +34 -0
- package/docs/plugins/rcpt_to.max_count.md +3 -0
- package/docs/plugins/record_envelope_addresses.md +20 -0
- package/docs/plugins/relay.md +3 -0
- package/docs/plugins/reseed_rng.md +16 -0
- package/docs/plugins/status.md +41 -0
- package/docs/plugins/tarpit.md +50 -0
- package/docs/plugins/tls.md +235 -0
- package/docs/plugins/toobusy.md +27 -0
- package/docs/plugins/xclient.md +10 -0
- package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
- package/docs/tutorials/SettingUpOutbound.md +62 -0
- package/eslint.config.mjs +2 -0
- package/haraka.js +74 -0
- package/haraka.sh +2 -0
- package/http/html/404.html +58 -0
- package/http/html/index.html +47 -0
- package/http/package.json +21 -0
- package/line_socket.js +24 -0
- package/logger.js +322 -0
- package/outbound/client_pool.js +59 -0
- package/outbound/config.js +134 -0
- package/outbound/hmail.js +1504 -0
- package/outbound/index.js +349 -0
- package/outbound/qfile.js +93 -0
- package/outbound/queue.js +399 -0
- package/outbound/tls.js +85 -0
- package/outbound/todo.js +17 -0
- package/package.json +91 -29
- package/plugins/.eslintrc.yaml +3 -0
- package/plugins/auth/auth_base.js +261 -0
- package/plugins/auth/auth_bridge.js +20 -0
- package/plugins/auth/auth_proxy.js +227 -0
- package/plugins/auth/auth_vpopmaild.js +162 -0
- package/plugins/auth/flat_file.js +44 -0
- package/plugins/block_me.js +88 -0
- package/plugins/data.signatures.js +30 -0
- package/plugins/delay_deny.js +153 -0
- package/plugins/prevent_credential_leaks.js +61 -0
- package/plugins/process_title.js +197 -0
- package/plugins/profile.js +11 -0
- package/plugins/queue/deliver.js +12 -0
- package/plugins/queue/discard.js +27 -0
- package/plugins/queue/lmtp.js +45 -0
- package/plugins/queue/qmail-queue.js +93 -0
- package/plugins/queue/quarantine.js +133 -0
- package/plugins/queue/smtp_bridge.js +45 -0
- package/plugins/queue/smtp_forward.js +371 -0
- package/plugins/queue/smtp_proxy.js +142 -0
- package/plugins/queue/test.js +15 -0
- package/plugins/rcpt_to.host_list_base.js +65 -0
- package/plugins/rcpt_to.in_host_list.js +56 -0
- package/plugins/record_envelope_addresses.js +17 -0
- package/plugins/reseed_rng.js +7 -0
- package/plugins/status.js +274 -0
- package/plugins/tarpit.js +45 -0
- package/plugins/tls.js +164 -0
- package/plugins/toobusy.js +47 -0
- package/plugins/xclient.js +124 -0
- package/plugins.js +604 -0
- package/queue/1772642154987_1775581346001_4_82235_TGwgfd_2_mattbook-m3.home.simerson.net +0 -0
- package/run_tests +11 -0
- package/server.js +827 -0
- package/smtp_client.js +504 -0
- package/test/.eslintrc.yaml +11 -0
- package/test/config/auth_flat_file.ini +5 -0
- package/test/config/block_me.recipient +1 -0
- package/test/config/block_me.senders +1 -0
- package/test/config/dhparams.pem +8 -0
- package/test/config/host_list +2 -0
- package/test/config/outbound_tls_cert.pem +1 -0
- package/test/config/outbound_tls_key.pem +1 -0
- package/test/config/plugins +7 -0
- package/test/config/smtp.ini +11 -0
- package/test/config/smtp_forward.ini +30 -0
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/config/tls/haraka.local.pem +51 -0
- package/test/config/tls.ini +45 -0
- package/test/config/tls_cert.pem +21 -0
- package/test/config/tls_key.pem +28 -0
- package/test/connection.js +817 -0
- package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
- package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
- package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
- package/test/fixtures/line_socket.js +21 -0
- package/test/fixtures/todo_qfile.txt +0 -0
- package/test/fixtures/util_hmailitem.js +156 -0
- package/test/installation/config/test-plugin-flat +1 -0
- package/test/installation/config/test-plugin.ini +10 -0
- package/test/installation/config/tls.ini +1 -0
- package/test/installation/node_modules/load_first/index.js +5 -0
- package/test/installation/node_modules/load_first/package.json +11 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
- package/test/installation/node_modules/test-plugin/package.json +5 -0
- package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
- package/test/installation/plugins/base_plugin.js +3 -0
- package/test/installation/plugins/folder_plugin/index.js +3 -0
- package/test/installation/plugins/folder_plugin/package.json +11 -0
- package/test/installation/plugins/inherits.js +7 -0
- package/test/installation/plugins/load_first.js +3 -0
- package/test/installation/plugins/plugin.js +1 -0
- package/test/installation/plugins/tls.js +3 -0
- package/test/logger.js +217 -0
- package/test/loud/config/dhparams.pem +0 -0
- package/test/loud/config/tls/goobered.pem +45 -0
- package/test/loud/config/tls.ini +43 -0
- package/test/mail_specimen/base64-root-part.txt +23 -0
- package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
- package/test/outbound/bounce_net_errors.js +133 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +210 -0
- package/test/outbound/index.js +385 -0
- package/test/outbound/qfile.js +124 -0
- package/test/outbound/queue.js +325 -0
- package/test/plugins/auth/auth_base.js +620 -0
- package/test/plugins/auth/auth_bridge.js +80 -0
- package/test/plugins/auth/auth_vpopmaild.js +81 -0
- package/test/plugins/auth/flat_file.js +123 -0
- package/test/plugins/block_me.js +141 -0
- package/test/plugins/data.signatures.js +111 -0
- package/test/plugins/delay_deny.js +262 -0
- package/test/plugins/prevent_credential_leaks.js +174 -0
- package/test/plugins/process_title.js +141 -0
- package/test/plugins/queue/deliver.js +98 -0
- package/test/plugins/queue/discard.js +78 -0
- package/test/plugins/queue/lmtp.js +137 -0
- package/test/plugins/queue/qmail-queue.js +98 -0
- package/test/plugins/queue/quarantine.js +80 -0
- package/test/plugins/queue/smtp_bridge.js +152 -0
- package/test/plugins/queue/smtp_forward.js +1023 -0
- package/test/plugins/queue/smtp_proxy.js +138 -0
- package/test/plugins/rcpt_to.host_list_base.js +102 -0
- package/test/plugins/rcpt_to.in_host_list.js +186 -0
- package/test/plugins/record_envelope_addresses.js +66 -0
- package/test/plugins/reseed_rng.js +34 -0
- package/test/plugins/status.js +207 -0
- package/test/plugins/tarpit.js +90 -0
- package/test/plugins/tls.js +86 -0
- package/test/plugins/toobusy.js +21 -0
- package/test/plugins/xclient.js +119 -0
- package/test/plugins.js +230 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- package/test/queue/zero-length +0 -0
- package/test/server.js +1012 -0
- package/test/smtp_client.js +1303 -0
- package/test/tls_socket.js +321 -0
- package/test/transaction.js +554 -0
- package/tls_socket.js +771 -0
- package/transaction.js +267 -0
- package/lib/index.js +0 -371
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const test = require('node:test')
|
|
4
|
+
const assert = require('node:assert/strict')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const net = require('node:net')
|
|
7
|
+
const tls = require('node:tls')
|
|
8
|
+
const fs = require('node:fs')
|
|
9
|
+
const { EventEmitter } = require('node:events')
|
|
10
|
+
|
|
11
|
+
const tls_socket = require('../tls_socket')
|
|
12
|
+
|
|
13
|
+
const TEST_CERT = fs.readFileSync(path.join(__dirname, 'config/tls_cert.pem'))
|
|
14
|
+
const TEST_KEY = fs.readFileSync(path.join(__dirname, 'config/tls_key.pem'))
|
|
15
|
+
|
|
16
|
+
test('tls_socket', async (t) => {
|
|
17
|
+
await t.test('parse_x509', async (t) => {
|
|
18
|
+
await t.test('handles empty string', async () => {
|
|
19
|
+
const res = await tls_socket.parse_x509('')
|
|
20
|
+
assert.deepEqual(res, {})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
await t.test('handles null/undefined', async () => {
|
|
24
|
+
const res = await tls_socket.parse_x509(null)
|
|
25
|
+
assert.deepEqual(res, {})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// This would exercise the uninitialized res.names bug if we had a cert string
|
|
29
|
+
// but since it spawns openssl, we'd need to mock spawn or provide a real cert.
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
await t.test('get_rejectUnauthorized', async (t) => {
|
|
33
|
+
await t.test('returns true if rejectUnauthorized is true', () => {
|
|
34
|
+
assert.strictEqual(tls_socket.get_rejectUnauthorized(true, 25, [25]), true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
await t.test('returns true if port is in port_list', () => {
|
|
38
|
+
assert.strictEqual(tls_socket.get_rejectUnauthorized(false, 465, [465]), true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await t.test('returns false if port is not in port_list', () => {
|
|
42
|
+
assert.strictEqual(tls_socket.get_rejectUnauthorized(false, 25, [465]), false)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
await t.test('SNICallback', async (t) => {
|
|
47
|
+
await t.test('calls sniDone with default context if servername unknown', (t, done) => {
|
|
48
|
+
// This test requires some setup of ctxByHost which is private to the module
|
|
49
|
+
// but we can test if it's a function
|
|
50
|
+
assert.strictEqual(typeof tls_socket.SNICallback, 'function')
|
|
51
|
+
done()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
await t.test('pluggableStream', async () => {
|
|
56
|
+
// This is a class inside the file, but not exported.
|
|
57
|
+
// We can test it via createServer or connect if we mock net.
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await t.test('connect', async () => {
|
|
61
|
+
// Exercise the `new tls.connect` bug
|
|
62
|
+
// We can't easily catch the 'new' keyword usage without proxying tls.connect
|
|
63
|
+
assert.strictEqual(typeof tls_socket.connect, 'function')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
await t.test('connect upgrade error propagation', async (t) => {
|
|
67
|
+
// Verify that TLS errors during socket.upgrade() are propagated to the outer
|
|
68
|
+
// pluggableStream socket, not silently swallowed.
|
|
69
|
+
// A TLS server that requires a client cert; connecting without one triggers
|
|
70
|
+
// a post-handshake "certificate required" alert (TLSv1.3).
|
|
71
|
+
await t.test('emits error on outer socket when client cert is missing', async () => {
|
|
72
|
+
const server = tls.createServer(
|
|
73
|
+
{ cert: TEST_CERT, key: TEST_KEY, requestCert: true, rejectUnauthorized: true },
|
|
74
|
+
() => {},
|
|
75
|
+
)
|
|
76
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
77
|
+
const { port } = server.address()
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const err = await new Promise((resolve, reject) => {
|
|
81
|
+
const socket = tls_socket.connect({ host: '127.0.0.1', port })
|
|
82
|
+
socket.upgrade({ rejectUnauthorized: false }, () => {})
|
|
83
|
+
socket.on('error', resolve)
|
|
84
|
+
socket.on('close', () => reject(new Error('closed without error')))
|
|
85
|
+
setTimeout(() => reject(new Error('timeout')), 3000)
|
|
86
|
+
})
|
|
87
|
+
assert.ok(
|
|
88
|
+
/certificate required|socket hang up|disconnected/.test(err.message),
|
|
89
|
+
`unexpected error: ${err.message}`,
|
|
90
|
+
)
|
|
91
|
+
assert.equal(err.source, 'tls', 'error.source should be "tls"')
|
|
92
|
+
} finally {
|
|
93
|
+
await new Promise((resolve) => server.close(resolve))
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
await t.test('second error handler does not crash when first handler removes all listeners', () => {
|
|
98
|
+
// Regression test for issue #3553
|
|
99
|
+
const originalNetConnect = net.connect
|
|
100
|
+
const originalTlsConnect = tls.connect
|
|
101
|
+
const originalTlsValid = tls_socket.tls_valid
|
|
102
|
+
|
|
103
|
+
const fakeCrypto = new EventEmitter()
|
|
104
|
+
fakeCrypto.writable = true
|
|
105
|
+
fakeCrypto.removeAllListeners = EventEmitter.prototype.removeAllListeners
|
|
106
|
+
fakeCrypto.setTimeout = () => {}
|
|
107
|
+
fakeCrypto.setKeepAlive = () => {}
|
|
108
|
+
|
|
109
|
+
let capturedCleartext
|
|
110
|
+
net.connect = () => fakeCrypto
|
|
111
|
+
tls.connect = () => {
|
|
112
|
+
capturedCleartext = new EventEmitter()
|
|
113
|
+
capturedCleartext.writable = true
|
|
114
|
+
capturedCleartext.setTimeout = () => {}
|
|
115
|
+
capturedCleartext.setKeepAlive = () => {}
|
|
116
|
+
return capturedCleartext
|
|
117
|
+
}
|
|
118
|
+
tls_socket.tls_valid = false
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const socket = tls_socket.connect({ host: 'bad-tls.example.com', port: 25 })
|
|
122
|
+
|
|
123
|
+
// Simulate what release_client does: strip all listeners on first error
|
|
124
|
+
socket.once('error', () => socket.removeAllListeners())
|
|
125
|
+
|
|
126
|
+
socket.upgrade({}, () => {})
|
|
127
|
+
|
|
128
|
+
// capturedCleartext now has two 'error' handlers (on from upgrade, once from attach).
|
|
129
|
+
// Emitting error must NOT throw even though the first handler removes all
|
|
130
|
+
// listeners from the outer socket before the second fires.
|
|
131
|
+
const tlsError = new Error('dh key too small')
|
|
132
|
+
assert.doesNotThrow(() => capturedCleartext.emit('error', tlsError))
|
|
133
|
+
} finally {
|
|
134
|
+
net.connect = originalNetConnect
|
|
135
|
+
tls.connect = originalTlsConnect
|
|
136
|
+
tls_socket.tls_valid = originalTlsValid
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
await t.test('getSocketOpts', async () => {
|
|
142
|
+
// Exercise the typo path (would requires failing config.getDir)
|
|
143
|
+
assert.strictEqual(typeof tls_socket.getSocketOpts, 'function')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
await t.test('getSocketOpts handles missing tls dir', async () => {
|
|
147
|
+
const originalGetCertsDir = tls_socket.get_certs_dir
|
|
148
|
+
tls_socket.get_certs_dir = async () => {
|
|
149
|
+
const err = new Error('missing')
|
|
150
|
+
err.code = 'ENOENT'
|
|
151
|
+
throw err
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const opts = await tls_socket.getSocketOpts('*')
|
|
155
|
+
assert.ok(opts)
|
|
156
|
+
} finally {
|
|
157
|
+
tls_socket.get_certs_dir = originalGetCertsDir
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
await t.test('connect upgrade applies mutual auth cert and timeout/keepalive', async () => {
|
|
162
|
+
const originalNetConnect = net.connect
|
|
163
|
+
const originalTlsConnect = tls.connect
|
|
164
|
+
const originalTlsValid = tls_socket.tls_valid
|
|
165
|
+
const originalCfg = tls_socket.cfg
|
|
166
|
+
const originalCertMap = {
|
|
167
|
+
default: tls_socket.certsByHost['*'],
|
|
168
|
+
host: tls_socket.certsByHost['client-cert.example'],
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const fakeSocket = new EventEmitter()
|
|
172
|
+
fakeSocket.remotePort = 2525
|
|
173
|
+
fakeSocket.remoteAddress = '127.0.0.1'
|
|
174
|
+
fakeSocket.localPort = 25
|
|
175
|
+
fakeSocket.localAddress = '127.0.0.1'
|
|
176
|
+
fakeSocket.writable = true
|
|
177
|
+
fakeSocket.removeAllListeners = EventEmitter.prototype.removeAllListeners
|
|
178
|
+
fakeSocket.setTimeout = () => {}
|
|
179
|
+
fakeSocket.setKeepAlive = () => {}
|
|
180
|
+
|
|
181
|
+
let capturedOptions
|
|
182
|
+
let timeoutSeen = null
|
|
183
|
+
let keepaliveSeen = null
|
|
184
|
+
|
|
185
|
+
net.connect = () => fakeSocket
|
|
186
|
+
tls.connect = (options) => {
|
|
187
|
+
capturedOptions = options
|
|
188
|
+
const clear = new EventEmitter()
|
|
189
|
+
clear.writable = true
|
|
190
|
+
clear.getCipher = () => ({ name: 'TLS_AES_256_GCM_SHA384' })
|
|
191
|
+
clear.getProtocol = () => 'TLSv1.3'
|
|
192
|
+
clear.getPeerCertificate = () => ({})
|
|
193
|
+
clear.setTimeout = (ms) => {
|
|
194
|
+
timeoutSeen = ms
|
|
195
|
+
}
|
|
196
|
+
clear.setKeepAlive = (value) => {
|
|
197
|
+
keepaliveSeen = value
|
|
198
|
+
}
|
|
199
|
+
process.nextTick(() => clear.emit('secureConnect'))
|
|
200
|
+
return clear
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
tls_socket.tls_valid = true
|
|
204
|
+
tls_socket.cfg = {
|
|
205
|
+
mutual_auth_hosts: { 'mx.example.com': 'client-cert.example' },
|
|
206
|
+
mutual_auth_hosts_exclude: {},
|
|
207
|
+
main: { mutual_tls: false },
|
|
208
|
+
}
|
|
209
|
+
tls_socket.certsByHost['*'] = { key: 'default-key', cert: 'default-cert' }
|
|
210
|
+
tls_socket.certsByHost['client-cert.example'] = { key: 'host-key', cert: 'host-cert' }
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const socket = tls_socket.connect({ host: 'mx.example.com', port: 25 })
|
|
214
|
+
socket.setTimeout(3210)
|
|
215
|
+
socket.setKeepAlive(true)
|
|
216
|
+
|
|
217
|
+
await new Promise((resolve) => {
|
|
218
|
+
socket.upgrade({ rejectUnauthorized: false }, () => resolve())
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
assert.equal(capturedOptions.key, 'host-key')
|
|
222
|
+
assert.equal(capturedOptions.cert, 'host-cert')
|
|
223
|
+
assert.equal(capturedOptions.socket, fakeSocket)
|
|
224
|
+
assert.equal(timeoutSeen, 3210)
|
|
225
|
+
assert.equal(keepaliveSeen, true)
|
|
226
|
+
} finally {
|
|
227
|
+
net.connect = originalNetConnect
|
|
228
|
+
tls.connect = originalTlsConnect
|
|
229
|
+
tls_socket.tls_valid = originalTlsValid
|
|
230
|
+
tls_socket.cfg = originalCfg
|
|
231
|
+
tls_socket.certsByHost['*'] = originalCertMap.default
|
|
232
|
+
if (originalCertMap.host === undefined) {
|
|
233
|
+
delete tls_socket.certsByHost['client-cert.example']
|
|
234
|
+
} else {
|
|
235
|
+
tls_socket.certsByHost['client-cert.example'] = originalCertMap.host
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
await t.test('load_plugin_tls_options', async (t) => {
|
|
241
|
+
// Point haraka-config at test/config so tls.ini fixtures load.
|
|
242
|
+
const origConfig = tls_socket.config
|
|
243
|
+
const origCfg = tls_socket.cfg
|
|
244
|
+
const test_config = require('haraka-config').module_config(path.resolve(__dirname))
|
|
245
|
+
|
|
246
|
+
t.beforeEach(() => {
|
|
247
|
+
tls_socket.config = test_config
|
|
248
|
+
tls_socket.cfg = undefined // bust load_tls_ini cache between cases
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
t.after(() => {
|
|
252
|
+
tls_socket.config = origConfig
|
|
253
|
+
tls_socket.cfg = origCfg
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
await t.test('inherits tls.ini [main] when plugin cfg is empty', () => {
|
|
257
|
+
const opts = tls_socket.load_plugin_tls_options({})
|
|
258
|
+
// From test/config/tls.ini [main]
|
|
259
|
+
assert.equal(opts.rejectUnauthorized, false)
|
|
260
|
+
assert.equal(opts.minVersion, 'TLSv1')
|
|
261
|
+
assert.equal(opts.honorCipherOrder, true)
|
|
262
|
+
assert.ok(opts.ciphers && opts.ciphers.length)
|
|
263
|
+
assert.ok(Buffer.isBuffer(opts.key), 'key resolved to Buffer')
|
|
264
|
+
assert.ok(Buffer.isBuffer(opts.cert), 'cert resolved to Buffer')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
await t.test('plugin cfg overrides [main]', () => {
|
|
268
|
+
const opts = tls_socket.load_plugin_tls_options({
|
|
269
|
+
rejectUnauthorized: true,
|
|
270
|
+
minVersion: 'TLSv1.3',
|
|
271
|
+
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
|
|
272
|
+
})
|
|
273
|
+
assert.equal(opts.rejectUnauthorized, true)
|
|
274
|
+
assert.equal(opts.minVersion, 'TLSv1.3')
|
|
275
|
+
assert.equal(opts.ciphers, 'ECDHE-RSA-AES256-GCM-SHA384')
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
await t.test('resolves key/cert/dhparam file refs to Buffers', () => {
|
|
279
|
+
const opts = tls_socket.load_plugin_tls_options({
|
|
280
|
+
key: 'outbound_tls_key.pem',
|
|
281
|
+
cert: 'outbound_tls_cert.pem',
|
|
282
|
+
dhparam: 'dhparams.pem',
|
|
283
|
+
})
|
|
284
|
+
assert.ok(Buffer.isBuffer(opts.key) && opts.key.length > 0)
|
|
285
|
+
assert.ok(Buffer.isBuffer(opts.cert) && opts.cert.length > 0)
|
|
286
|
+
assert.ok(Buffer.isBuffer(opts.dhparam) && opts.dhparam.length > 0)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
await t.test('drops missing dhparam rather than leaving null', () => {
|
|
290
|
+
const opts = tls_socket.load_plugin_tls_options({
|
|
291
|
+
dhparam: 'does_not_exist.pem',
|
|
292
|
+
})
|
|
293
|
+
assert.equal(opts.dhparam, undefined)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await t.test('normalises no_tls_hosts / force_tls_hosts to arrays', () => {
|
|
297
|
+
const opts = tls_socket.load_plugin_tls_options({
|
|
298
|
+
no_tls_hosts: '10.0.0.5',
|
|
299
|
+
force_tls_hosts: ['a.example.com', 'b.example.com'],
|
|
300
|
+
})
|
|
301
|
+
assert.deepEqual(opts.no_tls_hosts, ['10.0.0.5'])
|
|
302
|
+
assert.deepEqual(opts.force_tls_hosts, ['a.example.com', 'b.example.com'])
|
|
303
|
+
|
|
304
|
+
const opts2 = tls_socket.load_plugin_tls_options({})
|
|
305
|
+
assert.deepEqual(opts2.no_tls_hosts, [])
|
|
306
|
+
assert.deepEqual(opts2.force_tls_hosts, [])
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
await t.test('does not set servername', () => {
|
|
310
|
+
const opts = tls_socket.load_plugin_tls_options({})
|
|
311
|
+
assert.equal(opts.servername, undefined)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
await t.test('does not mutate the input plugin cfg', () => {
|
|
315
|
+
const input = { rejectUnauthorized: true, no_tls_hosts: '10.0.0.5' }
|
|
316
|
+
const before = JSON.stringify(input)
|
|
317
|
+
tls_socket.load_plugin_tls_options(input)
|
|
318
|
+
assert.equal(JSON.stringify(input), before)
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
})
|