Haraka 3.1.4 → 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.
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const assert = require('node:assert')
3
+ const assert = require('node:assert/strict')
4
+ const { describe, it, beforeEach } = require('node:test')
4
5
 
5
6
  const fixtures = require('haraka-test-fixtures')
6
7
  const outbound = require('../../outbound')
@@ -8,13 +9,12 @@ const TimerQueue = require('../../outbound/timer_queue')
8
9
 
9
10
  const Connection = fixtures.connection
10
11
 
11
- const _set_up = (done) => {
12
+ const _set_up = () => {
12
13
  this.plugin = new fixtures.plugin('status')
13
14
  this.plugin.outbound = outbound
14
15
 
15
16
  this.connection = Connection.createConnection()
16
17
  this.connection.remote.is_local = true
17
- done()
18
18
  }
19
19
 
20
20
  describe('status', () => {
@@ -29,7 +29,7 @@ describe('status', () => {
29
29
  describe('access', () => {
30
30
  beforeEach(_set_up)
31
31
 
32
- it('remote', (done) => {
32
+ it('remote', (t, done) => {
33
33
  this.connection.remote.is_local = false
34
34
  this.plugin.hook_unrecognized_command(
35
35
  (code) => {
@@ -45,7 +45,7 @@ describe('status', () => {
45
45
  describe('pools', () => {
46
46
  beforeEach(_set_up)
47
47
 
48
- it('list_pools', (done) => {
48
+ it('list_pools', (t, done) => {
49
49
  this.connection.respond = (code, message) => {
50
50
  const data = JSON.parse(message)
51
51
  assert.equal('object', typeof data) // there should be one pools array for noncluster and more for cluster
@@ -58,7 +58,7 @@ describe('status', () => {
58
58
  describe('queues', () => {
59
59
  beforeEach(_set_up)
60
60
 
61
- it('inspect_queue', (done) => {
61
+ it('inspect_queue', (t, done) => {
62
62
  // should list delivery_queue and temp_fail_queue per cluster children
63
63
  outbound.temp_fail_queue = new TimerQueue(10)
64
64
  outbound.temp_fail_queue.add('file1', 100, () => {})
@@ -73,7 +73,7 @@ describe('status', () => {
73
73
  this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE INSPECT'])
74
74
  })
75
75
 
76
- it('stat_queue', (done) => {
76
+ it('stat_queue', (t, done) => {
77
77
  // should list files only
78
78
  this.connection.respond = (code, message) => {
79
79
  const data = JSON.parse(message)
@@ -83,7 +83,7 @@ describe('status', () => {
83
83
  this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE STATS'])
84
84
  })
85
85
 
86
- it('list_queue', (done) => {
86
+ it('list_queue', (t, done) => {
87
87
  // should list files only
88
88
  this.connection.respond = (code, message) => {
89
89
  const data = JSON.parse(message)
@@ -93,7 +93,7 @@ describe('status', () => {
93
93
  this.plugin.hook_unrecognized_command(() => {}, this.connection, ['STATUS', 'QUEUE LIST'])
94
94
  })
95
95
 
96
- it('discard_from_queue', (done) => {
96
+ it('discard_from_queue', (t, done) => {
97
97
  const self = this
98
98
 
99
99
  outbound.temp_fail_queue = new TimerQueue(10)
@@ -118,7 +118,7 @@ describe('status', () => {
118
118
  )
119
119
  })
120
120
 
121
- it('push_email_at_queue', (done) => {
121
+ it('push_email_at_queue', (t, done) => {
122
122
  const timeout = setTimeout(() => {
123
123
  assert.ok(false, 'Timeout')
124
124
  done()
@@ -1,12 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const assert = require('node:assert')
3
+ const assert = require('node:assert/strict')
4
4
  const path = require('node:path')
5
+ const { describe, it, beforeEach } = require('node:test')
5
6
 
6
7
  const fixtures = require('haraka-test-fixtures')
7
8
  const Plugin = fixtures.plugin
8
9
 
9
- const _set_up = (done) => {
10
+ const _set_up = () => {
10
11
  this.plugin = new Plugin('tls')
11
12
  this.connection = new fixtures.connection.createConnection()
12
13
 
@@ -15,28 +16,17 @@ const _set_up = (done) => {
15
16
  this.plugin.net_utils.config = this.plugin.net_utils.config.module_config(path.resolve('test'))
16
17
 
17
18
  this.plugin.tls_opts = {}
18
- done()
19
19
  }
20
20
 
21
21
  describe('tls', () => {
22
22
  beforeEach(_set_up)
23
23
 
24
- it('has function register', () => {
25
- assert.ok(this.plugin)
26
- assert.equal('function', typeof this.plugin.register)
27
- })
28
-
29
- it('has function upgrade_connection', () => {
30
- assert.equal('function', typeof this.plugin.upgrade_connection)
31
- })
32
-
33
- it('has function advertise_starttls', () => {
34
- assert.equal('function', typeof this.plugin.advertise_starttls)
35
- })
36
-
37
- it('has function emit_upgrade_msg', () => {
38
- assert.equal('function', typeof this.plugin.emit_upgrade_msg)
39
- })
24
+ const methods = ['register', 'upgrade_connection', 'advertise_starttls', 'emit_upgrade_msg']
25
+ for (const method of methods) {
26
+ it(`has function ${method}`, () => {
27
+ assert.equal(typeof this.plugin[method], 'function')
28
+ })
29
+ }
40
30
 
41
31
  describe('register', () => {
42
32
  it('with certs, should register hooks', () => {
@@ -1,137 +1,102 @@
1
1
  'use strict'
2
2
 
3
- const assert = require('node:assert')
3
+ const assert = require('node:assert/strict')
4
+ const { describe, it, beforeEach } = require('node:test')
4
5
 
5
6
  const fixtures = require('haraka-test-fixtures')
6
7
 
7
- const _set_up = (done) => {
8
+ const _set_up = () => {
8
9
  this.plugin = new fixtures.plugin('xclient')
9
10
  this.connection = fixtures.connection.createConnection()
10
11
  this.connection.capabilities = []
11
- done()
12
12
  }
13
13
 
14
14
  describe('xclient', () => {
15
15
  beforeEach(_set_up)
16
16
 
17
17
  describe('hook_capabilities', () => {
18
- it('adds XCLIENT capability for allowed IP (127.0.0.1)', (done) => {
19
- this.connection.remote.ip = '127.0.0.1'
20
- this.plugin.hook_capabilities(() => {
21
- assert.ok(
22
- this.connection.capabilities.some((c) => c.startsWith('XCLIENT')),
23
- 'XCLIENT capability added',
24
- )
25
- done()
26
- }, this.connection)
27
- })
18
+ const cases = [
19
+ { desc: 'adds XCLIENT for loopback IPv4 (127.0.0.1)', ip: '127.0.0.1', expected: true },
20
+ { desc: 'adds XCLIENT for loopback IPv6 (::1)', ip: '::1', expected: true },
21
+ { desc: 'does not add XCLIENT for non-loopback IP', ip: '10.0.0.1', expected: false },
22
+ ]
28
23
 
29
- it('adds XCLIENT capability for allowed IP (::1)', (done) => {
30
- this.connection.remote.ip = '::1'
31
- this.plugin.hook_capabilities(() => {
32
- assert.ok(
33
- this.connection.capabilities.some((c) => c.startsWith('XCLIENT')),
34
- 'XCLIENT capability added for IPv6 loopback',
35
- )
36
- done()
37
- }, this.connection)
38
- })
39
-
40
- it('does not add XCLIENT capability for disallowed IP', (done) => {
41
- this.connection.remote.ip = '10.0.0.1'
42
- this.plugin.hook_capabilities(() => {
43
- assert.ok(
44
- !this.connection.capabilities.some((c) => c.startsWith('XCLIENT')),
45
- 'XCLIENT capability not added',
46
- )
47
- done()
48
- }, this.connection)
49
- })
24
+ for (const { desc, ip, expected } of cases) {
25
+ it(desc, async () => {
26
+ this.connection.remote.ip = ip
27
+ await new Promise((resolve) => this.plugin.hook_capabilities(resolve, this.connection))
28
+ const hasXclient = this.connection.capabilities.some((c) => c.startsWith('XCLIENT'))
29
+ assert.equal(hasXclient, expected)
30
+ })
31
+ }
50
32
  })
51
33
 
52
34
  describe('hook_unrecognized_command', () => {
53
- it('ignores non-XCLIENT commands', (done) => {
54
- this.plugin.hook_unrecognized_command(
55
- (code) => {
56
- assert.equal(code, undefined, 'next called with no args')
57
- done()
58
- },
59
- this.connection,
60
- ['EHLO', 'example.com'],
61
- )
62
- })
35
+ const callHook = (params) =>
36
+ new Promise((resolve) => {
37
+ this.plugin.hook_unrecognized_command((code) => resolve(code), this.connection, params)
38
+ })
63
39
 
64
- it('denies XCLIENT when transaction is in progress', (done) => {
65
- this.connection.init_transaction()
66
- this.plugin.hook_unrecognized_command(
67
- (code) => {
68
- assert.equal(code, DENY, 'denied with transaction in progress')
69
- done()
40
+ const cases = [
41
+ {
42
+ desc: 'ignores non-XCLIENT commands',
43
+ params: ['EHLO', 'example.com'],
44
+ check: (code) => assert.equal(code, undefined),
45
+ },
46
+ {
47
+ desc: 'denies XCLIENT when transaction is in progress',
48
+ setup: () => this.connection.init_transaction(),
49
+ params: ['XCLIENT', 'ADDR=127.0.0.1'],
50
+ check: (code) => assert.equal(code, DENY),
51
+ },
52
+ {
53
+ desc: 'denies XCLIENT from disallowed IP',
54
+ setup: () => {
55
+ this.connection.remote.ip = '10.0.0.1'
70
56
  },
71
- this.connection,
72
- ['XCLIENT', 'ADDR=127.0.0.1'],
73
- )
74
- })
75
-
76
- it('denies XCLIENT from disallowed IP', (done) => {
77
- this.connection.remote.ip = '10.0.0.1'
78
- this.plugin.hook_unrecognized_command(
79
- (code) => {
80
- assert.equal(code, DENY, 'denied from non-allowed IP')
81
- done()
57
+ params: ['XCLIENT', 'ADDR=127.0.0.2'],
58
+ check: (code) => assert.equal(code, DENY),
59
+ },
60
+ {
61
+ desc: 'denies XCLIENT with no valid IP address',
62
+ setup: () => {
63
+ this.connection.remote.ip = '127.0.0.1'
82
64
  },
83
- this.connection,
84
- ['XCLIENT', 'ADDR=127.0.0.2'],
85
- )
86
- })
87
-
88
- it('denies XCLIENT with no valid IP address', (done) => {
89
- this.connection.remote.ip = '127.0.0.1'
90
- this.plugin.hook_unrecognized_command(
91
- (code) => {
92
- assert.equal(code, DENY, 'denied when no valid ADDR')
93
- done()
65
+ params: ['XCLIENT', 'NAME=example.com'],
66
+ check: (code) => assert.equal(code, DENY),
67
+ },
68
+ {
69
+ desc: 'accepts XCLIENT with valid IPv4 ADDR from allowed host',
70
+ setup: () => {
71
+ this.connection.remote.ip = '127.0.0.1'
94
72
  },
95
- this.connection,
96
- ['XCLIENT', 'NAME=example.com'],
97
- )
98
- })
99
-
100
- it('accepts XCLIENT with valid IPv4 ADDR from allowed host', (done) => {
101
- this.connection.remote.ip = '127.0.0.1'
102
- this.plugin.hook_unrecognized_command(
103
- (code) => {
104
- // NEXT_HOOK or undefined (next called) means accepted
105
- assert.ok(code === NEXT_HOOK || code === undefined, 'accepted valid XCLIENT')
106
- done()
73
+ params: ['XCLIENT', 'ADDR=1.2.3.4'],
74
+ check: (code) => assert.ok(code === NEXT_HOOK || code === undefined),
75
+ },
76
+ {
77
+ desc: 'accepts XCLIENT with valid IPv6 ADDR from allowed host',
78
+ setup: () => {
79
+ this.connection.remote.ip = '127.0.0.1'
107
80
  },
108
- this.connection,
109
- ['XCLIENT', 'ADDR=1.2.3.4'],
110
- )
111
- })
112
-
113
- it('accepts XCLIENT with valid IPv6 ADDR from allowed host', (done) => {
114
- this.connection.remote.ip = '127.0.0.1'
115
- this.plugin.hook_unrecognized_command(
116
- (code) => {
117
- assert.ok(code === NEXT_HOOK || code === undefined, 'accepted valid IPv6 XCLIENT')
118
- done()
81
+ params: ['XCLIENT', 'ADDR=IPV6:2001:db8::1'],
82
+ check: (code) => assert.ok(code === NEXT_HOOK || code === undefined),
83
+ },
84
+ {
85
+ desc: 'accepts XCLIENT with ADDR and NAME, skipping rdns lookup',
86
+ setup: () => {
87
+ this.connection.remote.ip = '127.0.0.1'
119
88
  },
120
- this.connection,
121
- ['XCLIENT', 'ADDR=IPV6:2001:db8::1'],
122
- )
123
- })
89
+ params: ['XCLIENT', 'ADDR=1.2.3.4 NAME=example.com'],
90
+ check: (code) => assert.equal(code, NEXT_HOOK),
91
+ },
92
+ ]
124
93
 
125
- it('accepts XCLIENT with ADDR and NAME, skipping rdns lookup', (done) => {
126
- this.connection.remote.ip = '127.0.0.1'
127
- this.plugin.hook_unrecognized_command(
128
- (code) => {
129
- assert.equal(code, NEXT_HOOK, 'jumps to connect hook when NAME provided')
130
- done()
131
- },
132
- this.connection,
133
- ['XCLIENT', 'ADDR=1.2.3.4 NAME=example.com'],
134
- )
135
- })
94
+ for (const { desc, setup, params, check } of cases) {
95
+ it(desc, async () => {
96
+ if (setup) setup()
97
+ const code = await callHook(params)
98
+ check(code)
99
+ })
100
+ }
136
101
  })
137
102
  })
package/test/plugins.js CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  process.env.WITHOUT_CONFIG_CACHE = true
4
4
 
5
- const assert = require('node:assert')
5
+ const { describe, it, beforeEach, afterEach } = require('node:test')
6
+ const assert = require('node:assert/strict')
6
7
  const fs = require('node:fs')
7
8
  const path = require('node:path')
8
9
 
@@ -49,7 +50,7 @@ describe('plugin', () => {
49
50
 
50
51
  describe('get_timeout', () => {
51
52
  const toPath = path.resolve('config', `${piName}.timeout`)
52
- it('0s', (done) => {
53
+ it('0s', (t, done) => {
53
54
  fs.writeFile(toPath, '0', () => {
54
55
  this.plugin = new plugin.Plugin(piName)
55
56
  assert.equal(this.plugin.timeout, 0)
@@ -57,7 +58,7 @@ describe('plugin', () => {
57
58
  })
58
59
  })
59
60
 
60
- it('3s', (done) => {
61
+ it('3s', (t, done) => {
61
62
  fs.writeFile(toPath, '3', () => {
62
63
  this.plugin = new plugin.Plugin(piName)
63
64
  assert.equal(this.plugin.timeout, 3)
@@ -65,7 +66,7 @@ describe('plugin', () => {
65
66
  })
66
67
  })
67
68
 
68
- it('60s', (done) => {
69
+ it('60s', (t, done) => {
69
70
  fs.writeFile(toPath, '60', () => {
70
71
  this.plugin = new plugin.Plugin(piName)
71
72
  assert.equal(this.plugin.timeout, 60)
@@ -73,7 +74,7 @@ describe('plugin', () => {
73
74
  })
74
75
  })
75
76
 
76
- it('30s default (overrides NaN)', (done) => {
77
+ it('30s default (overrides NaN)', (t, done) => {
77
78
  fs.writeFile(toPath, 'apple', () => {
78
79
  this.plugin = new plugin.Plugin(piName)
79
80
  assert.equal(this.plugin.timeout, 30)
@@ -83,14 +84,12 @@ describe('plugin', () => {
83
84
  })
84
85
 
85
86
  describe('plugin_paths', () => {
86
- beforeEach((done) => {
87
+ beforeEach(() => {
87
88
  delete process.env.HARAKA
88
- done()
89
89
  })
90
90
 
91
- afterEach((done) => {
91
+ afterEach(() => {
92
92
  delete process.env.HARAKA
93
- done()
94
93
  })
95
94
 
96
95
  it('CORE plugin: (tls)', () => {
@@ -190,14 +189,12 @@ describe('plugin', () => {
190
189
  })
191
190
 
192
191
  describe('plugin_config', () => {
193
- beforeEach((done) => {
192
+ beforeEach(() => {
194
193
  delete process.env.HARAKA
195
- done()
196
194
  })
197
195
 
198
- afterEach((done) => {
196
+ afterEach(() => {
199
197
  delete process.env.HARAKA
200
- done()
201
198
  })
202
199
 
203
200
  it('CORE plugin: (tls)', () => {
package/test/rfc1869.js CHANGED
@@ -1,67 +1,44 @@
1
- const assert = require('node:assert')
1
+ 'use strict'
2
+
3
+ const { describe, it } = require('node:test')
4
+ const assert = require('node:assert/strict')
2
5
 
3
6
  const { parse } = require('../rfc1869')
4
7
 
5
8
  function _check(line, expected) {
6
9
  const match = /^(MAIL|RCPT)\s+(.*)$/.exec(line)
7
10
  const parsed = parse(match[1].toLowerCase(), match[2])
8
- assert.equal(parsed.length, expected.length)
9
- for (let x = 0; x < expected.length; x++) {
10
- assert.equal(parsed[x], expected[x])
11
- }
11
+ assert.deepEqual(parsed, expected)
12
12
  }
13
13
 
14
14
  describe('rfc1869', () => {
15
- it('MAIL FROM:<>', () => {
16
- _check('MAIL FROM:<>', ['<>'])
17
- })
18
-
19
- it('MAIL FROM:', () => {
20
- _check('MAIL FROM:', ['<>'])
21
- })
22
-
23
- it('MAIL FROM:<postmaster>', () => {
24
- _check('MAIL FROM:<postmaster>', ['<postmaster>'])
25
- })
26
-
27
- it('MAIL FROM:user', () => {
28
- _check('MAIL FROM:user', ['user'])
29
- })
30
-
31
- it('MAIL FROM:user size=1234', () => {
32
- _check('MAIL FROM:user size=1234', ['user', 'size=1234'])
33
- })
34
-
35
- it('MAIL FROM:user@domain size=1234', () => {
36
- _check('MAIL FROM:user@domain size=1234', ['user@domain', 'size=1234'])
37
- })
38
-
39
- it('MAIL FROM:<user@domain> size=1234', () => {
40
- _check('MAIL FROM:<user@domain> size=1234', ['<user@domain>', 'size=1234'])
41
- })
42
-
43
- it('MAIL FROM:<user@domain> somekey', () => {
44
- _check('MAIL FROM:<user@domain> somekey', ['<user@domain>', 'somekey'])
45
- })
46
-
47
- it('MAIL FROM:<user@domain> somekey other=foo', () => {
48
- _check('MAIL FROM:<user@domain> somekey other=foo', ['<user@domain>', 'somekey', 'other=foo'])
49
- })
50
-
51
- it('RCPT TO ugly', () => {
52
- _check('RCPT TO: 0@mailblog.biz 0=9 1=9', ['<0@mailblog.biz>', '0=9', '1=9'])
53
- })
54
-
55
- it('RCPT TO:<r86x-ray@emailitin.com> state=1', () => {
56
- _check('RCPT TO:<r86x-ray@emailitin.com> state=1', ['<r86x-ray@emailitin.com>', 'state=1'])
57
- })
58
-
59
- it('RCPT TO:<user=name@domain.com> foo=bar', () => {
60
- _check('RCPT TO:<user=name@domain.com> foo=bar', ['<user=name@domain.com>', 'foo=bar'])
61
- })
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
+ ]
62
38
 
63
- it('RCPT TO:<postmaster>', () => {
64
- _check('RCPT TO:<postmaster>', ['<postmaster>'])
39
+ for (const [line, expected] of validCases) {
40
+ it(line, () => _check(line, expected))
41
+ }
65
42
  })
66
43
 
67
44
  describe('error cases', () => {
@@ -88,22 +65,25 @@ describe('rfc1869', () => {
88
65
  })
89
66
 
90
67
  describe('strict mode', () => {
91
- it('strict MAIL FROM:<user@domain> accepts angle-bracket address', () => {
92
- const result = parse('mail', 'FROM:<user@domain.com>', true)
93
- assert.equal(result[0], '<user@domain.com>')
94
- })
95
-
96
- it('strict MAIL FROM without angle brackets throws', () => {
97
- assert.throws(() => parse('mail', 'FROM:user@domain.com', true), Error)
98
- })
99
-
100
- it('strict RCPT TO:<user@domain> accepts angle-bracket address', () => {
101
- const result = parse('rcpt', 'TO:<user@domain.com>', true)
102
- assert.equal(result[0], '<user@domain.com>')
103
- })
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
+ }
104
78
 
105
- it('strict RCPT TO without angle brackets throws', () => {
106
- assert.throws(() => parse('rcpt', 'TO:user@domain.com', true), Error)
107
- })
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
+ }
108
88
  })
109
89
  })