haraka-plugin-spamassassin 1.1.0 → 1.1.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/CHANGELOG.md +9 -0
- package/README.md +5 -2
- package/index.js +69 -47
- package/package.json +11 -9
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
4
4
|
|
|
5
5
|
### Unreleased
|
|
6
6
|
|
|
7
|
+
### [1.1.1] - 2026-06-20
|
|
8
|
+
|
|
9
|
+
- fix(spamd_socket): support `[ipv6]:port` via net_utils.endpoint #7
|
|
10
|
+
- deps(dev): bump haraka-test-fixtures to ^1.7.0
|
|
11
|
+
- refactor: rename hook_data_post to spamassassin_data_post
|
|
12
|
+
- split into reusable `parse_spamassassin` + `handle_spamassassin`
|
|
13
|
+
related to haraka/Haraka#3604
|
|
14
|
+
|
|
7
15
|
### [1.1.0] - 2026-05-17
|
|
8
16
|
|
|
9
17
|
- changed: dep address-rfc2821 -> @haraka/email-address
|
|
@@ -40,3 +48,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
40
48
|
[1.0.3]: https://github.com/haraka/haraka-plugin-spamassassin/releases/tag/v1.0.3
|
|
41
49
|
[1.0.4]: https://github.com/haraka/haraka-plugin-spamassassin/releases/tag/v1.0.4
|
|
42
50
|
[1.1.0]: https://github.com/haraka/haraka-plugin-spamassassin/releases/tag/v1.1.0
|
|
51
|
+
[1.1.1]: https://github.com/haraka/haraka-plugin-spamassassin/releases/tag/v1.1.1
|
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
[![CI Test Status][ci-img]][ci-url]
|
|
2
|
+
[![Code Coverage][cov-img]][cov-url]
|
|
2
3
|
[![Code Climate][clim-img]][clim-url]
|
|
3
4
|
|
|
4
5
|
# haraka-plugin-spamassassin
|
|
@@ -183,5 +184,7 @@ Other headers options you might find interesting or useful are:
|
|
|
183
184
|
|
|
184
185
|
[ci-img]: https://github.com/haraka/haraka-plugin-spamassassin/actions/workflows/ci.yml/badge.svg
|
|
185
186
|
[ci-url]: https://github.com/haraka/haraka-plugin-spamassassin/actions/workflows/ci.yml
|
|
186
|
-
[
|
|
187
|
-
[
|
|
187
|
+
[cov-img]: https://codecov.io/github/haraka/haraka-plugin-spamassassin/coverage.svg
|
|
188
|
+
[cov-url]: https://codecov.io/github/haraka/haraka-plugin-spamassassin
|
|
189
|
+
[clim-img]: https://qlty.sh/gh/haraka/projects/haraka-plugin-spamassassin/maintainability.svg
|
|
190
|
+
[clim-url]: https://qlty.sh/gh/haraka/projects/haraka-plugin-spamassassin
|
package/index.js
CHANGED
|
@@ -8,6 +8,10 @@ const net_utils = require('haraka-net-utils')
|
|
|
8
8
|
|
|
9
9
|
exports.register = function () {
|
|
10
10
|
this.load_spamassassin_ini()
|
|
11
|
+
// explicit hook (not magic hook_data_post) so the plugin can be inherited;
|
|
12
|
+
// don't rename. guarded so inheritors don't re-register. haraka/Haraka#3604
|
|
13
|
+
if (this.name === 'spamassassin')
|
|
14
|
+
this.register_hook('data_post', 'spamassassin_data_post')
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
exports.load_spamassassin_ini = function () {
|
|
@@ -55,7 +59,7 @@ exports.load_spamassassin_ini = function () {
|
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
exports.
|
|
62
|
+
exports.spamassassin_data_post = function (next, connection) {
|
|
59
63
|
if (this.should_skip(connection)) return next()
|
|
60
64
|
|
|
61
65
|
const txn = connection.transaction
|
|
@@ -65,14 +69,30 @@ exports.hook_data_post = function (next, connection) {
|
|
|
65
69
|
const headers = this.get_spamd_headers(connection, username)
|
|
66
70
|
const socket = this.get_spamd_socket(next, connection, headers)
|
|
67
71
|
|
|
68
|
-
const
|
|
69
|
-
let state = 'line0'
|
|
70
|
-
let last_header
|
|
72
|
+
const lines = []
|
|
71
73
|
const start = Date.now()
|
|
72
74
|
|
|
73
75
|
socket.on('line', (line) => {
|
|
74
|
-
connection.logprotocol(this, `Spamd C: ${line}
|
|
75
|
-
line
|
|
76
|
+
connection.logprotocol(this, `Spamd C: ${line}`)
|
|
77
|
+
lines.push(line.replace(/\r?\n/, ''))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
socket.once('end', () => {
|
|
81
|
+
if (!connection.transaction) return next() // client gone
|
|
82
|
+
const spamd_response = this.parse_spamassassin(lines.join('\n'))
|
|
83
|
+
socket.nextOnce(
|
|
84
|
+
...this.handle_spamassassin(connection, spamd_response, start),
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// parse a raw spamd response into a result object. reusable by inheriting
|
|
90
|
+
// plugins (e.g. @haraka/plugin-ecsd). See haraka/Haraka#3604.
|
|
91
|
+
exports.parse_spamassassin = function (raw) {
|
|
92
|
+
const spamd_response = { headers: {} }
|
|
93
|
+
let state = 'line0'
|
|
94
|
+
let last_header
|
|
95
|
+
for (const line of String(raw).split(/\r?\n/)) {
|
|
76
96
|
if (state === 'line0') {
|
|
77
97
|
spamd_response.line0 = line
|
|
78
98
|
state = 'response'
|
|
@@ -92,57 +112,59 @@ exports.hook_data_post = function (next, connection) {
|
|
|
92
112
|
} else if (state === 'headers') {
|
|
93
113
|
const m = line.match(/^X-Spam-([\x21-\x39\x3B-\x7E]+):\s*(.*)/)
|
|
94
114
|
if (m) {
|
|
95
|
-
connection.logdebug(this, `header: ${line}`)
|
|
96
115
|
last_header = m[1]
|
|
97
116
|
spamd_response.headers[m[1]] = m[2]
|
|
98
|
-
|
|
117
|
+
continue
|
|
99
118
|
}
|
|
100
119
|
let fold
|
|
101
120
|
if (last_header && (fold = line.match(/^(\s+.*)/))) {
|
|
102
121
|
spamd_response.headers[last_header] += `\r\n${fold[1]}`
|
|
103
|
-
|
|
122
|
+
continue
|
|
104
123
|
}
|
|
105
124
|
last_header = ''
|
|
106
125
|
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
}
|
|
127
|
+
extract_tests(spamd_response)
|
|
128
|
+
return spamd_response
|
|
129
|
+
}
|
|
111
130
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
131
|
+
function extract_tests(spamd_response) {
|
|
132
|
+
if (spamd_response.headers?.Tests) {
|
|
133
|
+
spamd_response.tests = spamd_response.headers.Tests.replace(/\s/g, '')
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
// SpamAssassin omits a space before autolearn= on folded header lines, so
|
|
137
|
+
// don't match autolearn onwards.
|
|
138
|
+
if (spamd_response.headers?.Status) {
|
|
139
|
+
const tests = /tests=((?:(?!autolearn)[^ ])+)/.exec(
|
|
140
|
+
spamd_response.headers.Status.replace(/\r?\n\t/g, ''),
|
|
141
|
+
)
|
|
142
|
+
if (tests) spamd_response.tests = tests[1]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
// handle a parsed spamd response (annotate + headers + reject decision).
|
|
147
|
+
// I/O-free so inheriting plugins can reuse it; returns next() args ([] = CONT).
|
|
148
|
+
// See haraka/Haraka#3604.
|
|
149
|
+
exports.handle_spamassassin = function (connection, spamd_response, start) {
|
|
150
|
+
const txn = connection.transaction
|
|
151
|
+
if (!txn) return []
|
|
134
152
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
txn.notes.spamassassin = spamd_response
|
|
154
|
+
connection.results.add(this, {
|
|
155
|
+
time: start === undefined ? undefined : (Date.now() - start) / 1000,
|
|
156
|
+
flag: spamd_response.flag,
|
|
157
|
+
})
|
|
138
158
|
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
this.fixup_old_headers(txn)
|
|
160
|
+
this.do_header_updates(connection, spamd_response)
|
|
161
|
+
this.log_results(connection, spamd_response)
|
|
141
162
|
|
|
142
|
-
|
|
163
|
+
const exceeds_err = this.score_too_high(connection, spamd_response)
|
|
164
|
+
if (exceeds_err) return [DENY, exceeds_err]
|
|
143
165
|
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
this.munge_subject(connection, spamd_response.score)
|
|
167
|
+
return []
|
|
146
168
|
}
|
|
147
169
|
|
|
148
170
|
exports.fixup_old_headers = function (txn) {
|
|
@@ -333,12 +355,12 @@ exports.get_spamd_socket = function (next, conn, headers) {
|
|
|
333
355
|
const connect_timeout = parseInt(plugin.cfg.main.connect_timeout) || 30
|
|
334
356
|
socket.setTimeout(connect_timeout * 1000)
|
|
335
357
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
358
|
+
const ep = net_utils.endpoint(plugin.cfg.main.spamd_socket, 783)
|
|
359
|
+
if (ep instanceof Error) throw ep
|
|
360
|
+
if (ep.path) {
|
|
361
|
+
socket.connect(ep.path)
|
|
339
362
|
} else {
|
|
340
|
-
|
|
341
|
-
socket.connect(hostport[1] || 783, hostport[0])
|
|
363
|
+
socket.connect(ep.port, ep.host)
|
|
342
364
|
}
|
|
343
365
|
|
|
344
366
|
return socket
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "haraka-plugin-spamassassin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Haraka plugin that scans messages with SpamAssassin",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -8,16 +8,18 @@
|
|
|
8
8
|
"config"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
+
"prepare": "git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true",
|
|
11
12
|
"format": "npm run prettier:fix && npm run lint:fix",
|
|
12
13
|
"lint": "npx eslint *.js test",
|
|
13
14
|
"lint:fix": "npx eslint *.js test --fix",
|
|
14
15
|
"prettier": "npx prettier . --check",
|
|
15
16
|
"prettier:fix": "npx prettier . --write --log-level=warn",
|
|
17
|
+
"qlty": "qlty smells --all",
|
|
16
18
|
"test": "node --test",
|
|
17
|
-
"versions": "npx npm-dep-mgr check",
|
|
18
|
-
"versions:fix": "npx npm-dep-mgr update",
|
|
19
19
|
"test:coverage": "node --test --experimental-test-coverage --test-coverage-exclude=package.json --test-coverage-exclude=test/*.js",
|
|
20
|
-
"test:coverage:lcov": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info test/*.js"
|
|
20
|
+
"test:coverage:lcov": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info test/*.js",
|
|
21
|
+
"versions": "npx npm-dep-mgr check",
|
|
22
|
+
"versions:fix": "npx npm-dep-mgr update"
|
|
21
23
|
},
|
|
22
24
|
"repository": {
|
|
23
25
|
"type": "git",
|
|
@@ -35,13 +37,13 @@
|
|
|
35
37
|
},
|
|
36
38
|
"homepage": "https://github.com/haraka/haraka-plugin-spamassassin#readme",
|
|
37
39
|
"dependencies": {
|
|
38
|
-
"haraka-net-utils": "^1.
|
|
39
|
-
"haraka-utils": "^
|
|
40
|
+
"haraka-net-utils": "^1.9.2",
|
|
41
|
+
"haraka-utils": "^2.2.1"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
|
-
"@haraka/
|
|
43
|
-
"@haraka/
|
|
44
|
-
"haraka-test-fixtures": "^1.
|
|
44
|
+
"@haraka/email-address": "^3.1.6",
|
|
45
|
+
"@haraka/eslint-config": "^3.0.0",
|
|
46
|
+
"haraka-test-fixtures": "^1.7.2"
|
|
45
47
|
},
|
|
46
48
|
"prettier": {
|
|
47
49
|
"singleQuote": true,
|