Haraka 3.2.1 → 3.3.1

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.
Files changed (82) hide show
  1. package/.githooks/pre-commit +41 -0
  2. package/.prettierignore +1 -0
  3. package/.qlty/.gitignore +7 -0
  4. package/.qlty/configs/.shellcheckrc +1 -0
  5. package/.qlty/qlty.toml +15 -0
  6. package/CHANGELOG.md +29 -5
  7. package/CONTRIBUTORS.md +5 -5
  8. package/README.md +6 -3
  9. package/bin/haraka +12 -4
  10. package/config/connection.ini +6 -0
  11. package/connection.js +67 -68
  12. package/contrib/bsd-rc.d/haraka +2 -0
  13. package/docs/CoreConfig.md +2 -0
  14. package/docs/HAProxy.md +4 -1
  15. package/eslint.config.mjs +2 -30
  16. package/haraka.js +2 -2
  17. package/line_socket.js +6 -33
  18. package/outbound/hmail.js +18 -29
  19. package/outbound/index.js +3 -3
  20. package/outbound/queue.js +8 -5
  21. package/package.json +49 -46
  22. package/plugins/auth/auth_proxy.js +7 -4
  23. package/plugins/block_me.js +1 -1
  24. package/plugins/delay_deny.js +1 -1
  25. package/plugins/queue/qmail-queue.js +1 -1
  26. package/plugins/queue/quarantine.js +5 -5
  27. package/plugins/queue/smtp_bridge.js +1 -1
  28. package/plugins/queue/smtp_proxy.js +2 -2
  29. package/plugins/status.js +2 -2
  30. package/plugins/toobusy.js +1 -1
  31. package/plugins.js +4 -3
  32. package/server.js +172 -28
  33. package/smtp_client.js +2 -1
  34. package/test/connection.js +119 -2
  35. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  36. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  37. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  38. package/test/fixtures/line_socket.js +1 -1
  39. package/test/fixtures/util_hmailitem.js +2 -3
  40. package/test/outbound/index.js +6 -7
  41. package/test/outbound/qfile.js +1 -1
  42. package/test/outbound/queue.js +2 -2
  43. package/test/plugins/auth/auth_base.js +17 -17
  44. package/test/plugins/auth/auth_bridge.js +3 -3
  45. package/test/plugins/auth/auth_vpopmaild.js +3 -3
  46. package/test/plugins/auth/flat_file.js +16 -21
  47. package/test/plugins/block_me.js +7 -23
  48. package/test/plugins/data.signatures.js +17 -20
  49. package/test/plugins/delay_deny.js +3 -4
  50. package/test/plugins/prevent_credential_leaks.js +17 -21
  51. package/test/plugins/process_title.js +12 -6
  52. package/test/plugins/queue/deliver.js +7 -8
  53. package/test/plugins/queue/discard.js +3 -4
  54. package/test/plugins/queue/lmtp.js +5 -6
  55. package/test/plugins/queue/qmail-queue.js +7 -8
  56. package/test/plugins/queue/quarantine.js +3 -4
  57. package/test/plugins/queue/smtp_bridge.js +5 -7
  58. package/test/plugins/queue/smtp_forward.js +49 -60
  59. package/test/plugins/queue/smtp_proxy.js +6 -7
  60. package/test/plugins/rcpt_to.host_list_base.js +6 -9
  61. package/test/plugins/rcpt_to.in_host_list.js +6 -11
  62. package/test/plugins/record_envelope_addresses.js +33 -60
  63. package/test/plugins/reseed_rng.js +3 -3
  64. package/test/plugins/status.js +4 -5
  65. package/test/plugins/tarpit.js +3 -4
  66. package/test/plugins/tls.js +3 -5
  67. package/test/plugins/toobusy.js +186 -9
  68. package/test/plugins/xclient.js +7 -4
  69. package/test/server.js +425 -1
  70. package/test/smtp_client.js +11 -18
  71. package/test/tls_socket.js +3 -6
  72. package/tls_socket.js +3 -3
  73. package/transaction.js +3 -3
  74. package/address.js +0 -53
  75. package/endpoint.js +0 -96
  76. package/host_pool.js +0 -169
  77. package/outbound/fsync_writestream.js +0 -44
  78. package/outbound/timer_queue.js +0 -86
  79. package/rfc1869.js +0 -93
  80. package/test/endpoint.js +0 -128
  81. package/test/host_pool.js +0 -188
  82. package/test/rfc1869.js +0 -89
