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/dkim_sign.js
CHANGED
|
@@ -6,7 +6,7 @@ const async = require('async');
|
|
|
6
6
|
const crypto = require('crypto');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const Stream = require('stream')
|
|
9
|
+
const { Stream } = require('stream');
|
|
10
10
|
|
|
11
11
|
const utils = require('haraka-utils');
|
|
12
12
|
|
|
@@ -106,22 +106,26 @@ class DKIMSignStream extends Stream {
|
|
|
106
106
|
*/
|
|
107
107
|
|
|
108
108
|
const headers = [];
|
|
109
|
-
for (
|
|
110
|
-
let head = this.header.get(
|
|
109
|
+
for (const element of this.headers_to_sign) {
|
|
110
|
+
let head = this.header.get(element);
|
|
111
111
|
if (head) {
|
|
112
112
|
head = head.replace(/\r?\n/gm, '');
|
|
113
113
|
head = head.replace(/\s+/gm, ' ');
|
|
114
114
|
head = head.replace(/\s+$/gm, '');
|
|
115
|
-
this.signer.update(`${
|
|
116
|
-
headers.push(
|
|
115
|
+
this.signer.update(`${element}:${head}\r\n`);
|
|
116
|
+
headers.push(element);
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// Create DKIM header
|
|
121
|
-
let dkim_header = `v=1;a=rsa-sha256;
|
|
121
|
+
let dkim_header = `v=1; a=rsa-sha256; c=relaxed/simple; d=${this.domain_name}; s=${this.selector}; h=${headers.join(':')}; bh=${bodyhash}; b=`;
|
|
122
122
|
this.signer.update(`dkim-signature:${dkim_header}`);
|
|
123
123
|
const signature = this.signer.sign(this.private_key, 'base64');
|
|
124
|
-
dkim_header
|
|
124
|
+
dkim_header = `v=1; a=rsa-sha256; c=relaxed/simple;\r\n\td=${this.domain_name}; s=${this.selector};\r\n\th=${headers.join(':')};\r\n\tbh=${bodyhash};\r\n\tb=`;
|
|
125
|
+
dkim_header += signature.substring(0,74);
|
|
126
|
+
for (let i=74; i<signature.length; i+=76) {
|
|
127
|
+
dkim_header += `\r\n\t${signature.substring(i, i+76)}`;
|
|
128
|
+
}
|
|
125
129
|
|
|
126
130
|
if (this.end_callback) this.end_callback(null, dkim_header);
|
|
127
131
|
this.end_callback = null;
|
|
@@ -139,28 +143,25 @@ class DKIMSignStream extends Stream {
|
|
|
139
143
|
exports.DKIMSignStream = DKIMSignStream;
|
|
140
144
|
|
|
141
145
|
exports.register = function () {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
plugin.load_dkim_default_key();
|
|
146
|
+
this.load_dkim_sign_ini();
|
|
147
|
+
this.load_dkim_default_key();
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
exports.load_dkim_sign_ini = function () {
|
|
148
|
-
|
|
149
|
-
plugin.cfg = plugin.config.get('dkim_sign.ini', {
|
|
151
|
+
this.cfg = this.config.get('dkim_sign.ini', {
|
|
150
152
|
booleans: [
|
|
151
153
|
'-disabled',
|
|
152
154
|
]
|
|
153
155
|
},
|
|
154
|
-
() => {
|
|
156
|
+
() => { this.load_dkim_sign_ini(); }
|
|
155
157
|
);
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
this.cfg.headers_to_sign = this.get_headers_to_sign();
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
exports.load_dkim_default_key = function () {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
plugin.load_dkim_default_key();
|
|
163
|
+
this.private_key = this.config.get('dkim.private.key', 'data', () => {
|
|
164
|
+
this.load_dkim_default_key();
|
|
164
165
|
}).join('\n');
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -169,37 +170,35 @@ exports.load_key = function (file) {
|
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
exports.hook_queue_outbound = exports.hook_pre_send_trans_email = function (next, connection) {
|
|
172
|
-
|
|
173
|
-
if (
|
|
173
|
+
if (this.cfg.main.disabled) return next();
|
|
174
|
+
if (!connection?.transaction) return next();
|
|
174
175
|
|
|
175
|
-
if (connection.transaction.notes
|
|
176
|
-
connection.logdebug(
|
|
176
|
+
if (connection.transaction.notes?.dkim_signed) {
|
|
177
|
+
connection.logdebug(this, 'already signed');
|
|
177
178
|
return next();
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
exports.get_sign_properties(connection, (err, props) => {
|
|
182
|
+
if (!connection?.transaction) return next();
|
|
181
183
|
// props: selector, domain, & private_key
|
|
182
|
-
if (err) connection.logerror(
|
|
184
|
+
if (err) connection.logerror(this, `${err.message}`);
|
|
183
185
|
|
|
184
|
-
if (!
|
|
185
|
-
connection.logerror(`missing key data for ${props.selector}.${props.domain}`)
|
|
186
|
-
return next();
|
|
187
|
-
}
|
|
186
|
+
if (!this.has_key_data(connection, props)) return next();
|
|
188
187
|
|
|
189
|
-
connection.logdebug(
|
|
188
|
+
connection.logdebug(this, `domain: ${props.domain}`);
|
|
190
189
|
|
|
191
190
|
const txn = connection.transaction;
|
|
192
|
-
props.headers =
|
|
191
|
+
props.headers = this.cfg.headers_to_sign;
|
|
193
192
|
|
|
194
193
|
txn.message_stream.pipe(
|
|
195
194
|
new DKIMSignStream(props, txn.header, (err2, dkim_header) => {
|
|
196
195
|
if (err2) {
|
|
197
|
-
txn.results.add(
|
|
196
|
+
txn.results.add(this, {err: err2.message});
|
|
198
197
|
return next(err2);
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
connection.loginfo(
|
|
202
|
-
txn.results.add(
|
|
200
|
+
connection.loginfo(this, `signed for ${props.domain}`);
|
|
201
|
+
txn.results.add(this, {pass: dkim_header});
|
|
203
202
|
txn.add_header('DKIM-Signature', dkim_header);
|
|
204
203
|
|
|
205
204
|
connection.transaction.notes.dkim_signed = true;
|
|
@@ -210,34 +209,36 @@ exports.hook_queue_outbound = exports.hook_pre_send_trans_email = function (next
|
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
exports.get_sign_properties = function (connection, done) {
|
|
213
|
-
|
|
212
|
+
if (!connection.transaction) return;
|
|
214
213
|
|
|
215
|
-
const domain =
|
|
214
|
+
const domain = this.get_sender_domain(connection);
|
|
216
215
|
|
|
217
216
|
if (!domain) {
|
|
218
|
-
connection.transaction.results.add(
|
|
217
|
+
connection.transaction.results.add(this, {msg: 'sending domain not detected', emit: true });
|
|
219
218
|
}
|
|
220
219
|
|
|
221
220
|
const props = { domain }
|
|
222
221
|
|
|
223
|
-
|
|
222
|
+
this.get_key_dir(connection, props, (err, keydir) => {
|
|
224
223
|
if (err) {
|
|
225
224
|
console.error(`err: ${err}`);
|
|
226
|
-
connection.logerror(
|
|
225
|
+
connection.logerror(this, err);
|
|
227
226
|
return done(new Error(`Error getting DKIM key_dir for ${domain}: ${err}`), props)
|
|
228
227
|
}
|
|
229
228
|
|
|
229
|
+
if (!connection.transaction) return done(null, props);
|
|
230
|
+
|
|
230
231
|
// a directory for ${domain} exists
|
|
231
232
|
if (keydir) {
|
|
232
233
|
props.domain = path.basename(keydir); // keydir might be apex (vs sub)domain
|
|
233
|
-
props.private_key =
|
|
234
|
-
props.selector =
|
|
234
|
+
props.private_key = this.load_key(path.join('dkim', props.domain, 'private'));
|
|
235
|
+
props.selector = this.load_key(path.join('dkim', props.domain, 'selector')).trim();
|
|
235
236
|
|
|
236
237
|
if (!props.selector) {
|
|
237
|
-
connection.transaction.results.add(
|
|
238
|
+
connection.transaction.results.add(this, {err: `missing selector for domain ${domain}`});
|
|
238
239
|
}
|
|
239
240
|
if (!props.private_key) {
|
|
240
|
-
connection.transaction.results.add(
|
|
241
|
+
connection.transaction.results.add(this, {err: `missing dkim private_key for domain ${domain}`});
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
if (props.selector && props.private_key ) { // AND has correct files
|
|
@@ -246,13 +247,13 @@ exports.get_sign_properties = function (connection, done) {
|
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
// try [default / single domain] configuration
|
|
249
|
-
if (
|
|
250
|
+
if (this.cfg.main.domain && this.cfg.main.selector && this.private_key) {
|
|
250
251
|
|
|
251
|
-
connection.transaction.results.add(
|
|
252
|
+
connection.transaction.results.add(this, {msg: 'using default key', emit: true });
|
|
252
253
|
|
|
253
|
-
props.domain =
|
|
254
|
-
props.private_key =
|
|
255
|
-
props.selector =
|
|
254
|
+
props.domain = this.cfg.main.domain;
|
|
255
|
+
props.private_key = this.private_key;
|
|
256
|
+
props.selector = this.cfg.main.selector;
|
|
256
257
|
|
|
257
258
|
return done(null, props)
|
|
258
259
|
}
|
|
@@ -263,7 +264,6 @@ exports.get_sign_properties = function (connection, done) {
|
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
exports.get_key_dir = function (connection, props, done) {
|
|
266
|
-
const plugin = this;
|
|
267
267
|
|
|
268
268
|
if (!props.domain) return done();
|
|
269
269
|
|
|
@@ -285,13 +285,12 @@ exports.get_key_dir = function (connection, props, done) {
|
|
|
285
285
|
});
|
|
286
286
|
},
|
|
287
287
|
(err, results) => {
|
|
288
|
-
connection.logdebug(
|
|
288
|
+
connection.logdebug(this, results);
|
|
289
289
|
done(err, results);
|
|
290
290
|
});
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
exports.has_key_data = function (conn, props) {
|
|
294
|
-
const plugin = this;
|
|
295
294
|
|
|
296
295
|
let missing = undefined;
|
|
297
296
|
|
|
@@ -308,22 +307,21 @@ exports.has_key_data = function (conn, props) {
|
|
|
308
307
|
|
|
309
308
|
if (missing) {
|
|
310
309
|
if (props.domain) {
|
|
311
|
-
conn.lognotice(
|
|
310
|
+
conn.lognotice(this, `skipped: no ${missing} for ${props.domain}`);
|
|
312
311
|
}
|
|
313
312
|
else {
|
|
314
|
-
conn.lognotice(
|
|
313
|
+
conn.lognotice(this, `skipped: no ${missing}`);
|
|
315
314
|
}
|
|
316
315
|
return false;
|
|
317
316
|
}
|
|
318
317
|
|
|
319
|
-
conn.logprotocol(
|
|
318
|
+
conn.logprotocol(this, `using selector: ${props.selector} at domain ${props.domain}`);
|
|
320
319
|
return true;
|
|
321
320
|
}
|
|
322
321
|
|
|
323
322
|
exports.get_headers_to_sign = function (cfg) {
|
|
324
|
-
const plugin = this;
|
|
325
323
|
|
|
326
|
-
if (!cfg) cfg =
|
|
324
|
+
if (!cfg) cfg = this.cfg;
|
|
327
325
|
if (!cfg.main.headers_to_sign) return [ 'from' ];
|
|
328
326
|
|
|
329
327
|
const headers = cfg.main.headers_to_sign
|
|
@@ -338,20 +336,16 @@ exports.get_headers_to_sign = function (cfg) {
|
|
|
338
336
|
}
|
|
339
337
|
|
|
340
338
|
exports.get_sender_domain = function (connection) {
|
|
341
|
-
const plugin = this;
|
|
342
|
-
if (!connection.transaction) {
|
|
343
|
-
connection.logerror(plugin, 'no transaction!')
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
339
|
|
|
347
|
-
const txn = connection
|
|
340
|
+
const txn = connection?.transaction;
|
|
341
|
+
if (!txn) return;
|
|
348
342
|
|
|
349
343
|
// fallback: use Envelope FROM when header parsing fails
|
|
350
344
|
let domain;
|
|
351
345
|
if (txn.mail_from.host) {
|
|
352
346
|
try { domain = txn.mail_from.host.toLowerCase(); }
|
|
353
347
|
catch (e) {
|
|
354
|
-
connection.logerror(
|
|
348
|
+
connection.logerror(this, e);
|
|
355
349
|
}
|
|
356
350
|
}
|
|
357
351
|
|
|
@@ -371,7 +365,7 @@ exports.get_sender_domain = function (connection) {
|
|
|
371
365
|
addrs = addrparser.parse(from_hdr);
|
|
372
366
|
}
|
|
373
367
|
catch (e) {
|
|
374
|
-
connection.logerror(
|
|
368
|
+
connection.logerror(this, `address-rfc2822 failed to parse From header: ${from_hdr}`)
|
|
375
369
|
return domain;
|
|
376
370
|
}
|
|
377
371
|
if (!addrs || ! addrs.length) return domain;
|
|
@@ -394,7 +388,7 @@ exports.get_sender_domain = function (connection) {
|
|
|
394
388
|
domain = (addrparser.parse(sender))[0].host().toLowerCase();
|
|
395
389
|
}
|
|
396
390
|
catch (e) {
|
|
397
|
-
connection.logerror(
|
|
391
|
+
connection.logerror(this, e);
|
|
398
392
|
}
|
|
399
393
|
}
|
|
400
394
|
return domain;
|
package/plugins/dkim_verify.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
const dkim = require('./dkim');
|
|
3
3
|
|
|
4
|
-
const DKIMVerifyStream = dkim
|
|
4
|
+
const { DKIMVerifyStream } = dkim;
|
|
5
5
|
|
|
6
6
|
const plugin = exports;
|
|
7
7
|
|
|
@@ -26,32 +26,33 @@ exports.load_config = function () {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
exports.hook_data_post = function (next, connection) {
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const txn = connection?.transaction;
|
|
30
|
+
if (!txn) return next();
|
|
31
|
+
|
|
31
32
|
const verifier = new DKIMVerifyStream(this.cfg, (err, result, results) => {
|
|
32
33
|
if (err) {
|
|
33
|
-
txn.results.add(
|
|
34
|
+
txn.results.add(this, { err });
|
|
34
35
|
return next();
|
|
35
36
|
}
|
|
36
37
|
if (!results || results.length === 0) {
|
|
37
|
-
txn.results.add(
|
|
38
|
+
txn.results.add(this, { skip: 'no/bad dkim signature' });
|
|
38
39
|
return next(CONT, 'no/bad signature')
|
|
39
40
|
}
|
|
40
41
|
results.forEach((res) => {
|
|
41
42
|
let res_err = '';
|
|
42
43
|
if (res.error) res_err = ` (${res.error})`;
|
|
43
44
|
connection.auth_results(`dkim=${res.result}${res_err} header.i=${res.identity} header.d=${res.domain} header.s=${res.selector}`);
|
|
44
|
-
connection.loginfo(
|
|
45
|
+
connection.loginfo(this, `identity="${res.identity}" domain="${res.domain}" selector="${res.selector}" result=${res.result} ${res_err}`);
|
|
45
46
|
|
|
46
47
|
// save to ResultStore
|
|
47
48
|
const rs_obj = JSON.parse(JSON.stringify(res));
|
|
48
49
|
if (res.result === 'pass') { rs_obj.pass = res.domain; }
|
|
49
50
|
else if (res.result === 'fail') { rs_obj.fail = res.domain + res_err; }
|
|
50
51
|
else { rs_obj.err = res.domain + res_err; }
|
|
51
|
-
txn.results.add(
|
|
52
|
+
txn.results.add(this, rs_obj);
|
|
52
53
|
});
|
|
53
54
|
|
|
54
|
-
connection.logdebug(
|
|
55
|
+
connection.logdebug(this, JSON.stringify(results));
|
|
55
56
|
// Store results for other plugins
|
|
56
57
|
txn.notes.dkim_results = results;
|
|
57
58
|
next();
|
package/plugins/dns_list_base.js
CHANGED
|
@@ -10,7 +10,6 @@ exports.redis_host = '127.0.0.1:6379';
|
|
|
10
10
|
let redis_client;
|
|
11
11
|
|
|
12
12
|
exports.lookup = function (lookup, zone, cb) {
|
|
13
|
-
const self = this;
|
|
14
13
|
|
|
15
14
|
if (!lookup || !zone) {
|
|
16
15
|
return setImmediate(() => cb(new Error('missing data')));
|
|
@@ -33,26 +32,32 @@ exports.lookup = function (lookup, zone, cb) {
|
|
|
33
32
|
|
|
34
33
|
// Build the query, adding the root dot if missing
|
|
35
34
|
let query = [lookup, zone].join('.');
|
|
36
|
-
if (query
|
|
35
|
+
if (!query.endsWith('.')) {
|
|
37
36
|
query += '.';
|
|
38
37
|
}
|
|
39
38
|
this.logdebug(`looking up: ${query}`);
|
|
40
39
|
// IS: IPv6 compatible (maybe; only if BL return IPv4 answers)
|
|
41
40
|
dns.resolve(query, 'A', (err, a) => {
|
|
42
|
-
|
|
41
|
+
this.stats_incr_zone(err, zone, start); // Statistics
|
|
43
42
|
|
|
44
43
|
// Check for a result of 127.0.0.1 or outside 127/8
|
|
45
44
|
// This should *never* happen on a proper DNS list
|
|
46
|
-
if (a && ((!
|
|
45
|
+
if (a && ((!this.lookback_is_rejected && a.includes('127.0.0.1')) ||
|
|
47
46
|
a.find((rec) => { return rec.split('.')[0] !== '127' }))
|
|
48
47
|
) {
|
|
49
|
-
|
|
48
|
+
this.disable_zone(zone, a);
|
|
49
|
+
return cb(err, null); // Return a null A record
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// <https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now>
|
|
53
|
+
if (a?.includes('127.255.255.')) {
|
|
54
|
+
this.disable_zone(zone, a);
|
|
50
55
|
return cb(err, null); // Return a null A record
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
if (err) {
|
|
54
59
|
if (err.code === dns.TIMEOUT) { // list timed out
|
|
55
|
-
|
|
60
|
+
this.disable_zone(zone, err.code); // disable it
|
|
56
61
|
}
|
|
57
62
|
if (err.code === dns.NOTFOUND) { // unlisted
|
|
58
63
|
return cb(null, a); // not an error for a DNSBL
|
|
@@ -63,38 +68,37 @@ exports.lookup = function (lookup, zone, cb) {
|
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
exports.stats_incr_zone = function (err, zone, start) {
|
|
66
|
-
|
|
67
|
-
if (!plugin.enable_stats) return;
|
|
71
|
+
if (!this.enable_stats) return;
|
|
68
72
|
|
|
69
73
|
const rkey = `dns-list-stat:${zone}`;
|
|
70
74
|
const elapsed = new Date().getTime() - start;
|
|
71
|
-
redis_client.
|
|
75
|
+
redis_client.hIncrBy(rkey, 'TOTAL', 1);
|
|
72
76
|
const foo = (err) ? err.code : 'LISTED';
|
|
73
|
-
redis_client.
|
|
74
|
-
redis_client.
|
|
75
|
-
if (err2) return;
|
|
77
|
+
redis_client.hIncrBy(rkey, foo, 1);
|
|
78
|
+
redis_client.hGet(rkey, 'AVG_RT').then(rt => {
|
|
76
79
|
const avg = parseInt(rt) ? (parseInt(elapsed) + parseInt(rt))/2
|
|
77
80
|
: parseInt(elapsed);
|
|
78
|
-
redis_client.
|
|
81
|
+
redis_client.hSet(rkey, 'AVG_RT', avg);
|
|
79
82
|
});
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
exports.init_redis = function () {
|
|
83
|
-
const plugin = this;
|
|
84
86
|
if (redis_client) { return; }
|
|
85
87
|
|
|
86
88
|
const redis = require('redis');
|
|
87
|
-
const host_port =
|
|
89
|
+
const host_port = this.redis_host.split(':');
|
|
88
90
|
const host = host_port[0] || '127.0.0.1';
|
|
89
91
|
const port = parseInt(host_port[1], 10) || 6379;
|
|
90
92
|
|
|
91
93
|
redis_client = redis.createClient(port, host);
|
|
92
|
-
redis_client.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
redis_client.connect().then(() => {
|
|
95
|
+
redis_client.on('error', err => {
|
|
96
|
+
this.logerror(`Redis error: ${err}`);
|
|
97
|
+
redis_client.quit();
|
|
98
|
+
redis_client = null; // should force a reconnect
|
|
99
|
+
// not sure if that's the right thing but better than nothing...
|
|
100
|
+
})
|
|
101
|
+
})
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
exports.multi = function (lookup, zones, cb) {
|
|
@@ -108,9 +112,9 @@ exports.multi = function (lookup, zones, cb) {
|
|
|
108
112
|
if (!self.enable_stats) return;
|
|
109
113
|
|
|
110
114
|
// Statistics: check hit overlap
|
|
111
|
-
for (
|
|
112
|
-
const foo = (
|
|
113
|
-
redis_client.
|
|
115
|
+
for (const element of listed) {
|
|
116
|
+
const foo = (element === zone) ? 'TOTAL' : element;
|
|
117
|
+
redis_client.hIncrBy(`dns-list-overlap:${zone}`, foo, 1);
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
120
|
|
|
@@ -149,14 +153,13 @@ exports.first = function (lookup, zones, cb, cb_each) {
|
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
exports.check_zones = function (interval) {
|
|
152
|
-
const self = this;
|
|
153
156
|
this.disable_allowed = true;
|
|
154
157
|
if (interval) interval = parseInt(interval);
|
|
155
|
-
if ((this.zones
|
|
156
|
-
(this.disabled_zones
|
|
158
|
+
if ((this.zones?.length) ||
|
|
159
|
+
(this.disabled_zones?.length)) {
|
|
157
160
|
let zones = [];
|
|
158
|
-
if (this.zones
|
|
159
|
-
if (this.disabled_zones
|
|
161
|
+
if (this.zones?.length) zones = zones.concat(this.zones);
|
|
162
|
+
if (this.disabled_zones?.length) {
|
|
160
163
|
zones = zones.concat(this.disabled_zones);
|
|
161
164
|
}
|
|
162
165
|
|
|
@@ -165,20 +168,20 @@ exports.check_zones = function (interval) {
|
|
|
165
168
|
this.multi('127.0.0.1', zones, (err, zone, a, pending) => {
|
|
166
169
|
if (!zone) return;
|
|
167
170
|
|
|
168
|
-
if ((!
|
|
169
|
-
return
|
|
171
|
+
if ((!this.lookback_is_rejected && a) || (err && err.code === 'ETIMEOUT')) {
|
|
172
|
+
return this.disable_zone(zone, ((a) ? a : err.code));
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
// Try the test point
|
|
173
|
-
|
|
176
|
+
this.lookup('127.0.0.2', zone, (err2, a2) => {
|
|
174
177
|
if (!a2) {
|
|
175
|
-
|
|
176
|
-
return
|
|
178
|
+
this.logwarn(`zone '${zone}' did not respond to test point (${err2})`);
|
|
179
|
+
return this.disable_zone(zone, a2);
|
|
177
180
|
}
|
|
178
181
|
// Was this zone previously disabled?
|
|
179
|
-
if (!
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
if (!this.zones.includes(zone)) {
|
|
183
|
+
this.loginfo(`re-enabling zone ${zone}`);
|
|
184
|
+
this.zones.push(zone);
|
|
182
185
|
}
|
|
183
186
|
});
|
|
184
187
|
});
|
|
@@ -187,16 +190,14 @@ exports.check_zones = function (interval) {
|
|
|
187
190
|
if (interval && interval >= 5 && !this._interval) {
|
|
188
191
|
this.logdebug(`will re-test list zones every ${interval} minutes`);
|
|
189
192
|
this._interval = setInterval(() => {
|
|
190
|
-
|
|
193
|
+
this.check_zones();
|
|
191
194
|
}, (interval * 60) * 1000);
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
exports.shutdown = function () {
|
|
196
199
|
clearInterval(this._interval);
|
|
197
|
-
if (redis_client)
|
|
198
|
-
redis_client.quit();
|
|
199
|
-
}
|
|
200
|
+
if (redis_client) redis_client.quit();
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
exports.disable_zone = function (zone, result) {
|
|
@@ -209,7 +210,7 @@ exports.disable_zone = function (zone, result) {
|
|
|
209
210
|
if (idx === -1) return false; // not enabled
|
|
210
211
|
|
|
211
212
|
this.zones.splice(idx, 1);
|
|
212
|
-
if (!(this.disabled_zones
|
|
213
|
+
if (!(this.disabled_zones?.length)) {
|
|
213
214
|
this.disabled_zones = [];
|
|
214
215
|
}
|
|
215
216
|
if (!this.disabled_zones.includes(zone)) {
|