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.
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +1 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +29 -5
- package/CONTRIBUTORS.md +5 -5
- package/README.md +6 -3
- package/bin/haraka +12 -4
- package/config/connection.ini +6 -0
- package/connection.js +67 -68
- package/contrib/bsd-rc.d/haraka +2 -0
- package/docs/CoreConfig.md +2 -0
- package/docs/HAProxy.md +4 -1
- package/eslint.config.mjs +2 -30
- package/haraka.js +2 -2
- package/line_socket.js +6 -33
- package/outbound/hmail.js +18 -29
- package/outbound/index.js +3 -3
- package/outbound/queue.js +8 -5
- package/package.json +49 -46
- package/plugins/auth/auth_proxy.js +7 -4
- package/plugins/block_me.js +1 -1
- package/plugins/delay_deny.js +1 -1
- package/plugins/queue/qmail-queue.js +1 -1
- package/plugins/queue/quarantine.js +5 -5
- package/plugins/queue/smtp_bridge.js +1 -1
- package/plugins/queue/smtp_proxy.js +2 -2
- package/plugins/status.js +2 -2
- package/plugins/toobusy.js +1 -1
- package/plugins.js +4 -3
- package/server.js +172 -28
- package/smtp_client.js +2 -1
- package/test/connection.js +119 -2
- 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 +1 -1
- package/test/fixtures/util_hmailitem.js +2 -3
- package/test/outbound/index.js +6 -7
- package/test/outbound/qfile.js +1 -1
- package/test/outbound/queue.js +2 -2
- package/test/plugins/auth/auth_base.js +17 -17
- package/test/plugins/auth/auth_bridge.js +3 -3
- package/test/plugins/auth/auth_vpopmaild.js +3 -3
- package/test/plugins/auth/flat_file.js +16 -21
- package/test/plugins/block_me.js +7 -23
- package/test/plugins/data.signatures.js +17 -20
- package/test/plugins/delay_deny.js +3 -4
- package/test/plugins/prevent_credential_leaks.js +17 -21
- package/test/plugins/process_title.js +12 -6
- package/test/plugins/queue/deliver.js +7 -8
- package/test/plugins/queue/discard.js +3 -4
- package/test/plugins/queue/lmtp.js +5 -6
- package/test/plugins/queue/qmail-queue.js +7 -8
- package/test/plugins/queue/quarantine.js +3 -4
- package/test/plugins/queue/smtp_bridge.js +5 -7
- package/test/plugins/queue/smtp_forward.js +49 -60
- package/test/plugins/queue/smtp_proxy.js +6 -7
- package/test/plugins/rcpt_to.host_list_base.js +6 -9
- package/test/plugins/rcpt_to.in_host_list.js +6 -11
- package/test/plugins/record_envelope_addresses.js +33 -60
- package/test/plugins/reseed_rng.js +3 -3
- package/test/plugins/status.js +4 -5
- package/test/plugins/tarpit.js +3 -4
- package/test/plugins/tls.js +3 -5
- package/test/plugins/toobusy.js +186 -9
- package/test/plugins/xclient.js +7 -4
- package/test/server.js +425 -1
- package/test/smtp_client.js +11 -18
- package/test/tls_socket.js +3 -6
- package/tls_socket.js +3 -3
- package/transaction.js +3 -3
- package/address.js +0 -53
- package/endpoint.js +0 -96
- package/host_pool.js +0 -169
- package/outbound/fsync_writestream.js +0 -44
- package/outbound/timer_queue.js +0 -86
- package/rfc1869.js +0 -93
- package/test/endpoint.js +0 -128
- package/test/host_pool.js +0 -188
- package/test/rfc1869.js +0 -89
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
const assert = require('node:assert')
|
|
3
3
|
const { describe, it, beforeEach } = require('node:test')
|
|
4
4
|
|
|
5
|
-
const { Address } = require('
|
|
6
|
-
const
|
|
5
|
+
const { Address } = require('@haraka/email-address')
|
|
6
|
+
const { makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
7
7
|
const utils = require('haraka-utils')
|
|
8
8
|
|
|
9
9
|
const _set_up = () => {
|
|
10
|
-
this.plugin =
|
|
10
|
+
this.plugin = makePlugin('auth/auth_base', { register: false })
|
|
11
11
|
|
|
12
12
|
this.plugin.get_plain_passwd = (user, cb) => {
|
|
13
13
|
if (user === 'test') return cb('testpass')
|
|
14
14
|
return cb(null)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
this.connection =
|
|
17
|
+
this.connection = makeConnection()
|
|
18
18
|
this.connection.capabilities = null
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const _set_up_2 = () => {
|
|
22
|
-
this.plugin =
|
|
22
|
+
this.plugin = makePlugin('auth/auth_base', { register: false })
|
|
23
23
|
|
|
24
24
|
this.plugin.get_plain_passwd = (user, connection, cb) => {
|
|
25
25
|
connection.notes.auth_custom_note = 'custom_note'
|
|
@@ -27,12 +27,12 @@ const _set_up_2 = () => {
|
|
|
27
27
|
return cb(null)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
this.connection =
|
|
30
|
+
this.connection = makeConnection()
|
|
31
31
|
this.connection.capabilities = null
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const _set_up_custom_pwcb_opts = () => {
|
|
35
|
-
this.plugin =
|
|
35
|
+
this.plugin = makePlugin('auth/auth_base', { register: false })
|
|
36
36
|
|
|
37
37
|
this.plugin.check_plain_passwd = (connection, user, passwd, pwok_cb) => {
|
|
38
38
|
switch (user) {
|
|
@@ -53,7 +53,7 @@ const _set_up_custom_pwcb_opts = () => {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
this.connection =
|
|
56
|
+
this.connection = makeConnection()
|
|
57
57
|
this.connection.capabilities = null
|
|
58
58
|
this.connection.notes.resp_strings = []
|
|
59
59
|
this.connection.respond = (code, msg, cb) => {
|
|
@@ -266,7 +266,7 @@ describe('auth_base', () => {
|
|
|
266
266
|
|
|
267
267
|
it('legacyok_nomessage', (t, done) => {
|
|
268
268
|
this.plugin.check_user(
|
|
269
|
-
(code
|
|
269
|
+
(code) => {
|
|
270
270
|
assert.equal(code, OK)
|
|
271
271
|
assert.equal(this.connection.relaying, true)
|
|
272
272
|
assert.deepEqual(this.connection.notes.resp_strings, [[235, '2.7.0 Authentication successful']])
|
|
@@ -280,7 +280,7 @@ describe('auth_base', () => {
|
|
|
280
280
|
|
|
281
281
|
it('legacyfail_nomessage', (t, done) => {
|
|
282
282
|
this.plugin.check_user(
|
|
283
|
-
(code
|
|
283
|
+
(code) => {
|
|
284
284
|
assert.equal(code, OK)
|
|
285
285
|
assert.equal(this.connection.relaying, false)
|
|
286
286
|
assert.deepEqual(this.connection.notes.resp_strings, [[535, '5.7.8 Authentication failed']])
|
|
@@ -294,7 +294,7 @@ describe('auth_base', () => {
|
|
|
294
294
|
|
|
295
295
|
it('legacyok_message', (t, done) => {
|
|
296
296
|
this.plugin.check_user(
|
|
297
|
-
(code
|
|
297
|
+
(code) => {
|
|
298
298
|
assert.equal(code, OK)
|
|
299
299
|
assert.equal(this.connection.relaying, true)
|
|
300
300
|
assert.deepEqual(this.connection.notes.resp_strings, [[235, 'GREAT SUCCESS']])
|
|
@@ -308,7 +308,7 @@ describe('auth_base', () => {
|
|
|
308
308
|
|
|
309
309
|
it('legacyfail_message', (t, done) => {
|
|
310
310
|
this.plugin.check_user(
|
|
311
|
-
(code
|
|
311
|
+
(code) => {
|
|
312
312
|
assert.equal(code, OK)
|
|
313
313
|
assert.equal(this.connection.relaying, false)
|
|
314
314
|
assert.deepEqual(this.connection.notes.resp_strings, [[535, 'FAIL 123']])
|
|
@@ -322,7 +322,7 @@ describe('auth_base', () => {
|
|
|
322
322
|
|
|
323
323
|
it('newok', (t, done) => {
|
|
324
324
|
this.plugin.check_user(
|
|
325
|
-
(code
|
|
325
|
+
(code) => {
|
|
326
326
|
assert.equal(code, OK)
|
|
327
327
|
assert.equal(this.connection.relaying, true)
|
|
328
328
|
assert.deepEqual(this.connection.notes.resp_strings, [[215, 'KOKOKO']])
|
|
@@ -336,7 +336,7 @@ describe('auth_base', () => {
|
|
|
336
336
|
|
|
337
337
|
it('newfail', (t, done) => {
|
|
338
338
|
this.plugin.check_user(
|
|
339
|
-
(code
|
|
339
|
+
(code) => {
|
|
340
340
|
assert.equal(code, OK)
|
|
341
341
|
assert.equal(this.connection.relaying, false)
|
|
342
342
|
assert.deepEqual(this.connection.notes.resp_strings, [[555, 'OHOHOH']])
|
|
@@ -355,7 +355,7 @@ describe('auth_base', () => {
|
|
|
355
355
|
it('bad auth: no notes should be set', (t, done) => {
|
|
356
356
|
const credentials = ['matt', 'ttam']
|
|
357
357
|
this.plugin.check_user(
|
|
358
|
-
(
|
|
358
|
+
() => {
|
|
359
359
|
assert.equal(this.connection.notes.auth_user, undefined)
|
|
360
360
|
assert.equal(this.connection.notes.auth_passwd, undefined)
|
|
361
361
|
done()
|
|
@@ -370,7 +370,7 @@ describe('auth_base', () => {
|
|
|
370
370
|
const creds = ['test', 'testpass']
|
|
371
371
|
this.plugin.blankout_password = true
|
|
372
372
|
this.plugin.check_user(
|
|
373
|
-
(
|
|
373
|
+
() => {
|
|
374
374
|
assert.equal(this.connection.notes.auth_user, creds[0])
|
|
375
375
|
assert.equal(this.connection.notes.auth_passwd, undefined)
|
|
376
376
|
done()
|
|
@@ -384,7 +384,7 @@ describe('auth_base', () => {
|
|
|
384
384
|
it('good auth: store password (default)', (t, done) => {
|
|
385
385
|
const creds = ['test', 'testpass']
|
|
386
386
|
this.plugin.check_user(
|
|
387
|
-
(
|
|
387
|
+
() => {
|
|
388
388
|
assert.equal(this.connection.notes.auth_user, creds[0])
|
|
389
389
|
assert.equal(this.connection.notes.auth_passwd, creds[1])
|
|
390
390
|
done()
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
4
|
const { describe, it, beforeEach } = require('node:test')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const { makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
7
7
|
|
|
8
8
|
describe('auth/auth_bridge', () => {
|
|
9
9
|
let plugin
|
|
10
10
|
|
|
11
11
|
beforeEach(() => {
|
|
12
|
-
plugin =
|
|
12
|
+
plugin = makePlugin('auth/auth_bridge', { register: false })
|
|
13
13
|
plugin.load_flat_ini()
|
|
14
14
|
})
|
|
15
15
|
|
|
@@ -28,7 +28,7 @@ describe('auth/auth_bridge', () => {
|
|
|
28
28
|
let conn
|
|
29
29
|
|
|
30
30
|
beforeEach(() => {
|
|
31
|
-
conn =
|
|
31
|
+
conn = makeConnection()
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
it('calls try_auth_proxy with just host when no port configured', (t, done) => {
|
|
@@ -4,19 +4,19 @@ const assert = require('node:assert/strict')
|
|
|
4
4
|
const path = require('node:path')
|
|
5
5
|
const { describe, it, beforeEach } = require('node:test')
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const { makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
8
8
|
|
|
9
9
|
const _set_up = () => {
|
|
10
10
|
this.backup = {}
|
|
11
11
|
|
|
12
|
-
this.plugin =
|
|
12
|
+
this.plugin = makePlugin('auth/auth_vpopmaild', { register: false })
|
|
13
13
|
this.plugin.inherits('auth/auth_base')
|
|
14
14
|
|
|
15
15
|
// reset the config/root_path
|
|
16
16
|
this.plugin.config.root_path = path.resolve(__dirname, '../../../config')
|
|
17
17
|
this.plugin.cfg = this.plugin.config.get('auth_vpopmaild.ini')
|
|
18
18
|
|
|
19
|
-
this.connection =
|
|
19
|
+
this.connection = makeConnection()
|
|
20
20
|
this.connection.capabilities = null
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -3,22 +3,13 @@
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
4
|
const { describe, it, beforeEach } = require('node:test')
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
function makeConnection(opts = {}) {
|
|
9
|
-
const conn = fixtures.connection.createConnection()
|
|
10
|
-
conn.capabilities = []
|
|
11
|
-
conn.notes.allowed_auth_methods = []
|
|
12
|
-
conn.remote = { is_private: opts.is_private ?? false }
|
|
13
|
-
conn.tls = { enabled: opts.tls_enabled ?? false }
|
|
14
|
-
return conn
|
|
15
|
-
}
|
|
6
|
+
const { callHook, makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
16
7
|
|
|
17
8
|
describe('auth/flat_file', () => {
|
|
18
9
|
let plugin
|
|
19
10
|
|
|
20
11
|
beforeEach(() => {
|
|
21
|
-
plugin =
|
|
12
|
+
plugin = makePlugin('auth/flat_file', { register: false })
|
|
22
13
|
plugin.inherits('auth/auth_base')
|
|
23
14
|
plugin.load_flat_ini()
|
|
24
15
|
})
|
|
@@ -39,55 +30,59 @@ describe('auth/flat_file', () => {
|
|
|
39
30
|
|
|
40
31
|
beforeEach(() => {
|
|
41
32
|
conn = makeConnection()
|
|
33
|
+
conn.capabilities = []
|
|
34
|
+
conn.notes.allowed_auth_methods = []
|
|
35
|
+
conn.remote.is_private = false
|
|
36
|
+
conn.tls.enabled = false
|
|
42
37
|
})
|
|
43
38
|
|
|
44
39
|
it('skips for public non-TLS connection', (t, done) => {
|
|
45
|
-
plugin.
|
|
40
|
+
callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
|
|
46
41
|
assert.equal(rc, undefined)
|
|
47
42
|
assert.equal(conn.capabilities.length, 0)
|
|
48
43
|
done()
|
|
49
|
-
}
|
|
44
|
+
})
|
|
50
45
|
})
|
|
51
46
|
|
|
52
47
|
it('adds AUTH methods for private connection (non-TLS)', (t, done) => {
|
|
53
48
|
conn.remote.is_private = true
|
|
54
49
|
plugin.cfg.core.methods = 'PLAIN,LOGIN'
|
|
55
|
-
plugin.
|
|
50
|
+
callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
|
|
56
51
|
assert.equal(rc, undefined)
|
|
57
52
|
assert.ok(
|
|
58
53
|
conn.capabilities.some((c) => c.startsWith('AUTH ')),
|
|
59
54
|
'AUTH capability should be present',
|
|
60
55
|
)
|
|
61
56
|
done()
|
|
62
|
-
}
|
|
57
|
+
})
|
|
63
58
|
})
|
|
64
59
|
|
|
65
60
|
it('adds AUTH methods when TLS is enabled', (t, done) => {
|
|
66
61
|
conn.tls.enabled = true
|
|
67
62
|
plugin.cfg.core.methods = 'PLAIN,LOGIN'
|
|
68
|
-
plugin.
|
|
63
|
+
callHook(plugin, 'hook_capabilities', conn).then(({ rc }) => {
|
|
69
64
|
assert.equal(rc, undefined)
|
|
70
65
|
assert.ok(conn.capabilities.some((c) => c.startsWith('AUTH ')))
|
|
71
66
|
done()
|
|
72
|
-
}
|
|
67
|
+
})
|
|
73
68
|
})
|
|
74
69
|
|
|
75
70
|
it('sets allowed_auth_methods on connection notes', (t, done) => {
|
|
76
71
|
conn.tls.enabled = true
|
|
77
72
|
plugin.cfg.core.methods = 'PLAIN,LOGIN'
|
|
78
|
-
plugin.
|
|
73
|
+
callHook(plugin, 'hook_capabilities', conn).then(() => {
|
|
79
74
|
assert.deepEqual(conn.notes.allowed_auth_methods, ['PLAIN', 'LOGIN'])
|
|
80
75
|
done()
|
|
81
|
-
}
|
|
76
|
+
})
|
|
82
77
|
})
|
|
83
78
|
|
|
84
79
|
it('does not add AUTH when no methods configured', (t, done) => {
|
|
85
80
|
conn.tls.enabled = true
|
|
86
81
|
plugin.cfg.core.methods = null
|
|
87
|
-
plugin.
|
|
82
|
+
callHook(plugin, 'hook_capabilities', conn).then(() => {
|
|
88
83
|
assert.equal(conn.capabilities.length, 0)
|
|
89
84
|
done()
|
|
90
|
-
}
|
|
85
|
+
})
|
|
91
86
|
})
|
|
92
87
|
})
|
|
93
88
|
|
package/test/plugins/block_me.js
CHANGED
|
@@ -5,8 +5,7 @@ const path = require('node:path')
|
|
|
5
5
|
const assert = require('node:assert/strict')
|
|
6
6
|
const { describe, it, beforeEach, after } = require('node:test')
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const { Address } = require('@haraka/email-address')
|
|
8
|
+
const { makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
10
9
|
require('haraka-constants').import(global)
|
|
11
10
|
|
|
12
11
|
// block_me appends to <config>/mail_from.blocklist when a sender is blocked;
|
|
@@ -15,20 +14,6 @@ after(() => {
|
|
|
15
14
|
fs.rmSync(path.resolve('test/config/mail_from.blocklist'), { force: true })
|
|
16
15
|
})
|
|
17
16
|
|
|
18
|
-
function makeConnection({
|
|
19
|
-
relaying = false,
|
|
20
|
-
mailFrom = 'sender@example.com',
|
|
21
|
-
rcptTo = ['blocklist@example.com'],
|
|
22
|
-
} = {}) {
|
|
23
|
-
const conn = fixtures.connection.createConnection()
|
|
24
|
-
conn.init_transaction()
|
|
25
|
-
conn.relaying = relaying
|
|
26
|
-
conn.transaction.mail_from = new Address(`<${mailFrom}>`)
|
|
27
|
-
conn.transaction.rcpt_to = rcptTo.map((r) => new Address(`<${r}>`))
|
|
28
|
-
conn.transaction.body = { bodytext: '', children: [] }
|
|
29
|
-
return conn
|
|
30
|
-
}
|
|
31
|
-
|
|
32
17
|
describe('block_me', () => {
|
|
33
18
|
let plugin
|
|
34
19
|
|
|
@@ -36,13 +21,12 @@ describe('block_me', () => {
|
|
|
36
21
|
// than the real config dir. block_me also appends matched senders to
|
|
37
22
|
// mail_from.blocklist; with this override that write lands in test/config too.
|
|
38
23
|
beforeEach(() => {
|
|
39
|
-
plugin =
|
|
40
|
-
plugin.config = plugin.config.module_config(path.resolve('test'))
|
|
24
|
+
plugin = makePlugin('block_me', { register: false, configDir: 'test' })
|
|
41
25
|
})
|
|
42
26
|
|
|
43
27
|
describe('hook_data', () => {
|
|
44
28
|
it('enables body parsing and calls next', (t, done) => {
|
|
45
|
-
const conn = makeConnection()
|
|
29
|
+
const conn = makeConnection({ withTxn: true })
|
|
46
30
|
conn.transaction.parse_body = false
|
|
47
31
|
plugin.hook_data((rc) => {
|
|
48
32
|
assert.equal(rc, undefined)
|
|
@@ -54,7 +38,7 @@ describe('block_me', () => {
|
|
|
54
38
|
|
|
55
39
|
describe('hook_data_post', () => {
|
|
56
40
|
it('calls next when not relaying', (t, done) => {
|
|
57
|
-
const conn = makeConnection({
|
|
41
|
+
const conn = makeConnection({ withTxn: true })
|
|
58
42
|
plugin.hook_data_post((rc) => {
|
|
59
43
|
assert.equal(rc, undefined)
|
|
60
44
|
done()
|
|
@@ -62,7 +46,7 @@ describe('block_me', () => {
|
|
|
62
46
|
})
|
|
63
47
|
|
|
64
48
|
it('calls next when transaction is missing', (t, done) => {
|
|
65
|
-
const conn =
|
|
49
|
+
const conn = makeConnection()
|
|
66
50
|
conn.relaying = true
|
|
67
51
|
conn.transaction = null
|
|
68
52
|
plugin.hook_data_post((rc) => {
|
|
@@ -138,7 +122,7 @@ describe('block_me', () => {
|
|
|
138
122
|
|
|
139
123
|
describe('hook_queue', () => {
|
|
140
124
|
it('returns OK when block_me note is set on transaction', (t, done) => {
|
|
141
|
-
const conn = makeConnection()
|
|
125
|
+
const conn = makeConnection({ withTxn: true })
|
|
142
126
|
conn.transaction.notes.block_me = 1
|
|
143
127
|
plugin.hook_queue((rc) => {
|
|
144
128
|
assert.equal(rc, OK)
|
|
@@ -147,7 +131,7 @@ describe('block_me', () => {
|
|
|
147
131
|
})
|
|
148
132
|
|
|
149
133
|
it('calls next when block_me note is not set', (t, done) => {
|
|
150
|
-
const conn = makeConnection()
|
|
134
|
+
const conn = makeConnection({ withTxn: true })
|
|
151
135
|
plugin.hook_queue((rc) => {
|
|
152
136
|
assert.equal(rc, undefined)
|
|
153
137
|
done()
|
|
@@ -3,26 +3,19 @@
|
|
|
3
3
|
const assert = require('node:assert/strict')
|
|
4
4
|
const { describe, it, beforeEach } = require('node:test')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const { makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
7
7
|
require('haraka-constants').import(global)
|
|
8
8
|
|
|
9
|
-
function makeConnection(bodytext = '', children = []) {
|
|
10
|
-
const conn = fixtures.connection.createConnection()
|
|
11
|
-
conn.init_transaction()
|
|
12
|
-
conn.transaction.body = { bodytext, children }
|
|
13
|
-
return conn
|
|
14
|
-
}
|
|
15
|
-
|
|
16
9
|
describe('data.signatures', () => {
|
|
17
10
|
let plugin
|
|
18
11
|
|
|
19
12
|
beforeEach(() => {
|
|
20
|
-
plugin =
|
|
13
|
+
plugin = makePlugin('data.signatures', { register: false })
|
|
21
14
|
})
|
|
22
15
|
|
|
23
16
|
describe('hook_data', () => {
|
|
24
17
|
it('enables body parsing', (t, done) => {
|
|
25
|
-
const conn = makeConnection()
|
|
18
|
+
const conn = makeConnection({ withTxn: true })
|
|
26
19
|
conn.transaction.parse_body = false
|
|
27
20
|
plugin.hook_data((rc) => {
|
|
28
21
|
assert.equal(rc, undefined)
|
|
@@ -32,7 +25,7 @@ describe('data.signatures', () => {
|
|
|
32
25
|
})
|
|
33
26
|
|
|
34
27
|
it('calls next when there is no transaction', (t, done) => {
|
|
35
|
-
const conn =
|
|
28
|
+
const conn = makeConnection()
|
|
36
29
|
conn.transaction = null
|
|
37
30
|
plugin.hook_data((rc) => {
|
|
38
31
|
assert.equal(rc, undefined)
|
|
@@ -43,7 +36,7 @@ describe('data.signatures', () => {
|
|
|
43
36
|
|
|
44
37
|
describe('hook_data_post', () => {
|
|
45
38
|
it('calls next when there is no transaction', (t, done) => {
|
|
46
|
-
const conn =
|
|
39
|
+
const conn = makeConnection()
|
|
47
40
|
conn.transaction = null
|
|
48
41
|
plugin.hook_data_post((rc) => {
|
|
49
42
|
assert.equal(rc, undefined)
|
|
@@ -53,7 +46,8 @@ describe('data.signatures', () => {
|
|
|
53
46
|
|
|
54
47
|
it('calls next when signature list is empty', (t, done) => {
|
|
55
48
|
plugin.config.get = (name, type) => (type === 'list' ? [] : {})
|
|
56
|
-
const conn = makeConnection(
|
|
49
|
+
const conn = makeConnection({ withTxn: true })
|
|
50
|
+
conn.transaction.body = { bodytext: 'This is some email body text', children: [] }
|
|
57
51
|
plugin.hook_data_post((rc) => {
|
|
58
52
|
assert.equal(rc, undefined)
|
|
59
53
|
done()
|
|
@@ -62,7 +56,8 @@ describe('data.signatures', () => {
|
|
|
62
56
|
|
|
63
57
|
it('denies when body matches a signature', (t, done) => {
|
|
64
58
|
plugin.config.get = (name, type) => (type === 'list' ? ['spam_signature_text'] : {})
|
|
65
|
-
const conn = makeConnection(
|
|
59
|
+
const conn = makeConnection({ withTxn: true })
|
|
60
|
+
conn.transaction.body = { bodytext: 'Buy cheap meds! spam_signature_text here', children: [] }
|
|
66
61
|
plugin.hook_data_post((rc, msg) => {
|
|
67
62
|
assert.equal(rc, DENY)
|
|
68
63
|
assert.ok(msg.includes('spam'))
|
|
@@ -72,7 +67,8 @@ describe('data.signatures', () => {
|
|
|
72
67
|
|
|
73
68
|
it('calls next when body does not match any signature', (t, done) => {
|
|
74
69
|
plugin.config.get = (name, type) => (type === 'list' ? ['bad_pattern'] : {})
|
|
75
|
-
const conn = makeConnection(
|
|
70
|
+
const conn = makeConnection({ withTxn: true })
|
|
71
|
+
conn.transaction.body = { bodytext: 'Totally normal email body', children: [] }
|
|
76
72
|
plugin.hook_data_post((rc) => {
|
|
77
73
|
assert.equal(rc, undefined)
|
|
78
74
|
done()
|
|
@@ -81,13 +77,12 @@ describe('data.signatures', () => {
|
|
|
81
77
|
|
|
82
78
|
it('denies when a child body part matches a signature', (t, done) => {
|
|
83
79
|
plugin.config.get = (name, type) => (type === 'list' ? ['spam_in_child'] : {})
|
|
84
|
-
const conn =
|
|
85
|
-
conn.init_transaction()
|
|
80
|
+
const conn = makeConnection({ withTxn: true })
|
|
86
81
|
conn.transaction.body = {
|
|
87
82
|
bodytext: 'clean parent text',
|
|
88
83
|
children: [{ bodytext: 'spam_in_child content here', children: [] }],
|
|
89
84
|
}
|
|
90
|
-
plugin.hook_data_post((rc
|
|
85
|
+
plugin.hook_data_post((rc) => {
|
|
91
86
|
assert.equal(rc, DENY)
|
|
92
87
|
done()
|
|
93
88
|
}, conn)
|
|
@@ -95,7 +90,8 @@ describe('data.signatures', () => {
|
|
|
95
90
|
|
|
96
91
|
it('calls next when multiple signatures do not match', (t, done) => {
|
|
97
92
|
plugin.config.get = (name, type) => (type === 'list' ? ['sig_one', 'sig_two', 'sig_three'] : {})
|
|
98
|
-
const conn = makeConnection(
|
|
93
|
+
const conn = makeConnection({ withTxn: true })
|
|
94
|
+
conn.transaction.body = { bodytext: 'No matching signatures here at all', children: [] }
|
|
99
95
|
plugin.hook_data_post((rc) => {
|
|
100
96
|
assert.equal(rc, undefined)
|
|
101
97
|
done()
|
|
@@ -104,7 +100,8 @@ describe('data.signatures', () => {
|
|
|
104
100
|
|
|
105
101
|
it('matches the first of multiple signatures', (t, done) => {
|
|
106
102
|
plugin.config.get = (name, type) => (type === 'list' ? ['no_match', 'buy_cheap_pills'] : {})
|
|
107
|
-
const conn = makeConnection(
|
|
103
|
+
const conn = makeConnection({ withTxn: true })
|
|
104
|
+
conn.transaction.body = { bodytext: 'This message has buy_cheap_pills for you', children: [] }
|
|
108
105
|
plugin.hook_data_post((rc) => {
|
|
109
106
|
assert.equal(rc, DENY)
|
|
110
107
|
done()
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const assert = require('node:assert/strict')
|
|
4
4
|
const { describe, it, beforeEach } = require('node:test')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const { makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
7
7
|
require('haraka-constants').import(global)
|
|
8
8
|
|
|
9
9
|
// params layout: [code, msg, pi_name, pi_function, pi_params, pi_hook]
|
|
@@ -15,10 +15,9 @@ describe('delay_deny', () => {
|
|
|
15
15
|
let plugin, conn
|
|
16
16
|
|
|
17
17
|
beforeEach(() => {
|
|
18
|
-
plugin =
|
|
18
|
+
plugin = makePlugin('delay_deny', { register: false })
|
|
19
19
|
plugin.config.get = () => ({ main: {} })
|
|
20
|
-
conn =
|
|
21
|
-
conn.init_transaction()
|
|
20
|
+
conn = makeConnection({ withTxn: true })
|
|
22
21
|
})
|
|
23
22
|
|
|
24
23
|
describe('hook_deny', () => {
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
const assert = require('node:assert/strict')
|
|
4
4
|
const { describe, it, beforeEach } = require('node:test')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const { makeConnection, makePlugin } = require('haraka-test-fixtures')
|
|
7
7
|
require('haraka-constants').import(global)
|
|
8
8
|
|
|
9
|
-
function
|
|
10
|
-
const conn =
|
|
11
|
-
conn.init_transaction()
|
|
9
|
+
function buildConnection({ authUser, authPasswd, bodytext = '', children = [] } = {}) {
|
|
10
|
+
const conn = makeConnection({ withTxn: true })
|
|
12
11
|
if (authUser) conn.notes.auth_user = authUser
|
|
13
12
|
if (authPasswd) conn.notes.auth_passwd = authPasswd
|
|
14
13
|
conn.transaction.body = { bodytext, children }
|
|
@@ -19,12 +18,12 @@ describe('prevent_credential_leaks', () => {
|
|
|
19
18
|
let plugin
|
|
20
19
|
|
|
21
20
|
beforeEach(() => {
|
|
22
|
-
plugin =
|
|
21
|
+
plugin = makePlugin('prevent_credential_leaks', { register: false })
|
|
23
22
|
})
|
|
24
23
|
|
|
25
24
|
describe('hook_data', () => {
|
|
26
25
|
it('does not enable parse_body when no auth credentials', (t, done) => {
|
|
27
|
-
const conn =
|
|
26
|
+
const conn = buildConnection()
|
|
28
27
|
conn.transaction.parse_body = false
|
|
29
28
|
plugin.hook_data((rc) => {
|
|
30
29
|
assert.equal(rc, undefined)
|
|
@@ -34,7 +33,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
34
33
|
})
|
|
35
34
|
|
|
36
35
|
it('enables parse_body when both auth_user and auth_passwd are present', (t, done) => {
|
|
37
|
-
const conn =
|
|
36
|
+
const conn = buildConnection({ authUser: 'user@example.com', authPasswd: 'secret' })
|
|
38
37
|
conn.transaction.parse_body = false
|
|
39
38
|
plugin.hook_data((rc) => {
|
|
40
39
|
assert.equal(rc, undefined)
|
|
@@ -44,7 +43,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
44
43
|
})
|
|
45
44
|
|
|
46
45
|
it('does not enable parse_body when only auth_user is set', (t, done) => {
|
|
47
|
-
const conn =
|
|
46
|
+
const conn = buildConnection({ authUser: 'user@example.com' })
|
|
48
47
|
conn.transaction.parse_body = false
|
|
49
48
|
plugin.hook_data((rc) => {
|
|
50
49
|
assert.equal(rc, undefined)
|
|
@@ -55,8 +54,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
55
54
|
|
|
56
55
|
it('handles missing connection gracefully', (t, done) => {
|
|
57
56
|
// Simulate a null-ish connection by calling with empty notes
|
|
58
|
-
const conn =
|
|
59
|
-
conn.init_transaction()
|
|
57
|
+
const conn = makeConnection({ withTxn: true })
|
|
60
58
|
conn.notes = {}
|
|
61
59
|
plugin.hook_data((rc) => {
|
|
62
60
|
assert.equal(rc, undefined)
|
|
@@ -67,7 +65,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
67
65
|
|
|
68
66
|
describe('hook_data_post', () => {
|
|
69
67
|
it('calls next when no auth credentials are set', (t, done) => {
|
|
70
|
-
const conn =
|
|
68
|
+
const conn = buildConnection({ bodytext: 'user@example.com secret123' })
|
|
71
69
|
plugin.hook_data_post((rc) => {
|
|
72
70
|
assert.equal(rc, undefined)
|
|
73
71
|
done()
|
|
@@ -75,7 +73,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
75
73
|
})
|
|
76
74
|
|
|
77
75
|
it('calls next when only auth_user is set (no password)', (t, done) => {
|
|
78
|
-
const conn =
|
|
76
|
+
const conn = buildConnection({ authUser: 'user@example.com', bodytext: 'user@example.com' })
|
|
79
77
|
plugin.hook_data_post((rc) => {
|
|
80
78
|
assert.equal(rc, undefined)
|
|
81
79
|
done()
|
|
@@ -83,7 +81,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
83
81
|
})
|
|
84
82
|
|
|
85
83
|
it('calls next when body contains neither username nor password', (t, done) => {
|
|
86
|
-
const conn =
|
|
84
|
+
const conn = buildConnection({
|
|
87
85
|
authUser: 'alice@example.com',
|
|
88
86
|
authPasswd: 'mypassword',
|
|
89
87
|
bodytext: 'Hello, this is a clean email with no credentials.',
|
|
@@ -95,7 +93,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
95
93
|
})
|
|
96
94
|
|
|
97
95
|
it('calls next when body contains username but not password', (t, done) => {
|
|
98
|
-
const conn =
|
|
96
|
+
const conn = buildConnection({
|
|
99
97
|
authUser: 'alice@example.com',
|
|
100
98
|
authPasswd: 'mypassword',
|
|
101
99
|
bodytext: 'Contact alice@example.com for more info.',
|
|
@@ -107,7 +105,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
107
105
|
})
|
|
108
106
|
|
|
109
107
|
it('denies when body contains both username and password', (t, done) => {
|
|
110
|
-
const conn =
|
|
108
|
+
const conn = buildConnection({
|
|
111
109
|
authUser: 'alice@example.com',
|
|
112
110
|
authPasswd: 'mypassword',
|
|
113
111
|
bodytext: 'Please send your login: alice and password: mypassword to activate.',
|
|
@@ -120,8 +118,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
120
118
|
})
|
|
121
119
|
|
|
122
120
|
it('denies when credentials appear in a child body part', (t, done) => {
|
|
123
|
-
const conn =
|
|
124
|
-
conn.init_transaction()
|
|
121
|
+
const conn = makeConnection({ withTxn: true })
|
|
125
122
|
conn.notes.auth_user = 'bob@example.com'
|
|
126
123
|
conn.notes.auth_passwd = 's3cr3t'
|
|
127
124
|
conn.transaction.body = {
|
|
@@ -135,7 +132,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
135
132
|
})
|
|
136
133
|
|
|
137
134
|
it('handles qualified username (user@domain) by making domain optional', (t, done) => {
|
|
138
|
-
const conn =
|
|
135
|
+
const conn = buildConnection({
|
|
139
136
|
authUser: 'carol@corp.example.com',
|
|
140
137
|
authPasswd: 'pass123',
|
|
141
138
|
bodytext: 'carol pass123 credentials',
|
|
@@ -149,7 +146,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
149
146
|
it('unqualified username (no @) is not split into a partial match', (t, done) => {
|
|
150
147
|
// Bug: `if (idx)` with idx === -1 treated 'admin' as qualified,
|
|
151
148
|
// splitting it to user='admi' which then matches 'admiral'.
|
|
152
|
-
const conn =
|
|
149
|
+
const conn = buildConnection({
|
|
153
150
|
authUser: 'admin',
|
|
154
151
|
authPasswd: 'pw',
|
|
155
152
|
bodytext: 'the admiral said pw today',
|
|
@@ -161,8 +158,7 @@ describe('prevent_credential_leaks', () => {
|
|
|
161
158
|
})
|
|
162
159
|
|
|
163
160
|
it('calls next when credentials appear in neither top nor child', (t, done) => {
|
|
164
|
-
const conn =
|
|
165
|
-
conn.init_transaction()
|
|
161
|
+
const conn = makeConnection({ withTxn: true })
|
|
166
162
|
conn.notes.auth_user = 'dave@example.com'
|
|
167
163
|
conn.notes.auth_passwd = 'xyzzy'
|
|
168
164
|
conn.transaction.body = {
|