package/test/host_pool.js DELETED
@@ -1,188 +0,0 @@
1
- 'use strict'
2
-
3
- const { describe, it } = require('node:test')
4
- const assert = require('node:assert/strict')
5
-
6
- const HostPool = require('../host_pool')
7
-
8
- describe('HostPool', () => {
9
- it('get a host', () => {
10
- const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222')
11
- const host = pool.get_host()
12
-
13
- assert.ok(/\d\.\d\.\d\.\d/.test(host.host), `'${host.host}' looks like a IP`)
14
- assert.ok(/\d\d\d\d/.test(host.port), `'${host.port}' looks like a port`)
15
- })
16
-
17
- it('uses all the list', () => {
18
- const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222')
19
-
20
- const host1 = pool.get_host()
21
- const host2 = pool.get_host()
22
- const host3 = pool.get_host()
23
-
24
- assert.notEqual(host1.host, host2.host)
25
- assert.notEqual(host3.host, host2.host)
26
- assert.equal(host3.host, host1.host)
27
- })
28
-
29
- it('default port 25', () => {
30
- const pool = new HostPool('1.1.1.1, 2.2.2.2')
31
-
32
- const host1 = pool.get_host()
33
- const host2 = pool.get_host()
34
-
35
- assert.equal(host1.port, 25, `is port 25: ${host1.port}`)
36
- assert.equal(host2.port, 25, `is port 25: ${host2.port}`)
37
- })
38
-
39
- it('dead host', () => {
40
- const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222', 0.001)
41
- pool.get_socket = () => ({
42
- pretendTimeout: () => {},
43
- setTimeout(ms, cb) {
44
- this.pretendTimeout = cb
45
- },
46
- listeners: {},
47
- on(ev, cb) {
48
- this.listeners[ev] = cb
49
- },
50
- connect(port, host, cb) {
51
- cb()
52
- }, // immediately "connects" to stop retry loop
53
- destroy() {},
54
- })
55
-
56
- pool.failed('1.1.1.1', '1111')
57
-
58
- let host
59
-
60
- host = pool.get_host()
61
- assert.equal(host.host, '2.2.2.2', 'dead host is not returned')
62
- host = pool.get_host()
63
- assert.equal(host.host, '2.2.2.2', 'dead host is not returned')
64
- host = pool.get_host()
65
- assert.equal(host.host, '2.2.2.2', 'dead host is not returned')
66
- })
67
-
68
- // if they're *all* dead, we return a host to try anyway, to keep from
69
- // accidentally DOS'ing ourselves if there's a transient but widespread
70
- // network outage
71
- it("they're all dead", () => {
72
- let host1
73
- let host2
74
-
75
- const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222', 0.001)
76
- pool.get_socket = () => ({
77
- pretendTimeout: () => {},
78
- setTimeout(ms, cb) {
79
- this.pretendTimeout = cb
80
- },
81
- listeners: {},
82
- on(ev, cb) {
83
- this.listeners[ev] = cb
84
- },
85
- connect(port, host, cb) {
86
- cb()
87
- }, // immediately "connects" to stop retry loop
88
- destroy() {},
89
- })
90
-
91
- host1 = pool.get_host()
92
-
93
- pool.failed('1.1.1.1', '1111')
94
- pool.failed('2.2.2.2', '2222')
95
-
96
- host2 = pool.get_host()
97
- assert.ok(host2, "if they're all dead, try one anyway")
98
- assert.notEqual(host1.host, host2.host, 'rotation continues')
99
-
100
- host1 = pool.get_host()
101
- assert.ok(host1, "if they're all dead, try one anyway")
102
- assert.notEqual(host1.host, host2.host, 'rotation continues')
103
-
104
- host2 = pool.get_host()
105
- assert.ok(host2, "if they're all dead, try one anyway")
106
- assert.notEqual(host1.host, host2.host, 'rotation continues')
107
- })
108
-
109
- // after .01 secs the timer to retry the dead host will fire, and then
110
- // we connect using this mock socket, whose "connect" always succeeds
111
- // so the code brings the dead host back to life
112
- it('host dead checking timer', async () => {
113
- let num_reqs = 0
114
- const MockSocket = function MockSocket(pool) {
115
- // these are the methods called from probe_dead_host
116
-
117
- // setTimeout on the socket
118
- this.pretendTimeout = () => {}
119
- this.setTimeout = (ms, cb) => {
120
- this.pretendTimeout = cb
121
- }
122
- // handle socket.on('error', ....
123
- this.listeners = {}
124
- this.on = (eventname, cb) => {
125
- this.listeners[eventname] = cb
126
- }
127
- this.emit = (eventname) => {
128
- this.listeners[eventname]()
129
- }
130
- // handle socket.connect(...
131
- this.connected = () => {}
132
- this.connect = (port, host, cb) => {
133
- switch (++num_reqs) {
134
- case 1:
135
- // the first time through we pretend it timed out
136
- this.pretendTimeout()
137
- break
138
- case 2:
139
- // the second time through, pretend socket error
140
- this.emit('error')
141
- break
142
- case 3:
143
- // the third time around, the socket connected
144
- cb()
145
- break
146
- default:
147
- // failsafe
148
- console.log(`num_reqs hit ${num_reqs}, wtf?`)
149
- process.exit(1)
150
- }
151
- }
152
- this.destroy = () => {}
153
- }
154
-
155
- const retry_secs = 0.001 // 1ms
156
- const pool = new HostPool('1.1.1.1:1111, 2.2.2.2:2222', retry_secs)
157
-
158
- // override the pool's get_socket method to return our mock
159
- pool.get_socket = () => new MockSocket(pool)
160
-
161
- // mark the host as failed and start up the retry timers
162
- pool.failed('1.1.1.1', '1111')
163
-
164
- assert.ok(pool.dead_hosts['1.1.1.1:1111'], 'yes it was marked dead')
165
-
166
- // probe_dead_host() will hit two failures and one success (based on
167
- // num_reqs above). So we wait at least 10s for that to happen:
168
- await new Promise((resolve, reject) => {
169
- const timer = setTimeout(() => {
170
- clearInterval(interval)
171
- reject(new Error('probe_dead_host failed'))
172
- }, 10 * 1000)
173
-
174
- const interval = setInterval(
175
- () => {
176
- if (!pool.dead_hosts['1.1.1.1:1111']) {
177
- clearTimeout(timer)
178
- clearInterval(interval)
179
- resolve()
180
- }
181
- },
182
- retry_secs * 1000 * 3,
183
- )
184
- })
185
-
186
- assert.ok(true, 'timer un-deaded it')
187
- })
188
- })
package/test/rfc1869.js DELETED
@@ -1,89 +0,0 @@
1
- 'use strict'
2
-
3
- const { describe, it } = require('node:test')
4
- const assert = require('node:assert/strict')
5
-
6
- const { parse } = require('../rfc1869')
7
-
8
- function _check(line, expected) {
9
- const match = /^(MAIL|RCPT)\s+(.*)$/.exec(line)
10
- const parsed = parse(match[1].toLowerCase(), match[2])
11
- assert.deepEqual(parsed, expected)
12
- }
13
-
14
- describe('rfc1869', () => {
15
- describe('valid parse cases', () => {
16
- const validCases = [
17
- // MAIL FROM variants
18
- ['MAIL FROM:<>', ['<>']],
19
- ['MAIL FROM:', ['<>']],
20
- ['MAIL FROM:<postmaster>', ['<postmaster>']],
21
- ['MAIL FROM:user', ['user']],
22
- ['MAIL FROM:user size=1234', ['user', 'size=1234']],
23
- ['MAIL FROM:user@domain size=1234', ['user@domain', 'size=1234']],
24
- ['MAIL FROM:<user@domain> size=1234', ['<user@domain>', 'size=1234']],
25
- ['MAIL FROM:<user@domain> somekey', ['<user@domain>', 'somekey']],
26
- ['MAIL FROM:<user@domain> somekey other=foo', ['<user@domain>', 'somekey', 'other=foo']],
27
- // RFC 1652 BODY extension keyword
28
- ['MAIL FROM:<user@domain> BODY=8BITMIME', ['<user@domain>', 'BODY=8BITMIME']],
29
- // RFC 6531 SMTPUTF8 keyword (no value)
30
- ['MAIL FROM:<user@domain> SMTPUTF8', ['<user@domain>', 'SMTPUTF8']],
31
- // RCPT TO variants
32
- ['RCPT TO: 0@mailblog.biz 0=9 1=9', ['<0@mailblog.biz>', '0=9', '1=9']],
33
- ['RCPT TO:<r86x-ray@emailitin.com> state=1', ['<r86x-ray@emailitin.com>', 'state=1']],
34
- ['RCPT TO:<user=name@domain.com> foo=bar', ['<user=name@domain.com>', 'foo=bar']],
35
- ['RCPT TO:<postmaster>', ['<postmaster>']],
36
- ['RCPT TO:<abuse>', ['<abuse>']],
37
- ]
38
-
39
- for (const [line, expected] of validCases) {
40
- it(line, () => _check(line, expected))
41
- }
42
- })
43
-
44
- describe('error cases', () => {
45
- const throwCases = [
46
- {
47
- desc: 'MAIL FROM with space inside angle-bracket address',
48
- args: ['mail', 'FROM:<user@dom ain>'],
49
- },
50
- {
51
- desc: 'RCPT TO with syntax error in address (space in address)',
52
- args: ['rcpt', 'TO: user @domain bad'],
53
- },
54
- {
55
- desc: 'RCPT TO unknown address (no @ and not postmaster/abuse)',
56
- args: ['rcpt', 'TO:unknown'],
57
- },
58
- ]
59
-
60
- for (const { desc, args } of throwCases) {
61
- it(`throws: ${desc}`, () => {
62
- assert.throws(() => parse(...args), Error)
63
- })
64
- }
65
- })
66
-
67
- describe('strict mode', () => {
68
- const strictValidCases = [
69
- ['mail', 'FROM:<user@domain.com>', '<user@domain.com>'],
70
- ['rcpt', 'TO:<user@domain.com>', '<user@domain.com>'],
71
- ]
72
- for (const [type, line, expected] of strictValidCases) {
73
- it(`strict ${type.toUpperCase()} with angle brackets accepts address`, () => {
74
- const result = parse(type, line, true)
75
- assert.equal(result[0], expected)
76
- })
77
- }
78
-
79
- const strictThrowCases = [
80
- ['mail', 'FROM:user@domain.com'],
81
- ['rcpt', 'TO:user@domain.com'],
82
- ]
83
- for (const [type, line] of strictThrowCases) {
84
- it(`strict ${type.toUpperCase()} without angle brackets throws`, () => {
85
- assert.throws(() => parse(type, line, true), Error)
86
- })
87
- }
88
- })
89
- })