Haraka 3.1.3 → 3.1.5
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/.prettierignore +2 -0
- package/CONTRIBUTORS.md +23 -1
- package/Changes.md +52 -0
- package/Plugins.md +81 -64
- package/README.md +1 -1
- package/bin/haraka +7 -5
- package/connection.js +15 -19
- package/docs/Plugins.md +1 -1
- package/docs/plugins/aliases.md +0 -2
- package/docs/plugins/queue/qmail-queue.md +0 -1
- package/logger.js +2 -2
- package/outbound/hmail.js +76 -83
- package/outbound/index.js +36 -34
- package/outbound/queue.js +231 -176
- package/package.json +26 -29
- package/plugins/prevent_credential_leaks.js +2 -2
- package/plugins/process_title.js +1 -1
- package/plugins/queue/smtp_forward.js +5 -5
- package/plugins/status.js +8 -5
- package/plugins/tls.js +1 -1
- package/plugins.js +19 -14
- package/rfc1869.js +10 -10
- package/run_tests +8 -2
- package/server.js +15 -10
- package/smtp_client.js +10 -15
- package/test/config/tls/haraka.local.pem +47 -47
- package/test/connection.js +286 -147
- package/test/endpoint.js +5 -4
- package/test/fixtures/line_socket.js +1 -0
- package/test/fixtures/util_hmailitem.js +1 -1
- package/test/host_pool.js +57 -31
- package/test/logger.js +75 -135
- package/test/outbound/bounce_net_errors.js +132 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +140 -104
- package/test/outbound/index.js +61 -101
- package/test/outbound/qfile.js +25 -25
- package/test/outbound/queue.js +233 -0
- package/test/plugins/auth/auth_base.js +39 -44
- package/test/plugins/auth/auth_vpopmaild.js +8 -9
- package/test/plugins/queue/smtp_forward.js +953 -183
- package/test/plugins/rcpt_to.host_list_base.js +58 -93
- package/test/plugins/rcpt_to.in_host_list.js +126 -175
- package/test/plugins/record_envelope_addresses.js +93 -0
- package/test/plugins/status.js +10 -10
- package/test/plugins/tls.js +11 -21
- package/test/plugins/xclient.js +102 -0
- package/test/plugins.js +10 -13
- package/test/rfc1869.js +71 -48
- package/test/server.js +281 -436
- package/test/smtp_client.js +1194 -220
- package/test/tls_socket.js +74 -243
- package/test/transaction.js +486 -201
- package/tls_socket.js +19 -23
- package/transaction.js +33 -10
- package/config/rabbitmq.ini +0 -10
- package/config/rabbitmq_amqplib.ini +0 -19
- package/docs/plugins/queue/rabbitmq.md +0 -34
- package/docs/plugins/queue/rabbitmq_amqplib.md +0 -51
- package/plugins/queue/rabbitmq.js +0 -141
- package/plugins/queue/rabbitmq_amqplib.js +0 -96
- package/test/config/tls/ec.pem +0 -23
- package/test/config/tls/mismatched.pem +0 -49
- package/test/outbound_bounce_net_errors.js +0 -157
- package/test/outbound_bounce_rfc3464.js +0 -366
package/test/outbound/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { describe, it, beforeEach, afterEach } = require('node:test')
|
|
3
4
|
const assert = require('node:assert')
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const path = require('path')
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
6
7
|
|
|
7
8
|
const constants = require('haraka-constants')
|
|
8
9
|
const logger = require('../../logger')
|
|
@@ -22,56 +23,51 @@ describe('outbound', () => {
|
|
|
22
23
|
let contents = lines.join(ending)
|
|
23
24
|
let result = ''
|
|
24
25
|
|
|
25
|
-
// Set data_lines to lines in contents
|
|
26
26
|
let match
|
|
27
27
|
const re = /^([^\n]*\n?)/
|
|
28
28
|
while ((match = re.exec(contents))) {
|
|
29
29
|
let line = match[1]
|
|
30
|
-
line = line.replace(/\r?\n?$/, '\r\n')
|
|
30
|
+
line = line.replace(/\r?\n?$/, '\r\n')
|
|
31
31
|
result += line
|
|
32
|
-
contents = contents.
|
|
33
|
-
if (contents.length === 0)
|
|
34
|
-
break
|
|
35
|
-
}
|
|
32
|
+
contents = contents.substring(match[1].length)
|
|
33
|
+
if (contents.length === 0) break
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
assert.deepEqual(lines.join('\r\n'), result)
|
|
39
37
|
}
|
|
40
38
|
})
|
|
41
39
|
|
|
42
|
-
it('log_methods added', () => {
|
|
40
|
+
it('log_methods added to HMailItem prototype', () => {
|
|
43
41
|
const levels = ['DATA', 'PROTOCOL', 'DEBUG', 'INFO', 'NOTICE', 'WARN', 'ERROR', 'CRIT', 'ALERT', 'EMERG']
|
|
44
|
-
|
|
45
|
-
const HMailItem = require('../../outbound
|
|
46
|
-
|
|
42
|
+
// Load via outbound/index to avoid circular-dep boot-order issue
|
|
43
|
+
const HMailItem = require('../../outbound').HMailItem
|
|
47
44
|
for (const level of levels) {
|
|
48
|
-
assert.ok(HMailItem.prototype[`log${level.toLowerCase()}`], `
|
|
45
|
+
assert.ok(HMailItem.prototype[`log${level.toLowerCase()}`], `log method for ${level}`)
|
|
49
46
|
}
|
|
50
47
|
})
|
|
51
48
|
|
|
52
49
|
it('set_temp_fail_intervals coverage', () => {
|
|
53
50
|
const config = require('../../outbound/config')
|
|
54
|
-
// Test default configuration
|
|
55
51
|
assert.deepEqual(
|
|
56
52
|
config.cfg.temp_fail_intervals,
|
|
57
53
|
[64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072],
|
|
58
54
|
)
|
|
59
|
-
|
|
55
|
+
|
|
60
56
|
config.cfg.temp_fail_intervals = '10s, 1m*2'
|
|
61
57
|
config.set_temp_fail_intervals()
|
|
62
58
|
assert.deepEqual(config.cfg.temp_fail_intervals, [10, 60, 60])
|
|
63
|
-
|
|
59
|
+
|
|
64
60
|
config.cfg.temp_fail_intervals = '30s, 1m, 5m, 9m, 15m*3, 30m*2, 1h*3, 2h*3, 1d'
|
|
65
61
|
config.set_temp_fail_intervals()
|
|
66
62
|
assert.deepEqual(
|
|
67
63
|
config.cfg.temp_fail_intervals,
|
|
68
64
|
[30, 60, 300, 540, 900, 900, 900, 1800, 1800, 3600, 3600, 3600, 7200, 7200, 7200, 86400],
|
|
69
65
|
)
|
|
70
|
-
|
|
66
|
+
|
|
71
67
|
config.cfg.temp_fail_intervals = 'none'
|
|
72
68
|
config.set_temp_fail_intervals()
|
|
73
69
|
assert.deepEqual(config.cfg.temp_fail_intervals, [])
|
|
74
|
-
|
|
70
|
+
|
|
75
71
|
config.cfg.temp_fail_intervals = '60 min'
|
|
76
72
|
config.set_temp_fail_intervals()
|
|
77
73
|
assert.deepEqual(
|
|
@@ -81,29 +77,26 @@ describe('outbound', () => {
|
|
|
81
77
|
})
|
|
82
78
|
|
|
83
79
|
describe('get_tls_options', () => {
|
|
84
|
-
|
|
80
|
+
let outbound, obtls
|
|
81
|
+
|
|
82
|
+
beforeEach(async () => {
|
|
85
83
|
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
outbound = require('../../outbound')
|
|
85
|
+
obtls = require('../../outbound/tls')
|
|
88
86
|
const tls_socket = require('../../tls_socket')
|
|
89
87
|
|
|
90
|
-
// reset config to load from tests directory
|
|
91
88
|
const testDir = path.resolve('test')
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
outbound.config = outbound.config.module_config(testDir)
|
|
90
|
+
obtls.test_config(tls_socket.config.module_config(testDir), outbound.config)
|
|
91
|
+
await new Promise((resolve) => obtls.init(resolve))
|
|
95
92
|
})
|
|
96
93
|
|
|
97
|
-
afterEach((
|
|
94
|
+
afterEach(() => {
|
|
98
95
|
delete process.env.HARAKA_TEST_DIR
|
|
99
|
-
done()
|
|
100
96
|
})
|
|
101
97
|
|
|
102
98
|
it('gets TLS properties from tls.ini.outbound', () => {
|
|
103
|
-
const tls_config =
|
|
104
|
-
exchange: 'mail.example.com',
|
|
105
|
-
})
|
|
106
|
-
|
|
99
|
+
const tls_config = obtls.get_tls_options({ exchange: 'mail.example.com' })
|
|
107
100
|
assert.deepEqual(tls_config, {
|
|
108
101
|
servername: 'mail.example.com',
|
|
109
102
|
key: fs.readFileSync(path.resolve('test', 'config', 'outbound_tls_key.pem')),
|
|
@@ -122,116 +115,83 @@ describe('outbound', () => {
|
|
|
122
115
|
})
|
|
123
116
|
|
|
124
117
|
describe('build_todo', () => {
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
let outbound
|
|
119
|
+
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
outbound = require('../../outbound')
|
|
127
122
|
try {
|
|
128
123
|
fs.unlinkSync('test/queue/multibyte')
|
|
129
124
|
fs.unlinkSync('test/queue/plain')
|
|
130
125
|
} catch (ignore) {}
|
|
131
|
-
done()
|
|
132
126
|
})
|
|
133
127
|
|
|
134
|
-
it('saves a file', () => {
|
|
128
|
+
it('saves a plain queue file', () => {
|
|
135
129
|
const todo = JSON.parse(
|
|
136
130
|
'{"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"}',
|
|
137
131
|
)
|
|
138
132
|
const fd = fs.openSync('test/queue/plain', 'w')
|
|
139
|
-
const ws = new fs.createWriteStream('test/queue/plain', {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
})
|
|
143
|
-
ws.on('close', () => {
|
|
144
|
-
// console.log(arguments);
|
|
145
|
-
assert.ok(1)
|
|
146
|
-
})
|
|
147
|
-
ws.on('error', (e) => {
|
|
148
|
-
console.error(e)
|
|
149
|
-
})
|
|
150
|
-
this.outbound.build_todo(todo, ws, () => {
|
|
133
|
+
const ws = new fs.createWriteStream('test/queue/plain', { fd, flags: constants.WRITE_EXCL })
|
|
134
|
+
ws.on('error', (e) => console.error(e))
|
|
135
|
+
outbound.build_todo(todo, ws, () => {
|
|
151
136
|
ws.write(Buffer.from('This is the message body'))
|
|
152
|
-
fs.fsync(fd, () =>
|
|
153
|
-
ws.close()
|
|
154
|
-
})
|
|
137
|
+
fs.fsync(fd, () => ws.close())
|
|
155
138
|
})
|
|
139
|
+
assert.ok(true)
|
|
156
140
|
})
|
|
157
141
|
|
|
158
|
-
it('saves a file with multibyte chars', () => {
|
|
142
|
+
it('saves a queue file with multibyte chars', () => {
|
|
159
143
|
const todo = JSON.parse(
|
|
160
|
-
'{"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":"
|
|
144
|
+
'{"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"}',
|
|
161
145
|
)
|
|
162
146
|
const fd = fs.openSync('test/queue/multibyte', 'w')
|
|
163
|
-
const ws = new fs.WriteStream('test/queue/multibyte', {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
})
|
|
167
|
-
ws.on('close', () => {
|
|
168
|
-
assert.ok(1)
|
|
169
|
-
})
|
|
170
|
-
ws.on('error', (e) => {
|
|
171
|
-
console.error(e)
|
|
172
|
-
})
|
|
173
|
-
this.outbound.build_todo(todo, ws, () => {
|
|
147
|
+
const ws = new fs.WriteStream('test/queue/multibyte', { fd, flags: constants.WRITE_EXCL })
|
|
148
|
+
ws.on('error', (e) => console.error(e))
|
|
149
|
+
outbound.build_todo(todo, ws, () => {
|
|
174
150
|
ws.write(Buffer.from('This is the message body'))
|
|
175
|
-
fs.fsync(fd, () =>
|
|
176
|
-
ws.close()
|
|
177
|
-
})
|
|
151
|
+
fs.fsync(fd, () => ws.close())
|
|
178
152
|
})
|
|
153
|
+
assert.ok(true)
|
|
179
154
|
})
|
|
180
155
|
})
|
|
181
156
|
|
|
182
157
|
describe('timer_queue', () => {
|
|
183
|
-
|
|
158
|
+
let outbound, ob_timer_queue
|
|
159
|
+
|
|
160
|
+
beforeEach(() => {
|
|
184
161
|
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
185
|
-
|
|
162
|
+
outbound = require('../../outbound')
|
|
186
163
|
const TimerQueue = require('../../outbound/timer_queue')
|
|
187
|
-
|
|
188
|
-
done()
|
|
164
|
+
ob_timer_queue = new TimerQueue(500)
|
|
189
165
|
})
|
|
190
166
|
|
|
191
|
-
afterEach((
|
|
167
|
+
afterEach(() => {
|
|
192
168
|
delete process.env.HARAKA_TEST_DIR
|
|
193
|
-
|
|
194
|
-
done()
|
|
169
|
+
ob_timer_queue.shutdown()
|
|
195
170
|
})
|
|
196
171
|
|
|
197
172
|
it('has initial length of 0', () => {
|
|
198
|
-
assert.equal(
|
|
173
|
+
assert.equal(ob_timer_queue.length(), 0)
|
|
199
174
|
})
|
|
200
175
|
|
|
201
176
|
it('can add items', () => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
assert.equal(this.ob_timer_queue.length(), 2)
|
|
177
|
+
ob_timer_queue.add('1', 1000)
|
|
178
|
+
ob_timer_queue.add('2', 2000)
|
|
179
|
+
assert.equal(ob_timer_queue.length(), 2)
|
|
206
180
|
})
|
|
207
181
|
|
|
208
182
|
it('can drain items', () => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
assert.equal(tq_length, 2)
|
|
215
|
-
|
|
216
|
-
this.ob_timer_queue.drain()
|
|
217
|
-
tq_length = this.ob_timer_queue.length()
|
|
218
|
-
|
|
219
|
-
assert.equal(tq_length, 0)
|
|
183
|
+
ob_timer_queue.add('1', 1000)
|
|
184
|
+
ob_timer_queue.add('2', 2000)
|
|
185
|
+
ob_timer_queue.drain()
|
|
186
|
+
assert.equal(ob_timer_queue.length(), 0)
|
|
220
187
|
})
|
|
221
188
|
|
|
222
189
|
it('can discard items by id', () => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
assert.equal(tq_length, 2)
|
|
229
|
-
|
|
230
|
-
this.ob_timer_queue.discard('2')
|
|
231
|
-
tq_length = this.ob_timer_queue.length()
|
|
232
|
-
|
|
233
|
-
assert.equal(tq_length, 1)
|
|
234
|
-
assert.equal(this.ob_timer_queue.queue[0].id, '1')
|
|
190
|
+
ob_timer_queue.add('1', 1000)
|
|
191
|
+
ob_timer_queue.add('2', 2000)
|
|
192
|
+
ob_timer_queue.discard('2')
|
|
193
|
+
assert.equal(ob_timer_queue.length(), 1)
|
|
194
|
+
assert.equal(ob_timer_queue.queue[0].id, '1')
|
|
235
195
|
})
|
|
236
196
|
})
|
|
237
197
|
})
|
package/test/outbound/qfile.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
1
4
|
const assert = require('node:assert')
|
|
2
5
|
const os = require('node:os')
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
describe('qfile', () => {
|
|
6
|
-
beforeEach((done) => {
|
|
7
|
-
this.qfile = require('../../outbound/qfile')
|
|
8
|
-
done()
|
|
9
|
-
})
|
|
7
|
+
const qfile = require('../../outbound/qfile')
|
|
10
8
|
|
|
9
|
+
describe('outbound/qfile', () => {
|
|
10
|
+
describe('name', () => {
|
|
11
11
|
it('name() basic functions', () => {
|
|
12
|
-
const name =
|
|
12
|
+
const name = qfile.name()
|
|
13
13
|
const split = name.split('_')
|
|
14
14
|
assert.equal(split.length, 7)
|
|
15
15
|
assert.equal(split[2], 0)
|
|
@@ -25,7 +25,7 @@ describe('qfile', () => {
|
|
|
25
25
|
uid: 'XXYYZZ',
|
|
26
26
|
host: os.hostname(),
|
|
27
27
|
}
|
|
28
|
-
const name =
|
|
28
|
+
const name = qfile.name(overrides)
|
|
29
29
|
const split = name.split('_')
|
|
30
30
|
assert.equal(split.length, 7)
|
|
31
31
|
assert.equal(split[0], overrides.arrival)
|
|
@@ -38,9 +38,9 @@ describe('qfile', () => {
|
|
|
38
38
|
|
|
39
39
|
it('rnd_unique() is unique-ish', () => {
|
|
40
40
|
const repeats = 1000
|
|
41
|
-
const u =
|
|
41
|
+
const u = qfile.rnd_unique()
|
|
42
42
|
for (let i = 0; i < repeats; i++) {
|
|
43
|
-
assert.notEqual(u,
|
|
43
|
+
assert.notEqual(u, qfile.rnd_unique())
|
|
44
44
|
}
|
|
45
45
|
})
|
|
46
46
|
})
|
|
@@ -49,7 +49,7 @@ describe('qfile', () => {
|
|
|
49
49
|
it('parts() updates previous queue filenames', () => {
|
|
50
50
|
// $nextattempt_$attempts_$pid_$uniq.$host
|
|
51
51
|
const name = '1111_0_2222_3333.foo.example.com'
|
|
52
|
-
const parts =
|
|
52
|
+
const parts = qfile.parts(name)
|
|
53
53
|
assert.equal(parts.next_attempt, 1111)
|
|
54
54
|
assert.equal(parts.attempts, 0)
|
|
55
55
|
assert.equal(parts.pid, 2222)
|
|
@@ -65,8 +65,8 @@ describe('qfile', () => {
|
|
|
65
65
|
uid: 'XXYYZZ',
|
|
66
66
|
host: os.hostname(),
|
|
67
67
|
}
|
|
68
|
-
const name =
|
|
69
|
-
const parts =
|
|
68
|
+
const name = qfile.name(overrides)
|
|
69
|
+
const parts = qfile.parts(name)
|
|
70
70
|
assert.equal(parts.arrival, overrides.arrival)
|
|
71
71
|
assert.equal(parts.next_attempt, overrides.next_attempt)
|
|
72
72
|
assert.equal(parts.attempts, overrides.attempts)
|
|
@@ -75,8 +75,8 @@ describe('qfile', () => {
|
|
|
75
75
|
assert.equal(parts.host, overrides.host)
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
-
it('handles 4', () => {
|
|
79
|
-
const r =
|
|
78
|
+
it('handles 4-part legacy filename', () => {
|
|
79
|
+
const r = qfile.parts('1484878079415_0_12345_8888.mta1.example.com')
|
|
80
80
|
delete r.arrival
|
|
81
81
|
delete r.uid
|
|
82
82
|
delete r.counter
|
|
@@ -89,8 +89,8 @@ describe('qfile', () => {
|
|
|
89
89
|
})
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
it('handles 7', () => {
|
|
93
|
-
const r =
|
|
92
|
+
it('handles 7-part standard filename', () => {
|
|
93
|
+
const r = qfile.parts('1516650518128_1516667073032_8_29538_TkPZWz_1_haraka')
|
|
94
94
|
delete r.age
|
|
95
95
|
assert.deepEqual(r, {
|
|
96
96
|
arrival: 1516650518128,
|
|
@@ -103,22 +103,22 @@ describe('qfile', () => {
|
|
|
103
103
|
})
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
-
it('punts on 5', () => {
|
|
107
|
-
assert.deepEqual(
|
|
106
|
+
it('punts on 5-part filename', () => {
|
|
107
|
+
assert.deepEqual(qfile.parts('1516650518128_1516667073032_8_29538_TkPZWz'), null)
|
|
108
108
|
})
|
|
109
109
|
})
|
|
110
110
|
|
|
111
111
|
describe('hostname', () => {
|
|
112
|
-
it('
|
|
113
|
-
assert.deepEqual(
|
|
112
|
+
it('defaults to os.hostname()', () => {
|
|
113
|
+
assert.deepEqual(qfile.hostname(), os.hostname())
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
-
it('
|
|
117
|
-
assert.deepEqual(
|
|
116
|
+
it('replaces backslash char', () => {
|
|
117
|
+
assert.deepEqual(qfile.hostname('mt\\a1.exam\\ple.com'), 'mt\\057a1.exam\\057ple.com')
|
|
118
118
|
})
|
|
119
119
|
|
|
120
|
-
it('
|
|
121
|
-
assert.deepEqual(
|
|
120
|
+
it('replaces underscore char', () => {
|
|
121
|
+
assert.deepEqual(qfile.hostname('mt_a1.exam_ple.com'), 'mt\\137a1.exam\\137ple.com')
|
|
122
122
|
})
|
|
123
123
|
})
|
|
124
124
|
})
|
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
const os = require('node:os')
|
|
8
|
+
|
|
9
|
+
const queue = require('../../outbound/queue')
|
|
10
|
+
const qfile = require('../../outbound/qfile')
|
|
11
|
+
|
|
12
|
+
const sourceQueueDir = path.join('test', 'queue')
|
|
13
|
+
const testQueueDir = path.join('test', 'test-queue')
|
|
14
|
+
const fixtureFiles = [
|
|
15
|
+
'1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
|
|
16
|
+
'1508269674999_1508269674999_0_34002_socVUF_1_haraka',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const clearTestQueue = () => {
|
|
20
|
+
fs.mkdirSync(testQueueDir, { recursive: true })
|
|
21
|
+
for (const file of fs.readdirSync(testQueueDir)) {
|
|
22
|
+
fs.unlinkSync(path.join(testQueueDir, file))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const populateTestQueue = () => {
|
|
27
|
+
clearTestQueue()
|
|
28
|
+
for (const file of fixtureFiles) {
|
|
29
|
+
fs.copyFileSync(path.join(sourceQueueDir, file), path.join(testQueueDir, file))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('outbound/queue', () => {
|
|
34
|
+
describe('read_parts', () => {
|
|
35
|
+
it('parses valid queue filenames', () => {
|
|
36
|
+
const filename = '1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka'
|
|
37
|
+
const parts = queue.read_parts(filename)
|
|
38
|
+
assert.ok(parts)
|
|
39
|
+
assert.equal(parts.arrival, 1507509981169)
|
|
40
|
+
assert.equal(parts.next_attempt, 1507509981169)
|
|
41
|
+
assert.equal(parts.attempts, 0)
|
|
42
|
+
assert.equal(parts.pid, 61403)
|
|
43
|
+
assert.equal(parts.uid, 'e0Y0Ym')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('rejects dot files', () => {
|
|
47
|
+
assert.strictEqual(queue.read_parts('__tmp__.filename'), false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('rejects error files', () => {
|
|
51
|
+
assert.strictEqual(queue.read_parts('error.something'), false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('rejects invalid queue files', () => {
|
|
55
|
+
assert.strictEqual(queue.read_parts('invalid-file'), false)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('load_queue_files', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
populateTestQueue()
|
|
62
|
+
})
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
clearTestQueue()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('processes valid queue files', async () => {
|
|
68
|
+
const seen = []
|
|
69
|
+
|
|
70
|
+
const files = await queue.load_queue_files(
|
|
71
|
+
null,
|
|
72
|
+
['1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka'],
|
|
73
|
+
(file) => {
|
|
74
|
+
seen.push(file)
|
|
75
|
+
return file
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
assert.equal(seen.length, 1)
|
|
79
|
+
assert.equal(files[0], '1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('skips invalid files', async () => {
|
|
83
|
+
const seen = []
|
|
84
|
+
|
|
85
|
+
await queue.load_queue_files(
|
|
86
|
+
null,
|
|
87
|
+
['1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka', 'invalid-file', 'zero-length'],
|
|
88
|
+
(file) => {
|
|
89
|
+
seen.push(file)
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
assert.equal(seen.length, 1)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('filters files by pid', async () => {
|
|
96
|
+
let renameAttempts = 0
|
|
97
|
+
|
|
98
|
+
const originalRename = queue.rename_to_actual_pid
|
|
99
|
+
queue.rename_to_actual_pid = (_file, _parts) => {
|
|
100
|
+
renameAttempts++
|
|
101
|
+
throw new Error('test skip')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await queue.load_queue_files(
|
|
105
|
+
61403,
|
|
106
|
+
[
|
|
107
|
+
'1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka',
|
|
108
|
+
'1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
|
|
109
|
+
],
|
|
110
|
+
(_file) => {},
|
|
111
|
+
)
|
|
112
|
+
queue.rename_to_actual_pid = originalRename
|
|
113
|
+
assert.equal(renameAttempts, 1)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('ensure_queue_dir', () => {
|
|
118
|
+
it('creates queue dir', async () => {
|
|
119
|
+
const tmpDir = path.join(os.tmpdir(), `haraka-test-queue-${Date.now()}`)
|
|
120
|
+
|
|
121
|
+
const originalQueueDir = queue.queue_dir
|
|
122
|
+
queue.queue_dir = tmpDir
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await queue.ensure_queue_dir()
|
|
126
|
+
assert.ok(fs.existsSync(tmpDir))
|
|
127
|
+
const stat = await fs.promises.stat(tmpDir)
|
|
128
|
+
assert.ok(stat.isDirectory())
|
|
129
|
+
} catch (err) {
|
|
130
|
+
assert.fail(`ensure_queue_dir threw an error: ${err.message}`)
|
|
131
|
+
} finally {
|
|
132
|
+
queue.queue_dir = originalQueueDir
|
|
133
|
+
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true })
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('returns early if queue dir already exists', async () => {
|
|
138
|
+
const tmpDir = path.join(os.tmpdir(), `haraka-test-queue-exists-${Date.now()}`)
|
|
139
|
+
fs.mkdirSync(tmpDir)
|
|
140
|
+
|
|
141
|
+
const originalQueueDir = queue.queue_dir
|
|
142
|
+
queue.queue_dir = tmpDir
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await queue.ensure_queue_dir()
|
|
146
|
+
assert.ok(fs.existsSync(tmpDir))
|
|
147
|
+
} catch (err) {
|
|
148
|
+
assert.fail(`ensure_queue_dir threw an error: ${err.message}`)
|
|
149
|
+
} finally {
|
|
150
|
+
queue.queue_dir = originalQueueDir
|
|
151
|
+
fs.rmSync(tmpDir, { recursive: true })
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('_load_cur_queue', () => {
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
populateTestQueue()
|
|
159
|
+
})
|
|
160
|
+
afterEach(() => {
|
|
161
|
+
clearTestQueue()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('reads queue directory and processes files', async () => {
|
|
165
|
+
const processedFiles = []
|
|
166
|
+
await queue._load_cur_queue(null, (file) => {
|
|
167
|
+
processedFiles.push(file)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
assert.ok(processedFiles.length >= 0)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('list_queue', () => {
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
populateTestQueue()
|
|
177
|
+
})
|
|
178
|
+
afterEach(() => {
|
|
179
|
+
clearTestQueue()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('returns todo objects from real queue files', async () => {
|
|
183
|
+
const qlist = await queue.list_queue()
|
|
184
|
+
assert.ok(Array.isArray(qlist))
|
|
185
|
+
assert.ok(qlist.length > 0)
|
|
186
|
+
assert.ok(qlist[0].mail_from)
|
|
187
|
+
assert.ok(Array.isArray(qlist[0].rcpt_to))
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe('stat_queue', () => {
|
|
192
|
+
beforeEach(() => {
|
|
193
|
+
populateTestQueue()
|
|
194
|
+
})
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
clearTestQueue()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('returns queue stats', async () => {
|
|
200
|
+
const stats = await queue.stat_queue()
|
|
201
|
+
assert.ok(stats)
|
|
202
|
+
assert.ok('queue_dir' in stats)
|
|
203
|
+
assert.ok(stats.queue_count >= 1)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('load_pid_queue', () => {
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
populateTestQueue()
|
|
210
|
+
})
|
|
211
|
+
afterEach(() => {
|
|
212
|
+
clearTestQueue()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('delegates pid loading to init_queue', async () => {
|
|
216
|
+
const parts = qfile.parts(fixtureFiles[0])
|
|
217
|
+
const observed = []
|
|
218
|
+
const originalLoadQueue = queue.init_queue
|
|
219
|
+
|
|
220
|
+
queue.init_queue = (pid) => {
|
|
221
|
+
observed.push(pid)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
assert.ok(fs.existsSync(path.join(testQueueDir, fixtureFiles[0])))
|
|
226
|
+
await queue.load_pid_queue(parts.pid)
|
|
227
|
+
assert.deepEqual(observed, [parts.pid])
|
|
228
|
+
} finally {
|
|
229
|
+
queue.init_queue = originalLoadQueue
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
})
|