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/helo.checks.js
CHANGED
|
@@ -19,42 +19,39 @@ const checks = [
|
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
exports.register = function () {
|
|
22
|
-
|
|
23
|
-
plugin.load_helo_checks_ini();
|
|
22
|
+
this.load_helo_checks_ini();
|
|
24
23
|
|
|
25
|
-
if (
|
|
24
|
+
if (this.cfg.check.proto_mismatch) {
|
|
26
25
|
// NOTE: these *must* run before init
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
this.register_hook('helo', 'proto_mismatch_smtp');
|
|
27
|
+
this.register_hook('ehlo', 'proto_mismatch_esmtp');
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
// Always run init
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
this.register_hook('helo', 'init');
|
|
32
|
+
this.register_hook('ehlo', 'init');
|
|
34
33
|
|
|
35
|
-
for (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
plugin.register_hook('ehlo', hook);
|
|
34
|
+
for (const c in checks) {
|
|
35
|
+
if (!this.cfg.check[c]) continue; // disabled in config
|
|
36
|
+
this.register_hook('helo', c);
|
|
37
|
+
this.register_hook('ehlo', c);
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
// Always emit a log entry
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
this.register_hook('helo', 'emit_log');
|
|
42
|
+
this.register_hook('ehlo', 'emit_log');
|
|
45
43
|
|
|
46
|
-
if (
|
|
44
|
+
if (this.cfg.check.match_re) {
|
|
47
45
|
const load_re_file = () => {
|
|
48
|
-
const regex_list = utils.valid_regexes(
|
|
46
|
+
const regex_list = utils.valid_regexes(this.config.get('helo.checks.regexps', 'list', load_re_file));
|
|
49
47
|
// pre-compile the regexes
|
|
50
|
-
|
|
48
|
+
this.cfg.list_re = new RegExp(`^(${regex_list.join('|')})$`, 'i');
|
|
51
49
|
};
|
|
52
50
|
load_re_file();
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
exports.load_helo_checks_ini = function () {
|
|
57
|
-
const plugin = this;
|
|
58
55
|
|
|
59
56
|
const booleans = [
|
|
60
57
|
'+skip.private_ip',
|
|
@@ -70,63 +67,53 @@ exports.load_helo_checks_ini = function () {
|
|
|
70
67
|
booleans.push(`-reject.${c}`);
|
|
71
68
|
});
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
this.cfg = this.config.get('helo.checks.ini', { booleans },
|
|
74
71
|
() => {
|
|
75
|
-
|
|
72
|
+
this.load_helo_checks_ini();
|
|
76
73
|
});
|
|
77
74
|
|
|
78
75
|
// backwards compatible with old config file
|
|
79
|
-
if (
|
|
80
|
-
|
|
76
|
+
if (this.cfg.check_no_dot !== undefined) {
|
|
77
|
+
this.cfg.check.valid_hostname = !!this.cfg.check_no_dot;
|
|
81
78
|
}
|
|
82
|
-
if (
|
|
83
|
-
|
|
79
|
+
if (this.cfg.check_dynamic !== undefined) {
|
|
80
|
+
this.cfg.check.dynamic = !!this.cfg.check_dynamic;
|
|
84
81
|
}
|
|
85
|
-
if (
|
|
86
|
-
|
|
82
|
+
if (this.cfg.check_raw_ip !== undefined) {
|
|
83
|
+
this.cfg.check.bare_ip = !!this.cfg.check_raw_ip;
|
|
87
84
|
}
|
|
88
85
|
|
|
89
86
|
// non-default setting, so apply their localized setting
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
if (this.cfg.check.mismatch !== undefined && !this.cfg.check.mismatch) {
|
|
88
|
+
this.logerror('deprecated setting mismatch renamed to host_mismatch');
|
|
89
|
+
this.cfg.check.host_mismatch = this.cfg.check.mismatch;
|
|
93
90
|
}
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
if (this.cfg.reject.mismatch !== undefined && this.cfg.reject.mismatch) {
|
|
92
|
+
this.logerror('deprecated setting mismatch renamed to host_mismatch');
|
|
93
|
+
this.cfg.reject.host_mismatch = this.cfg.reject.mismatch;
|
|
97
94
|
}
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
exports.init = function (next, connection, helo) {
|
|
101
|
-
const plugin = this;
|
|
102
98
|
|
|
103
99
|
const hc = connection.results.get('helo.checks');
|
|
104
100
|
if (!hc) { // first HELO result
|
|
105
|
-
connection.results.add(
|
|
101
|
+
connection.results.add(this, {helo_host: helo});
|
|
106
102
|
return next();
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
|
|
110
|
-
connection.results.add(plugin, {multi: true});
|
|
111
|
-
|
|
112
|
-
return next();
|
|
105
|
+
next();
|
|
113
106
|
}
|
|
114
107
|
|
|
115
108
|
exports.should_skip = function (connection, test_name) {
|
|
116
|
-
const plugin = this;
|
|
117
|
-
|
|
118
|
-
const hc = connection.results.get('helo.checks');
|
|
119
|
-
if (hc && hc.multi && test_name !== 'host_mismatch' && test_name !== 'proto_mismatch') {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
109
|
|
|
123
|
-
if (
|
|
124
|
-
connection.results.add(
|
|
110
|
+
if (this.cfg.skip.relaying && connection.relaying) {
|
|
111
|
+
connection.results.add(this, {skip: `${test_name}(relay)`});
|
|
125
112
|
return true;
|
|
126
113
|
}
|
|
127
114
|
|
|
128
|
-
if (
|
|
129
|
-
connection.results.add(
|
|
115
|
+
if (this.cfg.skip.private_ip && connection.remote.is_private) {
|
|
116
|
+
connection.results.add(this, {skip: `${test_name}(private)`});
|
|
130
117
|
return true;
|
|
131
118
|
}
|
|
132
119
|
|
|
@@ -134,42 +121,39 @@ exports.should_skip = function (connection, test_name) {
|
|
|
134
121
|
}
|
|
135
122
|
|
|
136
123
|
exports.host_mismatch = function (next, connection, helo) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (plugin.should_skip(connection, 'host_mismatch')) { return next(); }
|
|
124
|
+
if (this.should_skip(connection, 'host_mismatch')) return next();
|
|
140
125
|
|
|
141
126
|
const prev_helo = connection.results.get('helo.checks').helo_host;
|
|
142
127
|
if (!prev_helo) {
|
|
143
|
-
connection.results.add(
|
|
128
|
+
connection.results.add(this, {skip: 'host_mismatch(1st)'});
|
|
144
129
|
connection.notes.prev_helo = helo;
|
|
145
130
|
return next();
|
|
146
131
|
}
|
|
147
132
|
|
|
148
133
|
if (prev_helo === helo) {
|
|
149
|
-
connection.results.add(
|
|
134
|
+
connection.results.add(this, {pass: 'host_mismatch'});
|
|
150
135
|
return next();
|
|
151
136
|
}
|
|
152
137
|
|
|
153
138
|
const msg = `host_mismatch(${prev_helo} / ${helo})`;
|
|
154
|
-
connection.results.add(
|
|
155
|
-
if (!
|
|
139
|
+
connection.results.add(this, {fail: msg});
|
|
140
|
+
if (!this.cfg.reject.host_mismatch) return next();
|
|
156
141
|
|
|
157
|
-
|
|
142
|
+
next(DENY, `HELO host ${msg}`);
|
|
158
143
|
}
|
|
159
144
|
|
|
160
145
|
exports.valid_hostname = function (next, connection, helo) {
|
|
161
|
-
const plugin = this;
|
|
162
146
|
|
|
163
|
-
if (
|
|
147
|
+
if (this.should_skip(connection, 'valid_hostname')) return next();
|
|
164
148
|
|
|
165
149
|
if (net_utils.is_ip_literal(helo)) {
|
|
166
|
-
connection.results.add(
|
|
150
|
+
connection.results.add(this, {skip: 'valid_hostname(literal)'});
|
|
167
151
|
return next();
|
|
168
152
|
}
|
|
169
153
|
|
|
170
154
|
if (!/\./.test(helo)) {
|
|
171
|
-
connection.results.add(
|
|
172
|
-
if (
|
|
155
|
+
connection.results.add(this, {fail: 'valid_hostname(no_dot)'});
|
|
156
|
+
if (this.cfg.reject.valid_hostname) {
|
|
173
157
|
return next(DENY, 'HELO host must be a FQDN or address literal (RFC 5321 2.3.5)');
|
|
174
158
|
}
|
|
175
159
|
return next();
|
|
@@ -184,290 +168,281 @@ exports.valid_hostname = function (next, connection, helo) {
|
|
|
184
168
|
if (tld === 'local' || tld === 'lan' || tld === 'corp' || excludes.includes(`.${tld}`)) {
|
|
185
169
|
return next();
|
|
186
170
|
}
|
|
187
|
-
connection.results.add(
|
|
188
|
-
if (
|
|
171
|
+
connection.results.add(this, {fail: 'valid_hostname'});
|
|
172
|
+
if (this.cfg.reject.valid_hostname) {
|
|
189
173
|
return next(DENY, "HELO host name invalid");
|
|
190
174
|
}
|
|
191
175
|
return next();
|
|
192
176
|
}
|
|
193
177
|
|
|
194
|
-
connection.results.add(
|
|
195
|
-
|
|
178
|
+
connection.results.add(this, {pass: 'valid_hostname'});
|
|
179
|
+
next();
|
|
196
180
|
}
|
|
197
181
|
|
|
198
182
|
exports.match_re = function (next, connection, helo) {
|
|
199
|
-
const plugin = this;
|
|
200
183
|
|
|
201
|
-
if (
|
|
184
|
+
if (this.should_skip(connection, 'match_re')) return next();
|
|
202
185
|
|
|
203
|
-
if (
|
|
204
|
-
connection.results.add(
|
|
205
|
-
if (
|
|
186
|
+
if (this.cfg.list_re?.test(helo)) {
|
|
187
|
+
connection.results.add(this, {fail: 'match_re'});
|
|
188
|
+
if (this.cfg.reject.match_re) {
|
|
206
189
|
return next(DENY, "That HELO not allowed here");
|
|
207
190
|
}
|
|
208
191
|
return next();
|
|
209
192
|
}
|
|
210
|
-
connection.results.add(
|
|
211
|
-
|
|
193
|
+
connection.results.add(this, {pass: 'match_re'});
|
|
194
|
+
next();
|
|
212
195
|
}
|
|
213
196
|
|
|
214
197
|
exports.rdns_match = function (next, connection, helo) {
|
|
215
|
-
const plugin = this;
|
|
216
198
|
|
|
217
|
-
if (
|
|
199
|
+
if (this.should_skip(connection, 'rdns_match')) return next();
|
|
218
200
|
|
|
219
201
|
if (!helo) {
|
|
220
|
-
connection.results.add(
|
|
202
|
+
connection.results.add(this, {fail: 'rdns_match(empty)'});
|
|
221
203
|
return next();
|
|
222
204
|
}
|
|
223
205
|
|
|
224
206
|
if (net_utils.is_ip_literal(helo)) {
|
|
225
|
-
connection.results.add(
|
|
207
|
+
connection.results.add(this, {fail: 'rdns_match(literal)'});
|
|
226
208
|
return next();
|
|
227
209
|
}
|
|
228
210
|
|
|
229
211
|
const r_host = connection.remote.host;
|
|
230
212
|
if (r_host && helo === r_host) {
|
|
231
|
-
connection.results.add(
|
|
213
|
+
connection.results.add(this, {pass: 'rdns_match'});
|
|
232
214
|
return next();
|
|
233
215
|
}
|
|
234
216
|
|
|
235
217
|
if (tlds.get_organizational_domain(r_host) ===
|
|
236
218
|
tlds.get_organizational_domain(helo)) {
|
|
237
|
-
connection.results.add(
|
|
219
|
+
connection.results.add(this, {pass: 'rdns_match(org_dom)'});
|
|
238
220
|
return next();
|
|
239
221
|
}
|
|
240
222
|
|
|
241
|
-
connection.results.add(
|
|
242
|
-
if (
|
|
223
|
+
connection.results.add(this, {fail: 'rdns_match'});
|
|
224
|
+
if (this.cfg.reject.rdns_match) {
|
|
243
225
|
return next(DENY, 'HELO host does not match rDNS');
|
|
244
226
|
}
|
|
245
|
-
|
|
227
|
+
next();
|
|
246
228
|
}
|
|
247
229
|
|
|
248
230
|
exports.bare_ip = function (next, connection, helo) {
|
|
249
|
-
const plugin = this;
|
|
250
231
|
|
|
251
|
-
if (
|
|
232
|
+
if (this.should_skip(connection, 'bare_ip')) return next();
|
|
252
233
|
|
|
253
234
|
// RFC 2821, 4.1.1.1 Address literals must be in brackets
|
|
254
235
|
// RAW IPs must be formatted: "[1.2.3.4]" not "1.2.3.4" in HELO
|
|
255
236
|
if (net_utils.get_ipany_re('^(?:IPv6:)?','$','').test(helo)) {
|
|
256
|
-
connection.results.add(
|
|
257
|
-
if (
|
|
237
|
+
connection.results.add(this, {fail: 'bare_ip(invalid literal)'});
|
|
238
|
+
if (this.cfg.reject.bare_ip) {
|
|
258
239
|
return next(DENY, "Invalid address format in HELO");
|
|
259
240
|
}
|
|
260
241
|
return next();
|
|
261
242
|
}
|
|
262
243
|
|
|
263
|
-
connection.results.add(
|
|
264
|
-
|
|
244
|
+
connection.results.add(this, {pass: 'bare_ip'});
|
|
245
|
+
next();
|
|
265
246
|
}
|
|
266
247
|
|
|
267
248
|
exports.dynamic = function (next, connection, helo) {
|
|
268
|
-
const plugin = this;
|
|
269
249
|
|
|
270
|
-
if (
|
|
250
|
+
if (this.should_skip(connection, 'dynamic')) return next();
|
|
271
251
|
|
|
272
252
|
// Skip if no dots or an IP literal or address
|
|
273
253
|
if (!/\./.test(helo)) {
|
|
274
|
-
connection.results.add(
|
|
254
|
+
connection.results.add(this, {skip: 'dynamic(no dots)'});
|
|
275
255
|
return next();
|
|
276
256
|
}
|
|
277
257
|
|
|
278
258
|
if (net_utils.get_ipany_re('^\\[?(?:IPv6:)?','\\]?$','').test(helo)) {
|
|
279
|
-
connection.results.add(
|
|
259
|
+
connection.results.add(this, {skip: 'dynamic(literal)'});
|
|
280
260
|
return next();
|
|
281
261
|
}
|
|
282
262
|
|
|
283
263
|
if (net_utils.is_ip_in_str(connection.remote.ip, helo)) {
|
|
284
|
-
connection.results.add(
|
|
285
|
-
if (
|
|
264
|
+
connection.results.add(this, {fail: 'dynamic'});
|
|
265
|
+
if (this.cfg.reject.dynamic) {
|
|
286
266
|
return next(DENY, 'HELO is dynamic');
|
|
287
267
|
}
|
|
288
268
|
return next();
|
|
289
269
|
}
|
|
290
270
|
|
|
291
|
-
connection.results.add(
|
|
292
|
-
|
|
271
|
+
connection.results.add(this, {pass: 'dynamic'});
|
|
272
|
+
next();
|
|
293
273
|
}
|
|
294
274
|
|
|
295
275
|
exports.big_company = function (next, connection, helo) {
|
|
296
|
-
const plugin = this;
|
|
297
276
|
|
|
298
|
-
if (
|
|
277
|
+
if (this.should_skip(connection, 'big_company')) return next();
|
|
299
278
|
|
|
300
279
|
if (net_utils.is_ip_literal(helo)) {
|
|
301
|
-
connection.results.add(
|
|
280
|
+
connection.results.add(this, {skip: 'big_co(literal)'});
|
|
302
281
|
return next();
|
|
303
282
|
}
|
|
304
283
|
|
|
305
|
-
if (!
|
|
306
|
-
connection.results.add(
|
|
284
|
+
if (!this.cfg.bigco) {
|
|
285
|
+
connection.results.add(this, {err: 'big_co(config missing)'});
|
|
307
286
|
return next();
|
|
308
287
|
}
|
|
309
288
|
|
|
310
|
-
if (!
|
|
311
|
-
connection.results.add(
|
|
289
|
+
if (!this.cfg.bigco[helo]) {
|
|
290
|
+
connection.results.add(this, {pass: 'big_co(not)'});
|
|
312
291
|
return next();
|
|
313
292
|
}
|
|
314
293
|
|
|
315
294
|
const rdns = connection.remote.host;
|
|
316
295
|
if (!rdns || rdns === 'Unknown' || rdns === 'DNSERROR') {
|
|
317
|
-
connection.results.add(
|
|
318
|
-
if (
|
|
296
|
+
connection.results.add(this, {fail: 'big_co(rDNS)'});
|
|
297
|
+
if (this.cfg.reject.big_company) {
|
|
319
298
|
return next(DENY, "Big company w/o rDNS? Unlikely.");
|
|
320
299
|
}
|
|
321
300
|
return next();
|
|
322
301
|
}
|
|
323
302
|
|
|
324
|
-
const allowed_rdns =
|
|
325
|
-
for (
|
|
326
|
-
const re = new RegExp(`${
|
|
303
|
+
const allowed_rdns = this.cfg.bigco[helo].split(/,/);
|
|
304
|
+
for (const allow of allowed_rdns) {
|
|
305
|
+
const re = new RegExp(`${allow.replace(/\./g, '\\.')}$`);
|
|
327
306
|
if (re.test(rdns)) {
|
|
328
|
-
connection.results.add(
|
|
307
|
+
connection.results.add(this, {pass: 'big_co'});
|
|
329
308
|
return next();
|
|
330
309
|
}
|
|
331
310
|
}
|
|
332
311
|
|
|
333
|
-
connection.results.add(
|
|
334
|
-
if (
|
|
312
|
+
connection.results.add(this, {fail: 'big_co'});
|
|
313
|
+
if (this.cfg.reject.big_company) {
|
|
335
314
|
return next(DENY, "You are not who you say you are");
|
|
336
315
|
}
|
|
337
|
-
|
|
316
|
+
next();
|
|
338
317
|
}
|
|
339
318
|
|
|
340
319
|
exports.literal_mismatch = function (next, connection, helo) {
|
|
341
|
-
const plugin = this;
|
|
342
320
|
|
|
343
|
-
if (
|
|
321
|
+
if (this.should_skip(connection, 'literal_mismatch')) return next();
|
|
344
322
|
|
|
345
323
|
const literal = net_utils.get_ipany_re('^\\[(?:IPv6:)?','\\]$','').exec(helo);
|
|
346
324
|
if (!literal) {
|
|
347
|
-
connection.results.add(
|
|
325
|
+
connection.results.add(this, {pass: 'literal_mismatch'});
|
|
348
326
|
return next();
|
|
349
327
|
}
|
|
350
328
|
|
|
351
|
-
const lmm_mode = parseInt(
|
|
329
|
+
const lmm_mode = parseInt(this.cfg.check.literal_mismatch, 10);
|
|
352
330
|
const helo_ip = literal[1];
|
|
353
331
|
if (lmm_mode > 2 && net_utils.is_private_ip(helo_ip)) {
|
|
354
|
-
connection.results.add(
|
|
332
|
+
connection.results.add(this, {pass: 'literal_mismatch(private)'});
|
|
355
333
|
return next();
|
|
356
334
|
}
|
|
357
335
|
|
|
358
336
|
if (lmm_mode > 1) {
|
|
359
337
|
if (net_utils.same_ipv4_network(connection.remote.ip, [helo_ip])) {
|
|
360
|
-
connection.results.add(
|
|
338
|
+
connection.results.add(this, {pass: 'literal_mismatch'});
|
|
361
339
|
return next();
|
|
362
340
|
}
|
|
363
341
|
|
|
364
|
-
connection.results.add(
|
|
365
|
-
if (
|
|
342
|
+
connection.results.add(this, {fail: 'literal_mismatch'});
|
|
343
|
+
if (this.cfg.reject.literal_mismatch) {
|
|
366
344
|
return next(DENY, 'HELO IP literal not in the same /24 as your IP address');
|
|
367
345
|
}
|
|
368
346
|
return next();
|
|
369
347
|
}
|
|
370
348
|
|
|
371
349
|
if (helo_ip === connection.remote.ip) {
|
|
372
|
-
connection.results.add(
|
|
350
|
+
connection.results.add(this, {pass: 'literal_mismatch'});
|
|
373
351
|
return next();
|
|
374
352
|
}
|
|
375
353
|
|
|
376
|
-
connection.results.add(
|
|
377
|
-
if (
|
|
354
|
+
connection.results.add(this, {fail: 'literal_mismatch'});
|
|
355
|
+
if (this.cfg.reject.literal_mismatch) {
|
|
378
356
|
return next(DENY, 'HELO IP literal does not match your IP address');
|
|
379
357
|
}
|
|
380
|
-
|
|
358
|
+
next();
|
|
381
359
|
}
|
|
382
360
|
|
|
383
361
|
exports.forward_dns = function (next, connection, helo) {
|
|
384
|
-
const plugin = this;
|
|
385
362
|
|
|
386
|
-
if (
|
|
387
|
-
if (!
|
|
388
|
-
connection.results.add(
|
|
363
|
+
if (this.should_skip(connection, 'forward_dns')) return next();
|
|
364
|
+
if (!this.cfg.check.valid_hostname) {
|
|
365
|
+
connection.results.add(this, {err: 'forward_dns(valid_hostname disabled)'});
|
|
389
366
|
return next();
|
|
390
367
|
}
|
|
391
368
|
|
|
392
369
|
if (net_utils.is_ip_literal(helo)) {
|
|
393
|
-
connection.results.add(
|
|
370
|
+
connection.results.add(this, {skip: 'forward_dns(literal)'});
|
|
394
371
|
return next();
|
|
395
372
|
}
|
|
396
373
|
|
|
397
374
|
if (!connection.results.has('helo.checks', 'pass', /^valid_hostname/)) {
|
|
398
|
-
connection.results.add(
|
|
399
|
-
if (
|
|
375
|
+
connection.results.add(this, {fail: 'forward_dns(invalid_hostname)'});
|
|
376
|
+
if (this.cfg.reject.forward_dns) {
|
|
400
377
|
return next(DENY, "Invalid HELO host cannot achieve forward DNS match");
|
|
401
378
|
}
|
|
402
379
|
return next();
|
|
403
380
|
}
|
|
404
381
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
382
|
+
this.get_a_records(helo)
|
|
383
|
+
.then(ips => {
|
|
384
|
+
|
|
385
|
+
if (!ips) {
|
|
386
|
+
connection.results.add(this, {err: 'forward_dns, no ips!'});
|
|
409
387
|
return next();
|
|
410
388
|
}
|
|
411
|
-
|
|
412
|
-
connection.results.add(plugin, {fail: `forward_dns(${err.code})`});
|
|
413
|
-
return next(DENYSOFT, "DNS timeout resolving your HELO hostname");
|
|
414
|
-
}
|
|
415
|
-
connection.results.add(plugin, {err: `forward_dns(${err})`, emit_log_level: 'warn'});
|
|
416
|
-
return next();
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (!ips) {
|
|
420
|
-
connection.results.add(plugin, {err: 'forward_dns, no ips!'});
|
|
421
|
-
return next();
|
|
422
|
-
}
|
|
423
|
-
connection.results.add(plugin, {ips});
|
|
389
|
+
connection.results.add(this, {ips});
|
|
424
390
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
return next();
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// some valid hosts (facebook.com, hotmail.com, ) use a generic HELO
|
|
431
|
-
// hostname that resolves but doesn't contain the IP that is
|
|
432
|
-
// connecting. If their rDNS passed, and their HELO hostname is in
|
|
433
|
-
// the same domain, consider it close enough.
|
|
434
|
-
if (connection.results.has('helo.checks', 'pass', /^rdns_match/)) {
|
|
435
|
-
const helo_od = tlds.get_organizational_domain(helo);
|
|
436
|
-
const rdns_od = tlds.get_organizational_domain(connection.remote.host);
|
|
437
|
-
if (helo_od && helo_od === rdns_od) {
|
|
438
|
-
connection.results.add(plugin, {pass: 'forward_dns(domain)'});
|
|
391
|
+
if (ips.includes(connection.remote.ip)) {
|
|
392
|
+
connection.results.add(this, {pass: 'forward_dns'});
|
|
439
393
|
return next();
|
|
440
394
|
}
|
|
441
|
-
connection.results.add(plugin, {msg: `od miss: ${helo_od}, ${rdns_od}`});
|
|
442
|
-
}
|
|
443
395
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
396
|
+
// some valid hosts (facebook.com, hotmail.com) use a generic HELO
|
|
397
|
+
// hostname that resolves but doesn't contain the IP that is
|
|
398
|
+
// connecting. If their rDNS passed, and their HELO hostname is in
|
|
399
|
+
// the same domain, consider it close enough.
|
|
400
|
+
if (connection.results.has('helo.checks', 'pass', /^rdns_match/)) {
|
|
401
|
+
const helo_od = tlds.get_organizational_domain(helo);
|
|
402
|
+
const rdns_od = tlds.get_organizational_domain(connection.remote.host);
|
|
403
|
+
if (helo_od && helo_od === rdns_od) {
|
|
404
|
+
connection.results.add(this, {pass: 'forward_dns(domain)'});
|
|
405
|
+
return next();
|
|
406
|
+
}
|
|
407
|
+
connection.results.add(this, {msg: `od miss: ${helo_od}, ${rdns_od}`});
|
|
408
|
+
}
|
|
450
409
|
|
|
451
|
-
|
|
410
|
+
connection.results.add(this, {fail: 'forward_dns(no IP match)'});
|
|
411
|
+
if (this.cfg.reject.forward_dns) {
|
|
412
|
+
return next(DENY, "HELO host has no forward DNS match");
|
|
413
|
+
}
|
|
414
|
+
next();
|
|
415
|
+
})
|
|
416
|
+
.catch(err => {
|
|
417
|
+
if (err.code === dns.NOTFOUND || err.code === dns.NODATA || err.code === dns.SERVFAIL) {
|
|
418
|
+
connection.results.add(this, {fail: `forward_dns(${err.code})`});
|
|
419
|
+
return next();
|
|
420
|
+
}
|
|
421
|
+
if (err.code === dns.TIMEOUT && this.cfg.reject.forward_dns) {
|
|
422
|
+
connection.results.add(this, {fail: `forward_dns(${err.code})`});
|
|
423
|
+
return next(DENYSOFT, "DNS timeout resolving your HELO hostname");
|
|
424
|
+
}
|
|
425
|
+
connection.results.add(this, {err: `forward_dns(${err})`, emit_log_level: 'warn'});
|
|
426
|
+
next();
|
|
427
|
+
})
|
|
452
428
|
}
|
|
453
429
|
|
|
454
430
|
exports.proto_mismatch = function (next, connection, helo, proto) {
|
|
455
|
-
const plugin = this;
|
|
456
431
|
|
|
457
|
-
if (
|
|
432
|
+
if (this.should_skip(connection, 'proto_mismatch')) return next();
|
|
458
433
|
|
|
459
434
|
const r = connection.results.get('helo.checks');
|
|
460
|
-
if (!r || (r && !r.helo_host))
|
|
435
|
+
if (!r || (r && !r.helo_host)) return next();
|
|
461
436
|
|
|
462
437
|
if ((connection.esmtp && proto === 'smtp') ||
|
|
463
438
|
(!connection.esmtp && proto === 'esmtp')) {
|
|
464
|
-
connection.results.add(
|
|
465
|
-
if (
|
|
439
|
+
connection.results.add(this, {fail: `proto_mismatch(${proto})`});
|
|
440
|
+
if (this.cfg.reject.proto_mismatch) {
|
|
466
441
|
return next(DENY, `${proto === 'smtp' ? 'HELO' : 'EHLO'} protocol mismatch`);
|
|
467
442
|
}
|
|
468
443
|
}
|
|
469
444
|
|
|
470
|
-
|
|
445
|
+
next();
|
|
471
446
|
}
|
|
472
447
|
|
|
473
448
|
exports.proto_mismatch_smtp = function (next, connection, helo) {
|
|
@@ -479,7 +454,6 @@ exports.proto_mismatch_esmtp = function (next, connection, helo) {
|
|
|
479
454
|
}
|
|
480
455
|
|
|
481
456
|
exports.emit_log = function (next, connection, helo) {
|
|
482
|
-
const plugin = this;
|
|
483
457
|
// Spits out an INFO log entry. Default looks like this:
|
|
484
458
|
// [helo.checks] helo_host: [182.212.17.35], fail:big_co(rDNS) rdns_match(literal), pass:valid_hostname, match_re, bare_ip, literal_mismatch, mismatch, skip:dynamic(literal), valid_hostname(literal)
|
|
485
459
|
//
|
|
@@ -496,18 +470,17 @@ exports.emit_log = function (next, connection, helo) {
|
|
|
496
470
|
// [UUID] [helo.checks] fail:rdns_match
|
|
497
471
|
// [UUID] [helo.checks]
|
|
498
472
|
// [UUID] [helo.checks] fail:dynamic
|
|
499
|
-
connection.loginfo(
|
|
500
|
-
|
|
473
|
+
connection.loginfo(this, connection.results.collate(this));
|
|
474
|
+
next();
|
|
501
475
|
}
|
|
502
476
|
|
|
503
|
-
exports.get_a_records = function (host
|
|
504
|
-
const plugin = this;
|
|
477
|
+
exports.get_a_records = async function (host) {
|
|
505
478
|
|
|
506
479
|
if (!/\./.test(host)) {
|
|
507
480
|
// a single label is not a host name
|
|
508
481
|
const e = new Error("invalid hostname");
|
|
509
482
|
e.code = dns.NOTFOUND;
|
|
510
|
-
|
|
483
|
+
throw e;
|
|
511
484
|
}
|
|
512
485
|
|
|
513
486
|
// Set-up timer
|
|
@@ -516,19 +489,20 @@ exports.get_a_records = function (host, cb) {
|
|
|
516
489
|
timed_out = true;
|
|
517
490
|
const err = new Error(`timeout resolving: ${host}`);
|
|
518
491
|
err.code = dns.TIMEOUT;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}, (
|
|
492
|
+
this.logerror(err);
|
|
493
|
+
throw err;
|
|
494
|
+
}, (this.cfg.main.dns_timeout || 30) * 1000);
|
|
522
495
|
|
|
523
496
|
// fully qualify, to ignore any search options in /etc/resolv.conf
|
|
524
|
-
if (!/\.$/.test(host))
|
|
497
|
+
if (!/\.$/.test(host)) host = `${host}.`;
|
|
525
498
|
|
|
526
499
|
// do the queries
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
500
|
+
let ips
|
|
501
|
+
let err = '';
|
|
502
|
+
try {
|
|
503
|
+
ips = await net_utils.get_ips_by_host(host)
|
|
504
|
+
}
|
|
505
|
+
catch (errs) {
|
|
532
506
|
for (const error of errs) {
|
|
533
507
|
switch (error.code) {
|
|
534
508
|
case dns.NODATA:
|
|
@@ -539,9 +513,13 @@ exports.get_a_records = function (host, cb) {
|
|
|
539
513
|
err = `${err}, ${error.message}`;
|
|
540
514
|
}
|
|
541
515
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// results is now equals to: {queryA: 1, queryAAAA: 2}
|
|
519
|
+
if (timed_out) return;
|
|
520
|
+
if (timer) clearTimeout(timer);
|
|
521
|
+
if (!ips.length && err) throw err
|
|
522
|
+
// this.logdebug(this, host + ' => ' + ips);
|
|
523
|
+
// return the DNS results
|
|
524
|
+
return ips;
|
|
525
|
+
};
|