Haraka 2.8.28 → 3.0.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/.eslintrc.yaml +2 -10
- package/Changes.md +84 -2
- package/Dockerfile +1 -1
- package/Plugins.md +9 -4
- package/README.md +2 -6
- package/bin/haraka +5 -4
- package/config/outbound.ini +0 -7
- package/config/plugins +1 -1
- package/config/smtp.ini +1 -1
- package/config/smtp_forward.ini +2 -8
- package/config/smtp_proxy.ini +0 -6
- package/connection.js +178 -204
- package/coverage/lcov.info +13863 -0
- package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
- package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
- package/dkim.js +66 -73
- package/docs/Body.md +1 -22
- package/docs/CoreConfig.md +2 -2
- package/docs/Header.md +1 -47
- package/docs/Outbound.md +8 -36
- package/endpoint.js +1 -1
- package/haraka.js +1 -1
- package/host_pool.js +8 -12
- package/logger.js +25 -32
- package/outbound/client_pool.js +11 -153
- package/outbound/config.js +5 -11
- package/outbound/hmail.js +109 -143
- package/outbound/index.js +13 -25
- package/outbound/mx_lookup.js +10 -7
- package/outbound/queue.js +8 -12
- package/outbound/timer_queue.js +2 -4
- package/outbound/tls.js +17 -18
- package/outbound/todo.js +1 -0
- package/package.json +57 -55
- package/plugins/auth/auth_base.js +39 -63
- package/plugins/auth/auth_bridge.js +3 -4
- package/plugins/auth/auth_proxy.js +16 -16
- package/plugins/auth/auth_vpopmaild.js +30 -37
- package/plugins/auth/flat_file.js +9 -13
- package/plugins/avg.js +9 -11
- package/plugins/backscatterer.js +1 -1
- package/plugins/block_me.js +2 -6
- package/plugins/bounce.js +106 -124
- package/plugins/clamd.js +59 -63
- package/plugins/data.signatures.js +6 -6
- package/plugins/data.uribl.js +1 -415
- package/plugins/delay_deny.js +19 -20
- package/plugins/dkim_sign.js +56 -62
- package/plugins/dkim_verify.js +9 -8
- package/plugins/dns_list_base.js +43 -42
- package/plugins/dnsbl.js +41 -46
- package/plugins/dnswl.js +23 -26
- package/plugins/early_talker.js +24 -28
- package/plugins/esets.js +8 -11
- package/plugins/greylist.js +161 -190
- package/plugins/helo.checks.js +175 -197
- package/plugins/mail_from.is_resolvable.js +38 -38
- package/plugins/messagesniffer.js +33 -40
- package/plugins/prevent_credential_leaks.js +7 -5
- package/plugins/process_title.js +16 -17
- package/plugins/queue/deliver.js +2 -2
- package/plugins/queue/lmtp.js +5 -6
- package/plugins/queue/qmail-queue.js +11 -13
- package/plugins/queue/quarantine.js +25 -34
- package/plugins/queue/rabbitmq.js +3 -2
- package/plugins/queue/rabbitmq_amqplib.js +9 -9
- package/plugins/queue/smtp_bridge.js +5 -4
- package/plugins/queue/smtp_forward.js +81 -89
- package/plugins/queue/smtp_proxy.js +21 -22
- package/plugins/queue/test.js +2 -1
- package/plugins/rcpt_to.host_list_base.js +20 -30
- package/plugins/rcpt_to.in_host_list.js +12 -14
- package/plugins/rcpt_to.max_count.js +7 -5
- package/plugins/record_envelope_addresses.js +4 -6
- package/plugins/relay.js +64 -74
- package/plugins/reseed_rng.js +1 -2
- package/plugins/spamassassin.js +56 -68
- package/plugins/status.js +2 -3
- package/plugins/tarpit.js +8 -11
- package/plugins/tls.js +14 -17
- package/plugins/toobusy.js +6 -8
- package/plugins/xclient.js +14 -25
- package/plugins.js +24 -29
- package/rfc1869.js +2 -2
- package/server.js +3 -13
- package/smtp_client.js +138 -215
- package/tests/config/smtp_forward.ini +0 -6
- package/tests/fixtures/line_socket.js +1 -1
- package/tests/fixtures/util_hmailitem.js +5 -7
- package/tests/fixtures/vm_harness.js +2 -2
- package/tests/host_pool.js +13 -14
- package/tests/installation/plugins/inherits.js +1 -2
- package/tests/logger.js +2 -2
- package/tests/plugins/bounce.js +6 -8
- package/tests/plugins/dkim_signer.js +7 -7
- package/tests/plugins/dns_list_base.js +7 -7
- package/tests/plugins/helo.checks.js +1 -1
- package/tests/plugins/mail_from.is_resolvable.js +10 -54
- package/tests/plugins/queue/smtp_forward.js +11 -11
- package/tests/plugins/rcpt_to.host_list_base.js +1 -1
- package/tests/plugins/rcpt_to.in_host_list.js +1 -1
- package/tests/plugins/spamassassin.js +1 -1
- package/tests/queue/multibyte +0 -0
- package/tests/queue/plain +0 -0
- package/tests/rfc1869.js +4 -1
- package/tests/server.js +15 -9
- package/tests/smtp_client/auth.js +4 -14
- package/tests/smtp_client/basic.js +5 -15
- package/tests/smtp_client.js +7 -3
- package/tests/transaction.js +72 -19
- package/tls_socket.js +75 -85
- package/transaction.js +7 -9
- package/attachment_stream.js +0 -118
- package/bin/spf +0 -48
- package/chunkemitter.js +0 -75
- package/config/data.uribl.excludes +0 -202
- package/config/data.uribl.ini +0 -37
- package/config/spf.ini +0 -1
- package/docs/plugins/attachment.md +0 -92
- package/docs/plugins/data.uribl.md +0 -120
- package/docs/plugins/spf.md +0 -142
- package/mailbody.js +0 -502
- package/mailheader.js +0 -304
- package/messagestream.js +0 -441
- package/plugins/aliases.js +0 -120
- package/plugins/attachment.js +0 -503
- package/plugins/connect.p0f.js +0 -5
- package/plugins/spf.js +0 -327
- package/spf.js +0 -689
- package/tests/mailbody.js +0 -348
- package/tests/mailheader.js +0 -138
- package/tests/messagestream.js +0 -34
- package/tests/plugins/aliases.js +0 -376
- package/tests/plugins/spf.js +0 -251
- package/tests/spf.js +0 -96
package/plugins/bounce.js
CHANGED
|
@@ -1,44 +1,38 @@
|
|
|
1
1
|
// bounce tests
|
|
2
2
|
const tlds = require('haraka-tld');
|
|
3
|
+
const { SPF } = require('haraka-plugin-spf');
|
|
3
4
|
|
|
4
5
|
const net_utils = require('haraka-net-utils');
|
|
5
|
-
const SPF = require('./spf').SPF;
|
|
6
|
-
|
|
7
|
-
// Override logging in SPF module
|
|
8
|
-
SPF.prototype.log_debug = str => exports.logdebug(str)
|
|
9
6
|
|
|
10
7
|
exports.register = function () {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
plugin.register_hook('data_post', 'non_local_msgid');
|
|
8
|
+
this.load_bounce_ini();
|
|
9
|
+
this.load_bounce_bad_rcpt();
|
|
10
|
+
|
|
11
|
+
this.register_hook('mail', 'reject_all');
|
|
12
|
+
this.register_hook('data', 'single_recipient');
|
|
13
|
+
this.register_hook('data', 'bad_rcpt');
|
|
14
|
+
this.register_hook('data_post', 'empty_return_path');
|
|
15
|
+
this.register_hook('data', 'bounce_spf_enable');
|
|
16
|
+
this.register_hook('data_post', 'bounce_spf');
|
|
17
|
+
this.register_hook('data_post', 'non_local_msgid');
|
|
22
18
|
}
|
|
23
19
|
|
|
24
20
|
exports.load_bounce_bad_rcpt = function () {
|
|
25
|
-
const plugin = this;
|
|
26
21
|
|
|
27
|
-
const new_list =
|
|
28
|
-
|
|
22
|
+
const new_list = this.config.get('bounce_bad_rcpt', 'list', () => {
|
|
23
|
+
this.load_bounce_bad_rcpt();
|
|
29
24
|
});
|
|
30
25
|
|
|
31
26
|
const invalids = {};
|
|
32
|
-
for (
|
|
33
|
-
invalids[
|
|
27
|
+
for (const element of new_list) {
|
|
28
|
+
invalids[element] = true;
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
this.cfg.invalid_addrs = invalids;
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
exports.load_bounce_ini = function () {
|
|
40
|
-
|
|
41
|
-
plugin.cfg = plugin.config.get('bounce.ini', {
|
|
35
|
+
this.cfg = this.config.get('bounce.ini', {
|
|
42
36
|
booleans: [
|
|
43
37
|
'-check.reject_all',
|
|
44
38
|
'+check.single_recipient',
|
|
@@ -53,48 +47,41 @@ exports.load_bounce_ini = function () {
|
|
|
53
47
|
'-reject.non_local_msgid',
|
|
54
48
|
],
|
|
55
49
|
}, () => {
|
|
56
|
-
|
|
50
|
+
this.load_bounce_ini();
|
|
57
51
|
});
|
|
58
52
|
|
|
59
53
|
// Legacy config handling
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
if (this.cfg.main.reject_invalid) {
|
|
55
|
+
this.logerror('bounce.ini is out of date, please update!');
|
|
56
|
+
this.cfg.check.single_recipient=true;
|
|
57
|
+
this.cfg.reject.single_recipient=true;
|
|
64
58
|
}
|
|
65
59
|
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
if (this.cfg.main.reject_all) {
|
|
61
|
+
this.logerror('bounce.ini is out of date, please update!');
|
|
62
|
+
this.cfg.check.reject_all=true;
|
|
69
63
|
}
|
|
70
64
|
}
|
|
71
65
|
|
|
72
66
|
exports.reject_all = function (next, connection, params) {
|
|
73
|
-
|
|
74
|
-
if (!plugin.cfg.check.reject_all) { return next(); }
|
|
67
|
+
if (!this.cfg.check.reject_all) return next();
|
|
75
68
|
|
|
76
69
|
const mail_from = params[0];
|
|
70
|
+
// bounce messages are from null senders
|
|
71
|
+
if (!this.has_null_sender(connection, mail_from)) return next();
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
return next(); // bounce messages are from null senders
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
connection.transaction.results.add(plugin,
|
|
83
|
-
{fail: 'bounces_accepted', emit: true });
|
|
73
|
+
connection.transaction.results.add(this, {fail: 'bounces_accepted', emit: true });
|
|
84
74
|
return next(DENY, 'No bounces accepted here');
|
|
85
75
|
}
|
|
86
76
|
|
|
87
77
|
exports.single_recipient = function (next, connection) {
|
|
88
|
-
|
|
89
|
-
if (!
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const transaction = connection.transaction;
|
|
78
|
+
if (!this?.cfg?.check?.single_recipient) return next();
|
|
79
|
+
if (!this?.has_null_sender(connection)) return next();
|
|
80
|
+
const { transaction, relaying, remote } = connection;
|
|
93
81
|
|
|
94
82
|
// Valid bounces have a single recipient
|
|
95
|
-
if (
|
|
96
|
-
transaction.results.add(
|
|
97
|
-
{pass: 'single_recipient', emit: true });
|
|
83
|
+
if (transaction.rcpt_to.length === 1) {
|
|
84
|
+
transaction.results.add(this, {pass: 'single_recipient', emit: true });
|
|
98
85
|
return next();
|
|
99
86
|
}
|
|
100
87
|
|
|
@@ -103,33 +90,29 @@ exports.single_recipient = function (next, connection) {
|
|
|
103
90
|
// to distribution groups using the null-sender if
|
|
104
91
|
// the option 'Do not send delivery reports' is
|
|
105
92
|
// checked (not sure if this is default or not)
|
|
106
|
-
if (
|
|
107
|
-
transaction.results.add(
|
|
108
|
-
{skip: 'single_recipient(relay)', emit: true });
|
|
93
|
+
if (relaying) {
|
|
94
|
+
transaction.results.add(this, {skip: 'single_recipient(relay)', emit: true });
|
|
109
95
|
return next();
|
|
110
96
|
}
|
|
111
|
-
if (
|
|
112
|
-
transaction.results.add(
|
|
113
|
-
{skip: 'single_recipient(private_ip)', emit: true });
|
|
97
|
+
if (remote.is_private) {
|
|
98
|
+
transaction.results.add(this, {skip: 'single_recipient(private_ip)', emit: true });
|
|
114
99
|
return next();
|
|
115
100
|
}
|
|
116
101
|
|
|
117
|
-
connection.loginfo(
|
|
102
|
+
connection.loginfo(this, `bounce with too many recipients to: ${transaction.rcpt_to.join(',')}`);
|
|
118
103
|
|
|
119
|
-
transaction.results.add(
|
|
104
|
+
transaction.results.add(this, {fail: 'single_recipient', emit: true });
|
|
120
105
|
|
|
121
|
-
if (!
|
|
106
|
+
if (!this.cfg.reject.single_recipient) return next();
|
|
122
107
|
|
|
123
108
|
return next(DENY, 'this bounce message does not have 1 recipient');
|
|
124
109
|
}
|
|
125
110
|
|
|
126
111
|
exports.empty_return_path = function (next, connection) {
|
|
127
|
-
|
|
128
|
-
if (!
|
|
129
|
-
if (!plugin.has_null_sender(connection)) return next();
|
|
130
|
-
|
|
131
|
-
const transaction = connection.transaction;
|
|
112
|
+
if (!this.cfg.check.empty_return_path) return next();
|
|
113
|
+
if (!this.has_null_sender(connection)) return next();
|
|
132
114
|
|
|
115
|
+
const { transaction } = connection;
|
|
133
116
|
// Bounce messages generally do not have a Return-Path set. This checks
|
|
134
117
|
// for that. But whether it should is worth questioning...
|
|
135
118
|
|
|
@@ -144,56 +127,54 @@ exports.empty_return_path = function (next, connection) {
|
|
|
144
127
|
// Return-Path, aka Reverse-PATH, Envelope FROM, RFC5321.MailFrom
|
|
145
128
|
// validate that the Return-Path header is empty, RFC 3834
|
|
146
129
|
|
|
147
|
-
const rp =
|
|
130
|
+
const rp = transaction.header.get('Return-Path');
|
|
148
131
|
if (!rp) {
|
|
149
|
-
transaction.results.add(
|
|
132
|
+
transaction.results.add(this, {pass: 'empty_return_path' });
|
|
150
133
|
return next();
|
|
151
134
|
}
|
|
152
135
|
|
|
153
136
|
if (rp === '<>') {
|
|
154
|
-
transaction.results.add(
|
|
137
|
+
transaction.results.add(this, {pass: 'empty_return_path' });
|
|
155
138
|
return next();
|
|
156
139
|
}
|
|
157
140
|
|
|
158
|
-
transaction.results.add(
|
|
141
|
+
transaction.results.add(this, {fail: 'empty_return_path', emit: true });
|
|
159
142
|
return next(DENY, 'bounce with non-empty Return-Path (RFC 3834)');
|
|
160
143
|
}
|
|
161
144
|
|
|
162
145
|
exports.bad_rcpt = function (next, connection) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!plugin.cfg.invalid_addrs[rcpt]) continue;
|
|
173
|
-
transaction.results.add(plugin, {fail: 'bad_rcpt', emit: true });
|
|
146
|
+
if (!this.cfg.check.bad_rcpt) return next();
|
|
147
|
+
if (!this.has_null_sender(connection)) return next();
|
|
148
|
+
if (!this.cfg.invalid_addrs) return next();
|
|
149
|
+
|
|
150
|
+
const { transaction } = connection;
|
|
151
|
+
for (const element of transaction.rcpt_to) {
|
|
152
|
+
const rcpt = element.address();
|
|
153
|
+
if (!this.cfg.invalid_addrs[rcpt]) continue;
|
|
154
|
+
transaction.results.add(this, {fail: 'bad_rcpt', emit: true });
|
|
174
155
|
return next(DENY, 'That recipient does not accept bounces');
|
|
175
156
|
}
|
|
157
|
+
transaction.results.add(this, {pass: 'bad_rcpt'});
|
|
176
158
|
|
|
177
|
-
transaction.results.add(plugin, {pass: 'bad_rcpt'});
|
|
178
159
|
return next();
|
|
179
160
|
}
|
|
180
161
|
|
|
181
162
|
exports.has_null_sender = function (connection, mail_from) {
|
|
182
|
-
|
|
183
|
-
const transaction = connection
|
|
163
|
+
// ok ?
|
|
164
|
+
const transaction = connection?.transaction;
|
|
165
|
+
if (!transaction) return false;
|
|
184
166
|
|
|
185
|
-
if (!mail_from) mail_from =
|
|
167
|
+
if (!mail_from) mail_from = transaction.mail_from;
|
|
186
168
|
|
|
187
169
|
// bounces have a null sender.
|
|
188
170
|
// null sender could also be tested with mail_from.user
|
|
189
171
|
// Why would isNull() exist if it wasn't the right way to test this?
|
|
190
|
-
|
|
191
172
|
if (mail_from.isNull()) {
|
|
192
|
-
transaction.results.add(
|
|
173
|
+
transaction.results.add(this, {isa: 'yes'});
|
|
193
174
|
return true;
|
|
194
175
|
}
|
|
195
176
|
|
|
196
|
-
transaction.results.add(
|
|
177
|
+
transaction.results.add(this, {isa: 'no'});
|
|
197
178
|
return false;
|
|
198
179
|
}
|
|
199
180
|
|
|
@@ -201,11 +182,13 @@ const message_id_re = /^Message-ID:\s*(<?[^>]+>?)/mig;
|
|
|
201
182
|
|
|
202
183
|
function find_message_id_headers (headers, body, connection, self) {
|
|
203
184
|
if (!body) return;
|
|
185
|
+
|
|
204
186
|
let match;
|
|
205
187
|
while ((match = message_id_re.exec(body.bodytext))) {
|
|
206
188
|
const mid = match[1];
|
|
207
189
|
headers[mid] = true;
|
|
208
190
|
}
|
|
191
|
+
|
|
209
192
|
for (let i=0,l=body.children.length; i < l; i++) {
|
|
210
193
|
// Recure to any MIME children
|
|
211
194
|
find_message_id_headers(headers, body.children[i], connection, self);
|
|
@@ -213,12 +196,11 @@ function find_message_id_headers (headers, body, connection, self) {
|
|
|
213
196
|
}
|
|
214
197
|
|
|
215
198
|
exports.non_local_msgid = function (next, connection) {
|
|
216
|
-
|
|
217
|
-
if (!
|
|
218
|
-
if (!plugin.has_null_sender(connection)) return next();
|
|
219
|
-
|
|
220
|
-
const transaction = connection.transaction;
|
|
199
|
+
if (!this.cfg.check.non_local_msgid) return next();
|
|
200
|
+
if (!this.has_null_sender(connection)) return next();
|
|
221
201
|
|
|
202
|
+
const transaction = connection?.transaction;
|
|
203
|
+
if (!transaction) return next();
|
|
222
204
|
// Bounce messages usually contain the headers of the original message
|
|
223
205
|
// in the body. This parses the body, searching for the Message-ID header.
|
|
224
206
|
// It then inspects the contents of that header, extracting the domain part,
|
|
@@ -234,44 +216,43 @@ exports.non_local_msgid = function (next, connection) {
|
|
|
234
216
|
// http://lamsonproject.org/docs/bounce_detection.html
|
|
235
217
|
|
|
236
218
|
let matches = {}
|
|
237
|
-
find_message_id_headers(matches, transaction.body, connection,
|
|
219
|
+
find_message_id_headers(matches, transaction.body, connection, this);
|
|
238
220
|
matches = Object.keys(matches);
|
|
239
|
-
connection.logdebug(
|
|
221
|
+
connection.logdebug(this, `found Message-IDs: ${matches.join(', ')}`);
|
|
240
222
|
|
|
241
223
|
if (!matches.length) {
|
|
242
|
-
connection.loginfo(
|
|
243
|
-
transaction.results.add(
|
|
244
|
-
if (!
|
|
224
|
+
connection.loginfo(this, 'no Message-ID matches');
|
|
225
|
+
transaction.results.add(this, { fail: 'Message-ID' });
|
|
226
|
+
if (!this.cfg.reject.non_local_msgid) return next();
|
|
245
227
|
return next(DENY, `bounce without Message-ID in headers, unable to verify that I sent it`);
|
|
246
228
|
}
|
|
247
229
|
|
|
248
230
|
const domains=[];
|
|
249
|
-
for (
|
|
250
|
-
const res =
|
|
231
|
+
for (const match of matches) {
|
|
232
|
+
const res = match.match(/@([^>]*)>?/i);
|
|
251
233
|
if (!res) continue;
|
|
252
234
|
domains.push(res[1]);
|
|
253
235
|
}
|
|
254
236
|
|
|
255
237
|
if (domains.length === 0) {
|
|
256
|
-
connection.loginfo(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!plugin.cfg.reject.non_local_msgid) return next();
|
|
238
|
+
connection.loginfo(this, 'no domain(s) parsed from Message-ID headers');
|
|
239
|
+
transaction.results.add(this, { fail: 'Message-ID parseable' });
|
|
240
|
+
if (!this.cfg.reject.non_local_msgid) return next();
|
|
260
241
|
return next(DENY, `bounce with invalid Message-ID, I didn't send it.`);
|
|
261
242
|
}
|
|
262
243
|
|
|
263
|
-
connection.logdebug(
|
|
244
|
+
connection.logdebug(this, domains);
|
|
264
245
|
|
|
265
246
|
const valid_domains=[];
|
|
266
|
-
for (
|
|
267
|
-
const org_dom = tlds.get_organizational_domain(
|
|
247
|
+
for (const domain of domains) {
|
|
248
|
+
const org_dom = tlds.get_organizational_domain(domain);
|
|
268
249
|
if (!org_dom) { continue; }
|
|
269
250
|
valid_domains.push(org_dom);
|
|
270
251
|
}
|
|
271
252
|
|
|
272
253
|
if (valid_domains.length === 0) {
|
|
273
|
-
transaction.results.add(
|
|
274
|
-
if (!
|
|
254
|
+
transaction.results.add(this, { fail: 'Message-ID valid domain' });
|
|
255
|
+
if (!this.cfg.reject.non_local_msgid) return next();
|
|
275
256
|
return next(DENY, `bounce Message-ID without valid domain, I didn't send it.`);
|
|
276
257
|
}
|
|
277
258
|
|
|
@@ -307,30 +288,31 @@ function find_received_headers (ips, body, connection, self) {
|
|
|
307
288
|
}
|
|
308
289
|
|
|
309
290
|
exports.bounce_spf_enable = function (next, connection) {
|
|
310
|
-
|
|
311
|
-
if (
|
|
291
|
+
if (!connection.transaction) return next();
|
|
292
|
+
if (this.cfg.check.bounce_spf) {
|
|
312
293
|
connection.transaction.parse_body = true;
|
|
313
294
|
}
|
|
314
295
|
return next();
|
|
315
296
|
}
|
|
316
297
|
|
|
317
298
|
exports.bounce_spf = function (next, connection) {
|
|
318
|
-
|
|
319
|
-
if (!
|
|
320
|
-
|
|
321
|
-
const txn = connection
|
|
299
|
+
if (!this.cfg.check.bounce_spf) return next();
|
|
300
|
+
if (!this.has_null_sender(connection)) return next();
|
|
301
|
+
|
|
302
|
+
const txn = connection?.transaction;
|
|
303
|
+
if (!txn) return next();
|
|
322
304
|
|
|
323
305
|
// Recurse through all textual parts and store all parsed IPs
|
|
324
306
|
// in an object to remove any duplicates which might appear.
|
|
325
307
|
let ips = {};
|
|
326
|
-
find_received_headers(ips, txn.body, connection,
|
|
308
|
+
find_received_headers(ips, txn.body, connection, this);
|
|
327
309
|
ips = Object.keys(ips);
|
|
328
310
|
if (!ips.length) {
|
|
329
|
-
connection.loginfo(
|
|
311
|
+
connection.loginfo(this, 'No received headers found in message');
|
|
330
312
|
return next();
|
|
331
313
|
}
|
|
332
314
|
|
|
333
|
-
connection.logdebug(
|
|
315
|
+
connection.logdebug(this, `found IPs to check: ${ips.join(', ')}`);
|
|
334
316
|
|
|
335
317
|
let pending = 0;
|
|
336
318
|
let aborted = false;
|
|
@@ -348,10 +330,10 @@ exports.bounce_spf = function (next, connection) {
|
|
|
348
330
|
}
|
|
349
331
|
|
|
350
332
|
timer = setTimeout(() => {
|
|
351
|
-
connection.logerror(
|
|
352
|
-
txn.results.add(
|
|
333
|
+
connection.logerror(this, 'Timed out');
|
|
334
|
+
txn.results.add(this, { skip: 'bounce_spf(timeout)' });
|
|
353
335
|
return run_cb(true);
|
|
354
|
-
}, (
|
|
336
|
+
}, (this.timeout - 1) * 1000);
|
|
355
337
|
|
|
356
338
|
ips.forEach(ip => {
|
|
357
339
|
if (aborted) return;
|
|
@@ -362,30 +344,30 @@ exports.bounce_spf = function (next, connection) {
|
|
|
362
344
|
if (aborted) return;
|
|
363
345
|
pending--;
|
|
364
346
|
if (err) {
|
|
365
|
-
connection.logerror(
|
|
347
|
+
connection.logerror(this, err.message);
|
|
366
348
|
return run_cb();
|
|
367
349
|
}
|
|
368
|
-
connection.logdebug(
|
|
350
|
+
connection.logdebug(this, `ip=${ip} spf_result=${spf.result(result)}`);
|
|
369
351
|
switch (result) {
|
|
370
352
|
case (spf.SPF_NONE):
|
|
371
353
|
// falls through, domain doesn't publish an SPF record
|
|
372
354
|
case (spf.SPF_TEMPERROR):
|
|
373
355
|
case (spf.SPF_PERMERROR):
|
|
374
356
|
// Abort as all subsequent lookups will return this
|
|
375
|
-
connection.logdebug(
|
|
376
|
-
txn.results.add(
|
|
357
|
+
connection.logdebug(this, `Aborted: SPF returned ${spf.result(result)}`);
|
|
358
|
+
txn.results.add(this, { skip: 'bounce_spf' });
|
|
377
359
|
return run_cb(true);
|
|
378
360
|
case (spf.SPF_PASS):
|
|
379
361
|
// Presume this is a valid bounce
|
|
380
362
|
// TODO: this could be spoofed; could weight each IP to combat
|
|
381
|
-
connection.loginfo(
|
|
382
|
-
txn.results.add(
|
|
363
|
+
connection.loginfo(this, `Valid bounce originated from ${ip}`);
|
|
364
|
+
txn.results.add(this, { pass: 'bounce_spf' });
|
|
383
365
|
return run_cb(true);
|
|
384
366
|
}
|
|
385
367
|
if (pending === 0 && !aborted) {
|
|
386
368
|
// We've checked all the IPs and none of them returned Pass
|
|
387
|
-
txn.results.add(
|
|
388
|
-
if (!
|
|
369
|
+
txn.results.add(this, {fail: 'bounce_spf', emit: true });
|
|
370
|
+
if (!this.cfg.reject.bounce_spf) return run_cb();
|
|
389
371
|
return run_cb(false, DENY, 'Invalid bounce (spoofed sender)');
|
|
390
372
|
}
|
|
391
373
|
}
|