haraka 0.0.33 → 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 +1894 -0
- package/CLAUDE.md +40 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/GEMINI.md +38 -0
- package/LICENSE +22 -0
- package/Plugins.md +227 -0
- package/README.md +119 -4
- 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 +99 -4
- 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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { describe, it, before, beforeEach, afterEach } = require('node:test')
|
|
4
|
+
const assert = require('node:assert')
|
|
5
|
+
const { EventEmitter } = require('node:events')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const path = require('node:path')
|
|
8
|
+
|
|
9
|
+
// Load outbound/index FIRST to avoid the circular-dependency boot-order issue.
|
|
10
|
+
const outbound = require('../../outbound')
|
|
11
|
+
const Hmail = outbound.HMailItem
|
|
12
|
+
const client_pool = require('../../outbound/client_pool')
|
|
13
|
+
|
|
14
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const onEvent = (emitter, event) => new Promise((resolve) => emitter.once(event, resolve))
|
|
17
|
+
|
|
18
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
describe('outbound/hmail', () => {
|
|
21
|
+
let hmail
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
hmail = new Hmail(
|
|
25
|
+
'1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
|
|
26
|
+
'test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
|
|
27
|
+
{},
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('socket error/timeout handler robustness (#3388)', () => {
|
|
32
|
+
const mx = { using_lmtp: false, port: 25, exchange: 'mx.example.com', bind: null, bind_helo: 'test' }
|
|
33
|
+
let origRelease
|
|
34
|
+
|
|
35
|
+
function makeSocket() {
|
|
36
|
+
const s = new EventEmitter()
|
|
37
|
+
s.name = 'mock'
|
|
38
|
+
s.writable = true
|
|
39
|
+
s.write = () => {}
|
|
40
|
+
s.destroy = () => {}
|
|
41
|
+
return s
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
origRelease = client_pool.release_client
|
|
46
|
+
client_pool.release_client = () => {}
|
|
47
|
+
hmail.todo = { rcpt_to: [] }
|
|
48
|
+
hmail.try_deliver = () => {}
|
|
49
|
+
hmail.logerror = () => {}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
client_pool.release_client = origRelease
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('error then timeout does not throw ERR_UNHANDLED_ERROR', () => {
|
|
57
|
+
const socket = makeSocket()
|
|
58
|
+
hmail.try_deliver_host_on_socket(mx, '1.2.3.4', 25, socket)
|
|
59
|
+
socket.emit('error', new Error('connection refused'))
|
|
60
|
+
assert.doesNotThrow(() => socket.emit('timeout'), 'timeout after error must not crash')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('timeout then error does not throw ERR_UNHANDLED_ERROR', () => {
|
|
64
|
+
const socket = makeSocket()
|
|
65
|
+
hmail.try_deliver_host_on_socket(mx, '1.2.3.4', 25, socket)
|
|
66
|
+
socket.emit('timeout')
|
|
67
|
+
assert.doesNotThrow(
|
|
68
|
+
() => socket.emit('error', new Error('late error')),
|
|
69
|
+
'error after timeout must not crash',
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('multiple timeouts do not throw ERR_UNHANDLED_ERROR', () => {
|
|
74
|
+
const socket = makeSocket()
|
|
75
|
+
hmail.try_deliver_host_on_socket(mx, '1.2.3.4', 25, socket)
|
|
76
|
+
socket.emit('timeout')
|
|
77
|
+
assert.doesNotThrow(() => socket.emit('timeout'), 'second timeout must not crash')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('sort_mx orders by priority ascending', () => {
|
|
82
|
+
const sorted = hmail.sort_mx([
|
|
83
|
+
{ exchange: 'mx2.example.com', priority: 5 },
|
|
84
|
+
{ exchange: 'mx1.example.com', priority: 6 },
|
|
85
|
+
])
|
|
86
|
+
assert.equal(sorted[0].exchange, 'mx2.example.com')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('sort_mx shuffles equal-priority entries', () => {
|
|
90
|
+
const sorted = hmail.sort_mx([
|
|
91
|
+
{ exchange: 'mx2.example.com', priority: 5 },
|
|
92
|
+
{ exchange: 'mx1.example.com', priority: 6 },
|
|
93
|
+
{ exchange: 'mx3.example.com', priority: 6 },
|
|
94
|
+
])
|
|
95
|
+
assert.equal(sorted[0].exchange, 'mx2.example.com')
|
|
96
|
+
assert.ok(['mx1.example.com', 'mx3.example.com'].includes(sorted[1].exchange))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('get_force_tls matches by IP and domain', () => {
|
|
100
|
+
hmail.todo = { domain: 'miss.example.com' }
|
|
101
|
+
hmail.obtls.cfg = { force_tls_hosts: ['1.2.3.4', 'hit.example.com'] }
|
|
102
|
+
assert.equal(hmail.get_force_tls({ exchange: '1.2.3.4' }), true)
|
|
103
|
+
assert.equal(hmail.get_force_tls({ exchange: '1.2.3.5' }), false)
|
|
104
|
+
hmail.todo = { domain: 'hit.example.com' }
|
|
105
|
+
assert.equal(hmail.get_force_tls({ exchange: '1.2.3.5' }), true)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const TOOLONG_FIXTURE = 'test/queue/1509000000000_1509000000000_0_99999_ToLong_1_haraka'
|
|
110
|
+
|
|
111
|
+
const makeToolongFixture = () => {
|
|
112
|
+
const buf = Buffer.alloc(50)
|
|
113
|
+
buf.writeUInt32BE(9999, 0) // declares 9999 bytes but file has only 46 after the header
|
|
114
|
+
buf.write('{"domain":"example.com"', 4)
|
|
115
|
+
fs.writeFileSync(TOOLONG_FIXTURE, buf)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
describe('outbound/hmail.HMailItem — queue file loading', () => {
|
|
119
|
+
before(makeToolongFixture)
|
|
120
|
+
|
|
121
|
+
it('loads a valid queue file', async () => {
|
|
122
|
+
const h = new Hmail(
|
|
123
|
+
'1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
|
|
124
|
+
'test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
|
|
125
|
+
{},
|
|
126
|
+
)
|
|
127
|
+
await onEvent(h, 'ready')
|
|
128
|
+
assert.ok(h)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('loads a TODO with multibyte chars without error', async () => {
|
|
132
|
+
const h = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_1_qfile', 'test/fixtures/todo_qfile.txt', {})
|
|
133
|
+
await onEvent(h, 'ready')
|
|
134
|
+
assert.ok(h)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('emits error on too-short declared TODO length', async () => {
|
|
138
|
+
const h = new Hmail(
|
|
139
|
+
'1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka',
|
|
140
|
+
'test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka',
|
|
141
|
+
{},
|
|
142
|
+
)
|
|
143
|
+
const err = await new Promise((resolve) => {
|
|
144
|
+
h.once('ready', () => resolve(null))
|
|
145
|
+
h.once('error', resolve)
|
|
146
|
+
})
|
|
147
|
+
assert.ok(err, 'expected an error for truncated TODO')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('emits error on too-long declared TODO length', async () => {
|
|
151
|
+
// Recreate fixture in case a prior run renamed it to the error queue
|
|
152
|
+
makeToolongFixture()
|
|
153
|
+
const h = new Hmail('1509000000000_1509000000000_0_99999_ToLong_1_haraka', TOOLONG_FIXTURE, {})
|
|
154
|
+
const err = await new Promise((resolve) => {
|
|
155
|
+
h.once('ready', () => resolve(null))
|
|
156
|
+
h.once('error', resolve)
|
|
157
|
+
})
|
|
158
|
+
assert.ok(err, 'expected an error for oversized TODO')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('skips zero-length file without crash', async () => {
|
|
162
|
+
const h = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_zero', 'test/queue/zero-length', {})
|
|
163
|
+
await new Promise((resolve) => {
|
|
164
|
+
h.once('ready', resolve)
|
|
165
|
+
h.once('error', resolve)
|
|
166
|
+
})
|
|
167
|
+
assert.ok(h)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('releases queue slot when stat fails on exhausted-retry item (regression #3560)', async () => {
|
|
171
|
+
// When fs.stat fails and num_failures already equals temp_fail_intervals.length,
|
|
172
|
+
// temp_fail() calls convert_temp_failed_to_bounce() while this.todo is null.
|
|
173
|
+
// That must not crash, and must call next_cb() to release the queue slot.
|
|
174
|
+
// attempts=12 in the filename causes num_failures=12 at construction; after
|
|
175
|
+
// temp_fail() increments it to 13 (> temp_fail_intervals.length=12) the
|
|
176
|
+
// overflow path fires with this.todo still null.
|
|
177
|
+
const fname = '1508455115683_1508455115683_12_90253_9Q4o4V_1_haraka'
|
|
178
|
+
const h = new Hmail(fname, '/nonexistent/path/that/cannot/be/stat/ed', {})
|
|
179
|
+
await new Promise((resolve, reject) => {
|
|
180
|
+
const timer = setTimeout(() => reject(new Error('next_cb was never called')), 2000)
|
|
181
|
+
h.next_cb = () => {
|
|
182
|
+
clearTimeout(timer)
|
|
183
|
+
resolve()
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
assert.equal(h.todo, null, 'todo must remain null — file was never readable')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('lifecycle: reads and writes a queue file', async () => {
|
|
190
|
+
const h = new Hmail('1507509981169_1507509981169_0_61403_e0Y0Ym_2_qfile', 'test/fixtures/todo_qfile.txt', {})
|
|
191
|
+
|
|
192
|
+
await onEvent(h, 'ready')
|
|
193
|
+
|
|
194
|
+
const tmpfile = path.resolve('test', 'test-queue', 'delete-me')
|
|
195
|
+
await fs.promises.mkdir(path.dirname(tmpfile), { recursive: true })
|
|
196
|
+
const ws = new fs.WriteStream(tmpfile)
|
|
197
|
+
|
|
198
|
+
await new Promise((resolve, reject) => {
|
|
199
|
+
outbound.build_todo(h.todo, ws, () => {
|
|
200
|
+
const ds = h.data_stream()
|
|
201
|
+
ds.pipe(ws)
|
|
202
|
+
ws.on('close', resolve)
|
|
203
|
+
ws.on('error', reject)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
assert.equal(fs.statSync(tmpfile).size, 4204)
|
|
208
|
+
fs.unlinkSync(tmpfile)
|
|
209
|
+
})
|
|
210
|
+
})
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { describe, it, beforeEach, afterEach } = require('node:test')
|
|
4
|
+
const assert = require('node:assert')
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
|
|
8
|
+
const constants = require('haraka-constants')
|
|
9
|
+
|
|
10
|
+
const lines = [
|
|
11
|
+
'From: John Johnson <john@example.com>',
|
|
12
|
+
'To: Jane Johnson <jane@example.com>',
|
|
13
|
+
"Subject: What's for dinner?",
|
|
14
|
+
'',
|
|
15
|
+
"I'm hungry.",
|
|
16
|
+
'',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
describe('outbound', () => {
|
|
20
|
+
it('converts \\n and \\r\\n line endings to \\r\\n', () => {
|
|
21
|
+
for (const ending of ['\n', '\r\n']) {
|
|
22
|
+
let contents = lines.join(ending)
|
|
23
|
+
let result = ''
|
|
24
|
+
|
|
25
|
+
let match
|
|
26
|
+
const re = /^([^\n]*\n?)/
|
|
27
|
+
while ((match = re.exec(contents))) {
|
|
28
|
+
let line = match[1]
|
|
29
|
+
line = line.replace(/\r?\n?$/, '\r\n')
|
|
30
|
+
result += line
|
|
31
|
+
contents = contents.substring(match[1].length)
|
|
32
|
+
if (contents.length === 0) break
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(lines.join('\r\n'), result)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('log_methods added to HMailItem prototype', () => {
|
|
40
|
+
const levels = ['DATA', 'PROTOCOL', 'DEBUG', 'INFO', 'NOTICE', 'WARN', 'ERROR', 'CRIT', 'ALERT', 'EMERG']
|
|
41
|
+
// Load via outbound/index to avoid circular-dep boot-order issue
|
|
42
|
+
const HMailItem = require('../../outbound').HMailItem
|
|
43
|
+
for (const level of levels) {
|
|
44
|
+
assert.ok(HMailItem.prototype[`log${level.toLowerCase()}`], `log method for ${level}`)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('set_temp_fail_intervals coverage', () => {
|
|
49
|
+
const config = require('../../outbound/config')
|
|
50
|
+
assert.deepEqual(
|
|
51
|
+
config.cfg.temp_fail_intervals,
|
|
52
|
+
[64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
config.cfg.temp_fail_intervals = '10s, 1m*2'
|
|
56
|
+
config.set_temp_fail_intervals()
|
|
57
|
+
assert.deepEqual(config.cfg.temp_fail_intervals, [10, 60, 60])
|
|
58
|
+
|
|
59
|
+
config.cfg.temp_fail_intervals = '30s, 1m, 5m, 9m, 15m*3, 30m*2, 1h*3, 2h*3, 1d'
|
|
60
|
+
config.set_temp_fail_intervals()
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
config.cfg.temp_fail_intervals,
|
|
63
|
+
[30, 60, 300, 540, 900, 900, 900, 1800, 1800, 3600, 3600, 3600, 7200, 7200, 7200, 86400],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
config.cfg.temp_fail_intervals = 'none'
|
|
67
|
+
config.set_temp_fail_intervals()
|
|
68
|
+
assert.deepEqual(config.cfg.temp_fail_intervals, [])
|
|
69
|
+
|
|
70
|
+
config.cfg.temp_fail_intervals = '60 min'
|
|
71
|
+
config.set_temp_fail_intervals()
|
|
72
|
+
assert.deepEqual(
|
|
73
|
+
config.cfg.temp_fail_intervals,
|
|
74
|
+
[64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072],
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('get_tls_options', () => {
|
|
79
|
+
let outbound, obtls
|
|
80
|
+
|
|
81
|
+
beforeEach(async () => {
|
|
82
|
+
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
83
|
+
outbound = require('../../outbound')
|
|
84
|
+
obtls = require('../../outbound/tls')
|
|
85
|
+
const tls_socket = require('../../tls_socket')
|
|
86
|
+
|
|
87
|
+
const testDir = path.resolve('test')
|
|
88
|
+
outbound.config = outbound.config.module_config(testDir)
|
|
89
|
+
obtls.test_config(tls_socket.config.module_config(testDir), outbound.config)
|
|
90
|
+
await new Promise((resolve) => obtls.init(resolve))
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
delete process.env.HARAKA_TEST_DIR
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('gets TLS properties from tls.ini.outbound', () => {
|
|
98
|
+
const tls_config = obtls.get_tls_options({ exchange: 'mail.example.com' })
|
|
99
|
+
assert.deepEqual(tls_config, {
|
|
100
|
+
servername: 'mail.example.com',
|
|
101
|
+
key: fs.readFileSync(path.resolve('test', 'config', 'outbound_tls_key.pem')),
|
|
102
|
+
cert: fs.readFileSync(path.resolve('test', 'config', 'outbound_tls_cert.pem')),
|
|
103
|
+
dhparam: fs.readFileSync(path.resolve('test', 'config', 'dhparams.pem')),
|
|
104
|
+
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
|
|
105
|
+
minVersion: 'TLSv1',
|
|
106
|
+
rejectUnauthorized: false,
|
|
107
|
+
requestCert: false,
|
|
108
|
+
honorCipherOrder: false,
|
|
109
|
+
redis: { disable_for_failed_hosts: false },
|
|
110
|
+
no_tls_hosts: ['127.0.0.2', '192.168.31.1/24'],
|
|
111
|
+
force_tls_hosts: ['first.example.com', 'second.example.net'],
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('build_todo', () => {
|
|
117
|
+
let outbound
|
|
118
|
+
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
outbound = require('../../outbound')
|
|
121
|
+
try {
|
|
122
|
+
fs.unlinkSync('test/queue/multibyte')
|
|
123
|
+
fs.unlinkSync('test/queue/plain')
|
|
124
|
+
} catch (ignore) {}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('saves a plain queue file', () => {
|
|
128
|
+
const todo = JSON.parse(
|
|
129
|
+
'{"queue_time":1507509981169,"domain":"redacteed.com","rcpt_to":[{"original":"<postmaster@redacteed.com>","original_host":"redacteed.com","host":"redacteed.com","user":"postmaster"}],"mail_from":{"original":"<matt@tnpi.net>","original_host":"tnpi.net","host":"tnpi.net","user":"matt"},"notes":{"authentication_results":["spf=pass smtp.mailfrom=tnpi.net"],"spf_mail_result":"Pass","spf_mail_record":"v=spf1 a mx include:mx.theartfarm.com ?include:forwards._spf.tnpi.net include:lists._spf.tnpi.net -all","attachment_count":0,"attachments":[{"ctype":"application/pdf","filename":"FileWithoutAccent Chars.pdf","extension":".pdf","md5":"6c1d5f5c047cff3f6320b1210970bdf6"}],"attachment_ctypes":["application/pdf","multipart/mixed","text/plain","application/pdf"],"attachment_files":["FileWithoutaccent Chars.pdf"],"attachment_archive_files":[]},"uuid":"1D5483B0-3E00-4280-A961-3AFD2017B4FC.1"}',
|
|
130
|
+
)
|
|
131
|
+
const fd = fs.openSync('test/queue/plain', 'w')
|
|
132
|
+
const ws = new fs.createWriteStream('test/queue/plain', { fd, flags: constants.WRITE_EXCL })
|
|
133
|
+
ws.on('error', (e) => console.error(e))
|
|
134
|
+
outbound.build_todo(todo, ws, () => {
|
|
135
|
+
ws.write(Buffer.from('This is the message body'))
|
|
136
|
+
fs.fsync(fd, () => ws.close())
|
|
137
|
+
})
|
|
138
|
+
assert.ok(true)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('saves a queue file with multibyte chars', () => {
|
|
142
|
+
const todo = JSON.parse(
|
|
143
|
+
'{"queue_time":1507509981169,"domain":"redacteed.com","rcpt_to":[{"original":"<postmaster@redacteed.com>","original_host":"redacteed.com","host":"redacteed.com","user":"postmaster"}],"mail_from":{"original":"<matt@tnpi.net>","original_host":"tnpi.net","host":"tnpi.net","user":"matt"},"notes":{"authentication_results":["spf=pass smtp.mailfrom=tnpi.net"],"spf_mail_result":"Pass","spf_mail_record":"v=spf1 a mx include:mx.theartfarm.com ?include:forwards._spf.tnpi.net include:lists._spf.tnpi.net -all","attachment_count":0,"attachments":[{"ctype":"application/pdf","filename":"FileW\\u00eeth\\u00c1ccent Chars.pdf","extension":".pdf","md5":"6c1d5f5c047cff3f6320b1210970bdf6"}],"attachment_ctypes":["application/pdf","multipart/mixed","text/plain","application/pdf"],"attachment_files":["FileW\\u00eeth\\u00c1ccent Chars.pdf"],"attachment_archive_files":[]},"uuid":"1D5483B0-3E00-4280-A961-3AFD2017B4FC.1"}',
|
|
144
|
+
)
|
|
145
|
+
const fd = fs.openSync('test/queue/multibyte', 'w')
|
|
146
|
+
const ws = new fs.WriteStream('test/queue/multibyte', { fd, flags: constants.WRITE_EXCL })
|
|
147
|
+
ws.on('error', (e) => console.error(e))
|
|
148
|
+
outbound.build_todo(todo, ws, () => {
|
|
149
|
+
ws.write(Buffer.from('This is the message body'))
|
|
150
|
+
fs.fsync(fd, () => ws.close())
|
|
151
|
+
})
|
|
152
|
+
assert.ok(true)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('waits for drain when stream backpressure is applied', async () => {
|
|
156
|
+
const todo = {
|
|
157
|
+
queue_time: Date.now(),
|
|
158
|
+
domain: 'example.com',
|
|
159
|
+
rcpt_to: [],
|
|
160
|
+
mail_from: {},
|
|
161
|
+
notes: {},
|
|
162
|
+
uuid: 'u1',
|
|
163
|
+
}
|
|
164
|
+
let drained = false
|
|
165
|
+
|
|
166
|
+
await new Promise((resolve) => {
|
|
167
|
+
const ws = {
|
|
168
|
+
write() {
|
|
169
|
+
return false
|
|
170
|
+
},
|
|
171
|
+
once(event, cb) {
|
|
172
|
+
assert.equal(event, 'drain')
|
|
173
|
+
setImmediate(() => {
|
|
174
|
+
drained = true
|
|
175
|
+
cb()
|
|
176
|
+
resolve()
|
|
177
|
+
})
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
outbound.build_todo(todo, ws, () => {})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
assert.equal(drained, true)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
describe('send_trans_email', () => {
|
|
188
|
+
const queueDir = path.resolve('test', 'test-queue')
|
|
189
|
+
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
192
|
+
fs.mkdirSync(queueDir, { recursive: true })
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
delete process.env.HARAKA_TEST_DIR
|
|
197
|
+
try {
|
|
198
|
+
for (const f of fs.readdirSync(queueDir)) {
|
|
199
|
+
fs.unlinkSync(path.join(queueDir, f))
|
|
200
|
+
}
|
|
201
|
+
} catch (ignore) {}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// Regression test for haraka/Haraka#3551:
|
|
205
|
+
// When dkim_verify (data_post) pipes the message_stream and DKIMVerifyStream
|
|
206
|
+
// fires its callback early via process.nextTick (no DKIM-Signature found),
|
|
207
|
+
// the chain runs synchronously into process_delivery → pipe() while the
|
|
208
|
+
// first pipe is still in flight. pre_send_trans_email_respond must yield
|
|
209
|
+
// (via setImmediate) before opening a new pipe.
|
|
210
|
+
it('yields to setImmediate before opening process_delivery pipes', async () => {
|
|
211
|
+
const stream = require('node:stream')
|
|
212
|
+
const Transaction = require('../../transaction')
|
|
213
|
+
const Address = require('../../address').Address
|
|
214
|
+
const outbound = require('../../outbound')
|
|
215
|
+
const plugins = require('../../plugins')
|
|
216
|
+
|
|
217
|
+
const txn = Transaction.createTransaction()
|
|
218
|
+
const origRunHooks = plugins.run_hooks
|
|
219
|
+
try {
|
|
220
|
+
txn.mail_from = new Address('<from@example.com>')
|
|
221
|
+
txn.rcpt_to = [new Address('<to@example.com>')]
|
|
222
|
+
txn.message_stream.add_line(Buffer.from('From: from@example.com\r\n'))
|
|
223
|
+
txn.message_stream.add_line(Buffer.from('To: to@example.com\r\n'))
|
|
224
|
+
txn.message_stream.add_line(Buffer.from('\r\n'))
|
|
225
|
+
txn.message_stream.add_line(Buffer.from('body\r\n'))
|
|
226
|
+
await new Promise((r) => txn.message_stream.add_line_end(r))
|
|
227
|
+
|
|
228
|
+
// Start a pipe on the message_stream and fire a synchronous callback
|
|
229
|
+
// before it drains — this models what dkim_verify does.
|
|
230
|
+
const verifierFiredCb = new Promise((resolve) => {
|
|
231
|
+
let scheduled = false
|
|
232
|
+
const verifier = new stream.Writable({
|
|
233
|
+
write(_chunk, _enc, cb) {
|
|
234
|
+
if (!scheduled) {
|
|
235
|
+
scheduled = true
|
|
236
|
+
process.nextTick(resolve)
|
|
237
|
+
}
|
|
238
|
+
cb()
|
|
239
|
+
},
|
|
240
|
+
})
|
|
241
|
+
txn.message_stream.pipe(verifier)
|
|
242
|
+
})
|
|
243
|
+
await verifierFiredCb
|
|
244
|
+
|
|
245
|
+
// Now invoke send_trans_email — its pre_send_trans_email_respond
|
|
246
|
+
// should yield (await setImmediate) before calling process_delivery,
|
|
247
|
+
// letting the verifier pipe drain so the new pipe can succeed.
|
|
248
|
+
await new Promise((resolve, reject) => {
|
|
249
|
+
// Stub the heavy bits: we only care that the chain doesn't throw
|
|
250
|
+
// "Cannot pipe while currently piping" before queuing happens.
|
|
251
|
+
plugins.run_hooks = (hook, obj) => {
|
|
252
|
+
if (hook === 'pre_send_trans_email') {
|
|
253
|
+
// Mimic empty-hook synchronous callback (no plugins)
|
|
254
|
+
obj.pre_send_trans_email_respond(constants.cont).catch(reject)
|
|
255
|
+
} else {
|
|
256
|
+
origRunHooks.call(plugins, hook, obj)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
outbound.send_trans_email(txn, (retval) => {
|
|
261
|
+
if (retval === constants.ok) resolve()
|
|
262
|
+
else reject(new Error(`unexpected retval ${retval}`))
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
} finally {
|
|
266
|
+
plugins.run_hooks = origRunHooks
|
|
267
|
+
txn.message_stream.destroy()
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('adds missing Message-Id/Date and prepends Received before queueing', async () => {
|
|
272
|
+
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
273
|
+
const Address = require('../../address').Address
|
|
274
|
+
const outbound = require('../../outbound')
|
|
275
|
+
const plugins = require('../../plugins')
|
|
276
|
+
|
|
277
|
+
const added = []
|
|
278
|
+
const leading = []
|
|
279
|
+
const queued = []
|
|
280
|
+
const transaction = {
|
|
281
|
+
uuid: 'txn-add-headers',
|
|
282
|
+
header: {
|
|
283
|
+
get_all() {
|
|
284
|
+
return []
|
|
285
|
+
},
|
|
286
|
+
get() {
|
|
287
|
+
return null
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
rcpt_to: [new Address('<user@example.com>')],
|
|
291
|
+
notes: {},
|
|
292
|
+
add_header(name, value) {
|
|
293
|
+
added.push([name, value])
|
|
294
|
+
},
|
|
295
|
+
remove_header() {},
|
|
296
|
+
add_leading_header(name, value) {
|
|
297
|
+
leading.push([name, value])
|
|
298
|
+
},
|
|
299
|
+
results: {
|
|
300
|
+
add() {},
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const originalRunHooks = plugins.run_hooks
|
|
305
|
+
const originalProcessDelivery = outbound.process_delivery
|
|
306
|
+
const originalPush = outbound.delivery_queue.push
|
|
307
|
+
outbound.delivery_queue.push = (hmail) => {
|
|
308
|
+
queued.push(hmail)
|
|
309
|
+
}
|
|
310
|
+
outbound.process_delivery = async (_okPaths, _todo, hmails) => {
|
|
311
|
+
hmails.push({ queued: true })
|
|
312
|
+
}
|
|
313
|
+
plugins.run_hooks = (hook, conn) => {
|
|
314
|
+
if (hook === 'pre_send_trans_email') {
|
|
315
|
+
conn.pre_send_trans_email_respond(constants.cont)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const result = await new Promise((resolve) => {
|
|
321
|
+
outbound.send_trans_email(transaction, (retval, msg) => resolve({ retval, msg }))
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
assert.equal(result.retval, constants.ok)
|
|
325
|
+
assert.match(result.msg, /Message Queued/)
|
|
326
|
+
assert.equal(queued.length, 1)
|
|
327
|
+
assert.equal(
|
|
328
|
+
added.some(([name]) => name === 'Message-Id'),
|
|
329
|
+
true,
|
|
330
|
+
)
|
|
331
|
+
assert.equal(
|
|
332
|
+
added.some(([name]) => name === 'Date'),
|
|
333
|
+
true,
|
|
334
|
+
)
|
|
335
|
+
assert.equal(leading[0][0], 'Received')
|
|
336
|
+
} finally {
|
|
337
|
+
plugins.run_hooks = originalRunHooks
|
|
338
|
+
outbound.process_delivery = originalProcessDelivery
|
|
339
|
+
outbound.delivery_queue.push = originalPush
|
|
340
|
+
delete process.env.HARAKA_TEST_DIR
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
describe('timer_queue', () => {
|
|
346
|
+
let ob_timer_queue
|
|
347
|
+
|
|
348
|
+
beforeEach(() => {
|
|
349
|
+
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
350
|
+
require('../../outbound')
|
|
351
|
+
const { TimerQueue } = require('haraka-utils')
|
|
352
|
+
ob_timer_queue = new TimerQueue(500)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
afterEach(() => {
|
|
356
|
+
delete process.env.HARAKA_TEST_DIR
|
|
357
|
+
ob_timer_queue.shutdown()
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('has initial length of 0', () => {
|
|
361
|
+
assert.equal(ob_timer_queue.length(), 0)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('can add items', () => {
|
|
365
|
+
ob_timer_queue.add('1', 1000)
|
|
366
|
+
ob_timer_queue.add('2', 2000)
|
|
367
|
+
assert.equal(ob_timer_queue.length(), 2)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('can drain items', () => {
|
|
371
|
+
ob_timer_queue.add('1', 1000)
|
|
372
|
+
ob_timer_queue.add('2', 2000)
|
|
373
|
+
ob_timer_queue.drain()
|
|
374
|
+
assert.equal(ob_timer_queue.length(), 0)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('can discard items by id', () => {
|
|
378
|
+
ob_timer_queue.add('1', 1000)
|
|
379
|
+
ob_timer_queue.add('2', 2000)
|
|
380
|
+
ob_timer_queue.discard('2')
|
|
381
|
+
assert.equal(ob_timer_queue.length(), 1)
|
|
382
|
+
assert.equal(ob_timer_queue.queue[0].id, '1')
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
})
|