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/greylist.js
CHANGED
|
@@ -10,17 +10,16 @@ const util = require('util');
|
|
|
10
10
|
const DSN = require('haraka-dsn');
|
|
11
11
|
const tlds = require('haraka-tld');
|
|
12
12
|
const net_utils = require('haraka-net-utils');
|
|
13
|
-
const Address = require('address-rfc2821')
|
|
13
|
+
const { Address } = require('address-rfc2821');
|
|
14
14
|
|
|
15
15
|
// External NPM modules
|
|
16
16
|
const ipaddr = require('ipaddr.js');
|
|
17
17
|
|
|
18
18
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
19
19
|
exports.register = function (next) {
|
|
20
|
-
|
|
21
|
-
plugin.inherits('haraka-plugin-redis');
|
|
20
|
+
this.inherits('haraka-plugin-redis');
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
this.load_config();
|
|
24
23
|
|
|
25
24
|
this.register_hook('init_master', 'init_redis_plugin');
|
|
26
25
|
this.register_hook('init_child', 'init_redis_plugin');
|
|
@@ -31,19 +30,18 @@ exports.register = function (next) {
|
|
|
31
30
|
|
|
32
31
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
33
32
|
exports.load_config = function () {
|
|
34
|
-
const plugin = this;
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
this.cfg = this.config.get('greylist.ini', {
|
|
37
35
|
booleans : [
|
|
38
36
|
'+skip.dnswlorg',
|
|
39
37
|
'-skip.mailspikewl'
|
|
40
38
|
]
|
|
41
39
|
}, () => {
|
|
42
|
-
|
|
40
|
+
this.load_config();
|
|
43
41
|
});
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
this.merge_redis_ini();
|
|
44
|
+
this.load_config_lists();
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
// Load various configuration lists
|
|
@@ -60,8 +58,8 @@ exports.load_config_lists = function () {
|
|
|
60
58
|
|
|
61
59
|
// toLower when loading spends a fraction of a second at load time
|
|
62
60
|
// to save millions of seconds during run time.
|
|
63
|
-
for (
|
|
64
|
-
plugin.whitelist[type][
|
|
61
|
+
for (const element of list) {
|
|
62
|
+
plugin.whitelist[type][element.toLowerCase()] = true;
|
|
65
63
|
}
|
|
66
64
|
plugin.logdebug(`whitelist {${type}} loaded from ${file_name} with ${list.length} entries`);
|
|
67
65
|
}
|
|
@@ -71,9 +69,9 @@ exports.load_config_lists = function () {
|
|
|
71
69
|
|
|
72
70
|
const list = Object.keys(plugin.cfg[file_name]);
|
|
73
71
|
|
|
74
|
-
for (
|
|
72
|
+
for (const element of list) {
|
|
75
73
|
try {
|
|
76
|
-
let addr =
|
|
74
|
+
let addr = element;
|
|
77
75
|
if (addr.match(/\/\d+$/)) {
|
|
78
76
|
addr = ipaddr.parseCIDR(addr);
|
|
79
77
|
}
|
|
@@ -111,156 +109,140 @@ exports.shutdown = function () {
|
|
|
111
109
|
|
|
112
110
|
// We check for IP and envelope whitelist
|
|
113
111
|
exports.hook_mail = function (next, connection, params) {
|
|
114
|
-
|
|
112
|
+
if (!connection.transaction) return next();
|
|
113
|
+
|
|
115
114
|
const mail_from = params[0];
|
|
116
115
|
|
|
117
116
|
// whitelist checks
|
|
118
|
-
if (
|
|
117
|
+
if (this.ip_in_list(connection.remote.ip)) { // check connecting IP
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
connection.transaction.results.add(
|
|
122
|
-
skip : 'config-whitelist(ip)'
|
|
123
|
-
});
|
|
119
|
+
this.loginfo(connection, 'Connecting IP was whitelisted via config');
|
|
120
|
+
connection.transaction.results.add(this, { skip : 'config-whitelist(ip)' })
|
|
124
121
|
|
|
125
122
|
}
|
|
126
|
-
else if (
|
|
123
|
+
else if (this.addr_in_list('mail', mail_from.address().toLowerCase())) { // check envelope (email & domain)
|
|
127
124
|
|
|
128
|
-
|
|
129
|
-
connection.transaction.results.add(
|
|
130
|
-
skip : 'config-whitelist(envelope)'
|
|
131
|
-
});
|
|
125
|
+
this.loginfo(connection, 'Envelope was whitelisted via config');
|
|
126
|
+
connection.transaction.results.add(this, { skip : 'config-whitelist(envelope)' });
|
|
132
127
|
|
|
133
128
|
}
|
|
134
129
|
else {
|
|
135
|
-
const why_skip =
|
|
130
|
+
const why_skip = this.process_skip_rules(connection);
|
|
136
131
|
|
|
137
132
|
if (why_skip) {
|
|
138
|
-
|
|
139
|
-
connection.transaction.results.add(
|
|
140
|
-
skip : `requested(${why_skip})`
|
|
141
|
-
});
|
|
133
|
+
this.loginfo(connection, `Requested to skip the GL because skip rule matched: ${why_skip}`);
|
|
134
|
+
connection.transaction.results.add(this, { skip : `requested(${why_skip})` });
|
|
142
135
|
}
|
|
143
136
|
}
|
|
144
137
|
|
|
145
|
-
|
|
138
|
+
next();
|
|
146
139
|
}
|
|
147
140
|
|
|
148
141
|
//
|
|
149
142
|
exports.hook_rcpt_ok = function (next, connection, rcpt) {
|
|
150
|
-
const plugin = this;
|
|
151
143
|
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
plugin.logdebug(connection, 'host already whitelisted in this session');
|
|
144
|
+
if (this.should_skip_check(connection)) return next();
|
|
145
|
+
if (this.was_whitelisted_in_session(connection)) {
|
|
146
|
+
this.logdebug(connection, 'host already whitelisted in this session');
|
|
156
147
|
return next();
|
|
157
148
|
}
|
|
158
149
|
|
|
159
|
-
const
|
|
160
|
-
|
|
150
|
+
const { transaction } = connection
|
|
151
|
+
|
|
152
|
+
const ctr = transaction.results;
|
|
153
|
+
const { mail_from } = transaction;
|
|
161
154
|
|
|
162
155
|
// check rcpt in whitelist (email & domain)
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
ctr.add(
|
|
166
|
-
skip : 'config-whitelist(recipient)'
|
|
167
|
-
});
|
|
156
|
+
if (this.addr_in_list('rcpt', rcpt.address().toLowerCase())) {
|
|
157
|
+
this.loginfo(connection, 'RCPT was whitelisted via config');
|
|
158
|
+
ctr.add(this, { skip : 'config-whitelist(recipient)' });
|
|
168
159
|
return next();
|
|
169
160
|
}
|
|
170
161
|
|
|
171
|
-
|
|
162
|
+
this.check_and_update_white(connection, (err, white_rec) => {
|
|
172
163
|
if (err) {
|
|
173
|
-
|
|
164
|
+
this.logerror(connection, `Got error: ${util.inspect(err)}`);
|
|
174
165
|
return next(DENYSOFT, DSN.sec_unspecified('Backend failure. Please, retry later or contact our support.'));
|
|
175
166
|
}
|
|
176
167
|
if (white_rec) {
|
|
177
|
-
|
|
178
|
-
ctr.add(
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
ctr.push(plugin, {
|
|
182
|
-
stats : {
|
|
183
|
-
rcpt : white_rec
|
|
184
|
-
},
|
|
185
|
-
stage : 'rcpt'
|
|
186
|
-
});
|
|
168
|
+
this.logdebug(connection, 'host in WHITE zone');
|
|
169
|
+
ctr.add(this, { pass : 'whitelisted' });
|
|
170
|
+
ctr.push(this, { stats : { rcpt : white_rec }, stage : 'rcpt' });
|
|
187
171
|
|
|
188
172
|
return next();
|
|
189
173
|
}
|
|
190
|
-
else {
|
|
191
|
-
|
|
192
|
-
return plugin.process_tuple(connection, mail_from.address(), rcpt.address(), (err2, white_promo_rec) => {
|
|
193
|
-
if (err2) {
|
|
194
|
-
if (err2 instanceof Error && err2.notanerror) {
|
|
195
|
-
plugin.logdebug(connection, 'host in GREY zone');
|
|
196
|
-
|
|
197
|
-
ctr.add(plugin, {
|
|
198
|
-
fail : 'greylisted'
|
|
199
|
-
});
|
|
200
|
-
ctr.push(plugin, {
|
|
201
|
-
stats : {
|
|
202
|
-
rcpt : err2.record
|
|
203
|
-
},
|
|
204
|
-
stage : 'rcpt'
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
return plugin.invoke_outcome_cb(next, false);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
throw err2;
|
|
211
|
-
}
|
|
212
174
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
175
|
+
return this.process_tuple(connection, mail_from.address(), rcpt.address(), (err2, white_promo_rec) => {
|
|
176
|
+
if (err2) {
|
|
177
|
+
if (err2 instanceof Error && err2.notanerror) {
|
|
178
|
+
this.logdebug(connection, 'host in GREY zone');
|
|
179
|
+
|
|
180
|
+
ctr.add(this, {
|
|
181
|
+
fail : 'greylisted'
|
|
217
182
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
ctr.add(plugin, {
|
|
223
|
-
pass : 'whitelisted',
|
|
224
|
-
stats : white_promo_rec,
|
|
183
|
+
ctr.push(this, {
|
|
184
|
+
stats : {
|
|
185
|
+
rcpt : err2.record
|
|
186
|
+
},
|
|
225
187
|
stage : 'rcpt'
|
|
226
188
|
});
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
});
|
|
230
|
-
return plugin.invoke_outcome_cb(next, true);
|
|
189
|
+
|
|
190
|
+
return this.invoke_outcome_cb(next, false);
|
|
231
191
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
192
|
+
|
|
193
|
+
throw err2;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!white_promo_rec) {
|
|
197
|
+
ctr.add(this, {
|
|
198
|
+
fail : 'greylisted',
|
|
199
|
+
stage : 'rcpt'
|
|
200
|
+
});
|
|
201
|
+
return this.invoke_outcome_cb(next, false);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
this.loginfo(connection, 'host has been promoted to WHITE zone');
|
|
205
|
+
ctr.add(this, {
|
|
206
|
+
pass : 'whitelisted',
|
|
207
|
+
stats : white_promo_rec,
|
|
208
|
+
stage : 'rcpt'
|
|
209
|
+
});
|
|
210
|
+
ctr.add(this, {
|
|
211
|
+
pass : 'whitelisted'
|
|
212
|
+
});
|
|
213
|
+
return this.invoke_outcome_cb(next, true);
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
})
|
|
235
217
|
}
|
|
236
218
|
|
|
237
219
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
238
220
|
|
|
239
221
|
// Main GL engine that accepts tuple and returns matched record or a rejection.
|
|
240
222
|
exports.process_tuple = function (connection, sender, rcpt, cb) {
|
|
241
|
-
const plugin = this;
|
|
242
223
|
|
|
243
|
-
const key =
|
|
224
|
+
const key = this.craft_grey_key(connection, sender, rcpt);
|
|
225
|
+
if (!key) return;
|
|
244
226
|
|
|
245
|
-
return
|
|
227
|
+
return this.db_lookup(key, (err, record) => {
|
|
246
228
|
if (err) {
|
|
247
229
|
if (err instanceof Error && err.what == 'db_error')
|
|
248
|
-
|
|
230
|
+
this.logwarn(connection, `got err from DB: ${util.inspect(err)}`);
|
|
249
231
|
throw err;
|
|
250
232
|
}
|
|
251
|
-
|
|
233
|
+
this.logdebug(connection, `got record: ${util.inspect(record)}`);
|
|
252
234
|
|
|
253
235
|
// { created: TS, updated: TS, lifetime: TTL, tried: Integer }
|
|
254
236
|
const now = Date.now() / 1000;
|
|
255
237
|
|
|
256
238
|
if (record &&
|
|
257
|
-
(record.created +
|
|
239
|
+
(record.created + this.cfg.period.black < now) &&
|
|
258
240
|
(record.created + record.lifetime >= now)) {
|
|
259
241
|
// Host passed greylisting
|
|
260
|
-
return
|
|
242
|
+
return this.promote_to_white(connection, record, cb);
|
|
261
243
|
}
|
|
262
244
|
|
|
263
|
-
return
|
|
245
|
+
return this.update_grey(key, !record, (err2, created_record) => {
|
|
264
246
|
const err3 = new Error('in black zone');
|
|
265
247
|
err3.record = created_record || record;
|
|
266
248
|
err3.notanerror = true;
|
|
@@ -271,22 +253,21 @@ exports.process_tuple = function (connection, sender, rcpt, cb) {
|
|
|
271
253
|
|
|
272
254
|
// Checks if host is _white_. Updates stats if so.
|
|
273
255
|
exports.check_and_update_white = function (connection, cb) {
|
|
274
|
-
const plugin = this;
|
|
275
256
|
|
|
276
|
-
const key =
|
|
257
|
+
const key = this.craft_white_key(connection);
|
|
277
258
|
|
|
278
|
-
return
|
|
259
|
+
return this.db_lookup(key, (err, record) => {
|
|
279
260
|
if (err) {
|
|
280
|
-
|
|
261
|
+
this.logwarn(connection, `got err from DB: ${util.inspect(err)}`);
|
|
281
262
|
throw err;
|
|
282
263
|
}
|
|
283
264
|
if (record) {
|
|
284
265
|
if (record.updated + record.lifetime - 2 < Date.now() / 1000) { // race "prevention".
|
|
285
|
-
|
|
266
|
+
this.logerror(connection, "Mischief! Race condition triggered.");
|
|
286
267
|
return cb(new Error('drunkard'));
|
|
287
268
|
}
|
|
288
269
|
|
|
289
|
-
return
|
|
270
|
+
return this.update_white_record(key, record, cb);
|
|
290
271
|
}
|
|
291
272
|
|
|
292
273
|
return cb(null, false);
|
|
@@ -295,46 +276,45 @@ exports.check_and_update_white = function (connection, cb) {
|
|
|
295
276
|
|
|
296
277
|
// invokes next() depending on outcome param
|
|
297
278
|
exports.invoke_outcome_cb = function (next, is_whitelisted) {
|
|
298
|
-
const plugin = this;
|
|
299
279
|
|
|
300
|
-
if (is_whitelisted)
|
|
301
|
-
return next();
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
const text = plugin.cfg.main.text || '';
|
|
280
|
+
if (is_whitelisted) return next();
|
|
305
281
|
|
|
306
|
-
|
|
307
|
-
|
|
282
|
+
const text = this.cfg.main.text || '';
|
|
283
|
+
|
|
284
|
+
return next(DENYSOFT, DSN.sec_unauthorized(text, '451'));
|
|
308
285
|
}
|
|
309
286
|
|
|
310
287
|
// Should we skip greylisting invokation altogether?
|
|
311
288
|
exports.should_skip_check = function (connection) {
|
|
312
|
-
const
|
|
313
|
-
|
|
289
|
+
const { transaction, relaying, remote } = connection ?? {}
|
|
290
|
+
|
|
291
|
+
if (!transaction) return true;
|
|
314
292
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
293
|
+
const ctr = transaction.results;
|
|
294
|
+
|
|
295
|
+
if (relaying) {
|
|
296
|
+
this.logdebug(connection, 'skipping GL for relaying host');
|
|
297
|
+
ctr.add(this, {
|
|
318
298
|
skip : 'relaying'
|
|
319
299
|
});
|
|
320
300
|
return true;
|
|
321
301
|
}
|
|
322
302
|
|
|
323
|
-
if (
|
|
324
|
-
connection.logdebug(
|
|
325
|
-
ctr.add(
|
|
303
|
+
if (remote?.is_private) {
|
|
304
|
+
connection.logdebug(this, `skipping private IP: ${connection.remote.ip}`);
|
|
305
|
+
ctr.add(this, {
|
|
326
306
|
skip : 'private-ip'
|
|
327
307
|
});
|
|
328
308
|
return true;
|
|
329
309
|
}
|
|
330
310
|
|
|
331
311
|
if (ctr) {
|
|
332
|
-
if (ctr.has(
|
|
333
|
-
|
|
312
|
+
if (ctr.has(this, 'skip', /^config-whitelist/)) {
|
|
313
|
+
this.loginfo(connection, 'skipping GL for host whitelisted in config');
|
|
334
314
|
return true;
|
|
335
315
|
}
|
|
336
|
-
if (ctr.has(
|
|
337
|
-
|
|
316
|
+
if (ctr.has(this, 'skip', /^requested/)) {
|
|
317
|
+
this.loginfo(connection, 'skipping GL because was asked to previously');
|
|
338
318
|
return true;
|
|
339
319
|
}
|
|
340
320
|
}
|
|
@@ -344,14 +324,14 @@ exports.should_skip_check = function (connection) {
|
|
|
344
324
|
|
|
345
325
|
// Was whitelisted previously in this session
|
|
346
326
|
exports.was_whitelisted_in_session = function (connection) {
|
|
327
|
+
if (!connection?.transaction?.results) return false;
|
|
347
328
|
return connection.transaction.results.has(this, 'pass', 'whitelisted');
|
|
348
329
|
}
|
|
349
330
|
|
|
350
331
|
exports.process_skip_rules = function (connection) {
|
|
351
|
-
const plugin = this;
|
|
352
332
|
const cr = connection.results;
|
|
353
333
|
|
|
354
|
-
const skip_cfg =
|
|
334
|
+
const skip_cfg = this.cfg.skip;
|
|
355
335
|
if (skip_cfg) {
|
|
356
336
|
if (skip_cfg.dnswlorg && cr.has('dnswl.org', 'pass', /^list\.dnswl\.org\([123]\)$/)) {
|
|
357
337
|
return 'dnswl.org(MED)'
|
|
@@ -362,7 +342,7 @@ exports.process_skip_rules = function (connection) {
|
|
|
362
342
|
}
|
|
363
343
|
}
|
|
364
344
|
|
|
365
|
-
return
|
|
345
|
+
return '';
|
|
366
346
|
}
|
|
367
347
|
|
|
368
348
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
@@ -371,8 +351,11 @@ exports.process_skip_rules = function (connection) {
|
|
|
371
351
|
// When _to_ is false, we craft +sender+ key
|
|
372
352
|
// When _to_ is String, we craft +rcpt+ key
|
|
373
353
|
exports.craft_grey_key = function (connection, from, to) {
|
|
374
|
-
|
|
375
|
-
|
|
354
|
+
|
|
355
|
+
const crafted_host_id = this.craft_hostid(connection)
|
|
356
|
+
if (!crafted_host_id) return null;
|
|
357
|
+
|
|
358
|
+
let key = `grey:${crafted_host_id}:${(from || '<>')}`;
|
|
376
359
|
if (to != undefined) {
|
|
377
360
|
key += `:${(to || '<>')}`;
|
|
378
361
|
}
|
|
@@ -381,40 +364,39 @@ exports.craft_grey_key = function (connection, from, to) {
|
|
|
381
364
|
|
|
382
365
|
// Build white DB key off supplied params.
|
|
383
366
|
exports.craft_white_key = function (connection) {
|
|
384
|
-
|
|
385
|
-
return `white:${plugin.craft_hostid(connection)}`;
|
|
367
|
+
return `white:${this.craft_hostid(connection)}`;
|
|
386
368
|
}
|
|
387
369
|
|
|
388
370
|
// Return so-called +hostid+.
|
|
389
371
|
exports.craft_hostid = function (connection) {
|
|
390
372
|
const plugin = this;
|
|
391
|
-
const
|
|
373
|
+
const { transaction, remote } = connection ?? {};
|
|
374
|
+
if (!transaction || !remote) return null;
|
|
392
375
|
|
|
393
|
-
if (
|
|
394
|
-
return
|
|
395
|
-
|
|
396
|
-
const ip = connection.remote.ip;
|
|
397
|
-
let rdns = connection.remote.host;
|
|
376
|
+
if (transaction.notes?.greylist?.hostid) {
|
|
377
|
+
return transaction.notes.greylist.hostid; // "caching"
|
|
378
|
+
}
|
|
398
379
|
|
|
399
380
|
function chsit (value, reason) { // cache the return value
|
|
400
381
|
if (!value)
|
|
401
382
|
plugin.logdebug(connection, `hostid set to IP: ${reason}`);
|
|
402
383
|
|
|
403
|
-
|
|
384
|
+
transaction.results.add(plugin, {
|
|
404
385
|
hostid_type : value ? 'domain' : 'ip',
|
|
405
|
-
rdns : (value || ip),
|
|
386
|
+
rdns : (value || remote.ip),
|
|
406
387
|
msg : reason
|
|
407
388
|
}); // !don't move me.
|
|
408
389
|
|
|
409
|
-
value = value || ip;
|
|
390
|
+
value = value || remote.ip;
|
|
410
391
|
|
|
411
|
-
return ((
|
|
392
|
+
return ((transaction.notes.greylist = transaction.notes.greylist || {}).hostid = value);
|
|
412
393
|
}
|
|
413
394
|
|
|
414
|
-
|
|
395
|
+
// no rDNS . FIXME: use fcrdns results
|
|
396
|
+
if (!remote.host || [ 'Unknown' | 'DNSERROR' ].includes(remote.host))
|
|
415
397
|
return chsit(null, 'no rDNS info for this host');
|
|
416
398
|
|
|
417
|
-
|
|
399
|
+
remote.host = remote.host.replace(/\.$/, ''); // strip ending dot, just in case
|
|
418
400
|
|
|
419
401
|
const fcrdns = connection.results.get('fcrdns');
|
|
420
402
|
if (!fcrdns) {
|
|
@@ -435,22 +417,20 @@ exports.craft_hostid = function (connection) {
|
|
|
435
417
|
return chsit(null, 'invalid org domain in rDNS');
|
|
436
418
|
|
|
437
419
|
// strip first label up until the tld boundary.
|
|
438
|
-
const decoupled = tlds.split_hostname(
|
|
420
|
+
const decoupled = tlds.split_hostname(!remote.host, 3);
|
|
439
421
|
const vardom = decoupled[0]; // "variable" portion of domain
|
|
440
422
|
const dom = decoupled[1]; // "static" portion of domain
|
|
441
423
|
|
|
442
424
|
// we check for special cases where rdns looks custom/static, but really is dynamic
|
|
443
|
-
const special_case_info = plugin.check_rdns_for_special_cases(
|
|
444
|
-
if (special_case_info)
|
|
445
|
-
return chsit(null, special_case_info.why);
|
|
446
|
-
}
|
|
425
|
+
const special_case_info = plugin.check_rdns_for_special_cases(!remote.host, vardom);
|
|
426
|
+
if (special_case_info) return chsit(null, special_case_info.why);
|
|
447
427
|
|
|
448
428
|
let stripped_dom = dom;
|
|
449
429
|
|
|
450
430
|
if (vardom) {
|
|
451
431
|
|
|
452
432
|
// check for decimal IP in rDNS
|
|
453
|
-
if (vardom.match(String(net_utils.ip_to_long(ip))))
|
|
433
|
+
if (vardom.match(String(net_utils.ip_to_long(remote.ip))))
|
|
454
434
|
return chsit(null, 'decimal IP');
|
|
455
435
|
|
|
456
436
|
// craft the +hostid+
|
|
@@ -467,15 +447,14 @@ exports.craft_hostid = function (connection) {
|
|
|
467
447
|
// Retrieve _grey_ record
|
|
468
448
|
// not implemented
|
|
469
449
|
exports.retrieve_grey = function (rcpt_key, sender_key, cb) {
|
|
470
|
-
const
|
|
471
|
-
const multi = plugin.db.multi();
|
|
450
|
+
const multi = this.db.multi();
|
|
472
451
|
|
|
473
452
|
multi.hgetall(rcpt_key);
|
|
474
453
|
multi.hgetall(sender_key);
|
|
475
454
|
|
|
476
455
|
multi.exec((err, result) => {
|
|
477
456
|
if (err) {
|
|
478
|
-
|
|
457
|
+
this.lognotice(`DB error: ${util.inspect(err)}`);
|
|
479
458
|
err.what = 'db_error';
|
|
480
459
|
throw err;
|
|
481
460
|
}
|
|
@@ -485,15 +464,13 @@ exports.retrieve_grey = function (rcpt_key, sender_key, cb) {
|
|
|
485
464
|
|
|
486
465
|
// Update or create _grey_ record
|
|
487
466
|
exports.update_grey = function (key, create, cb) {
|
|
488
|
-
|
|
489
|
-
const plugin = this;
|
|
490
|
-
const multi = plugin.db.multi();
|
|
467
|
+
const multi = this.db.multi();
|
|
491
468
|
|
|
492
469
|
const ts_now = Math.round(Date.now() / 1000);
|
|
493
470
|
let new_record;
|
|
494
471
|
|
|
495
472
|
if (create) {
|
|
496
|
-
const lifetime =
|
|
473
|
+
const lifetime = this.cfg.period.grey;
|
|
497
474
|
new_record = {
|
|
498
475
|
created : ts_now,
|
|
499
476
|
updated : ts_now,
|
|
@@ -513,7 +490,7 @@ exports.update_grey = function (key, create, cb) {
|
|
|
513
490
|
|
|
514
491
|
multi.exec((err, records) => {
|
|
515
492
|
if (err) {
|
|
516
|
-
|
|
493
|
+
this.lognotice(`DB error: ${util.inspect(err)}`);
|
|
517
494
|
err.what = 'db_error';
|
|
518
495
|
throw err;
|
|
519
496
|
}
|
|
@@ -523,10 +500,9 @@ exports.update_grey = function (key, create, cb) {
|
|
|
523
500
|
|
|
524
501
|
// Promote _grey_ record to _white_.
|
|
525
502
|
exports.promote_to_white = function (connection, grey_rec, cb) {
|
|
526
|
-
const plugin = this;
|
|
527
503
|
|
|
528
504
|
const ts_now = Math.round(Date.now() / 1000);
|
|
529
|
-
const white_ttl =
|
|
505
|
+
const white_ttl = this.cfg.period.white;
|
|
530
506
|
|
|
531
507
|
// { first_connect: TS, whitelisted: TS, updated: TS, lifetime: TTL, tried: Integer, tried_when_greylisted: Integer }
|
|
532
508
|
const white_rec = {
|
|
@@ -538,17 +514,18 @@ exports.promote_to_white = function (connection, grey_rec, cb) {
|
|
|
538
514
|
tried : 1
|
|
539
515
|
};
|
|
540
516
|
|
|
541
|
-
const white_key =
|
|
517
|
+
const white_key = this.craft_white_key(connection);
|
|
518
|
+
if (!white_key) return;
|
|
542
519
|
|
|
543
|
-
return
|
|
520
|
+
return this.db.hmset(white_key, white_rec, (err, result) => {
|
|
544
521
|
if (err) {
|
|
545
|
-
|
|
522
|
+
this.lognotice(`DB error: ${util.inspect(err)}`);
|
|
546
523
|
err.what = 'db_error';
|
|
547
524
|
throw err;
|
|
548
525
|
}
|
|
549
|
-
|
|
526
|
+
this.db.expire(white_key, white_ttl, (err2, result2) => {
|
|
550
527
|
if (err2) {
|
|
551
|
-
|
|
528
|
+
this.lognotice(`DB error: ${util.inspect(err2)}`);
|
|
552
529
|
}
|
|
553
530
|
return cb(err2, result2);
|
|
554
531
|
});
|
|
@@ -557,9 +534,8 @@ exports.promote_to_white = function (connection, grey_rec, cb) {
|
|
|
557
534
|
|
|
558
535
|
// Update _white_ record
|
|
559
536
|
exports.update_white_record = function (key, record, cb) {
|
|
560
|
-
const plugin = this;
|
|
561
537
|
|
|
562
|
-
const multi =
|
|
538
|
+
const multi = this.db.multi();
|
|
563
539
|
const ts_now = Math.round(Date.now() / 1000);
|
|
564
540
|
|
|
565
541
|
// { first_connect: TS, whitelisted: TS, updated: TS, lifetime: TTL, tried: Integer, tried_when_greylisted: Integer }
|
|
@@ -571,7 +547,7 @@ exports.update_white_record = function (key, record, cb) {
|
|
|
571
547
|
|
|
572
548
|
return multi.exec((err2, record2) => {
|
|
573
549
|
if (err2) {
|
|
574
|
-
|
|
550
|
+
this.lognotice(`DB error: ${util.inspect(err2)}`);
|
|
575
551
|
err2.what = 'db_error';
|
|
576
552
|
throw err2;
|
|
577
553
|
}
|
|
@@ -582,11 +558,10 @@ exports.update_white_record = function (key, record, cb) {
|
|
|
582
558
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
583
559
|
|
|
584
560
|
exports.db_lookup = function (key, cb) {
|
|
585
|
-
const plugin = this;
|
|
586
561
|
|
|
587
|
-
|
|
562
|
+
this.db.hgetall(key, (err, result) => {
|
|
588
563
|
if (err) {
|
|
589
|
-
|
|
564
|
+
this.lognotice(`DB error: ${util.inspect(err)}`, key);
|
|
590
565
|
}
|
|
591
566
|
if (result && typeof result === 'object') { // groom known-to-be numeric values
|
|
592
567
|
['created', 'updated', 'lifetime', 'tried', 'first_connect', 'whitelisted', 'tried_when_greylisted'].forEach(kk => {
|
|
@@ -602,20 +577,19 @@ exports.db_lookup = function (key, cb) {
|
|
|
602
577
|
|
|
603
578
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
604
579
|
exports.addr_in_list = function (type, address) {
|
|
605
|
-
const plugin = this;
|
|
606
580
|
|
|
607
|
-
if (!
|
|
608
|
-
|
|
581
|
+
if (!this.whitelist[type]) {
|
|
582
|
+
this.logwarn(`List not defined: ${type}`);
|
|
609
583
|
return false;
|
|
610
584
|
}
|
|
611
585
|
|
|
612
|
-
if (
|
|
586
|
+
if (this.whitelist[type][address]) {
|
|
613
587
|
return true;
|
|
614
588
|
}
|
|
615
589
|
|
|
616
590
|
try {
|
|
617
591
|
const addr = new Address(address);
|
|
618
|
-
return !!
|
|
592
|
+
return !!this.whitelist[type][addr.host];
|
|
619
593
|
}
|
|
620
594
|
catch (err) {
|
|
621
595
|
return false;
|
|
@@ -623,14 +597,13 @@ exports.addr_in_list = function (type, address) {
|
|
|
623
597
|
}
|
|
624
598
|
|
|
625
599
|
exports.ip_in_list = function (ip) {
|
|
626
|
-
const plugin = this;
|
|
627
600
|
const ipobj = ipaddr.parse(ip);
|
|
628
601
|
|
|
629
|
-
const list =
|
|
602
|
+
const list = this.whitelist.ip;
|
|
630
603
|
|
|
631
|
-
for (
|
|
604
|
+
for (const element of list) {
|
|
632
605
|
try {
|
|
633
|
-
if (ipobj.match(
|
|
606
|
+
if (ipobj.match(element)) {
|
|
634
607
|
return true;
|
|
635
608
|
}
|
|
636
609
|
}
|
|
@@ -642,16 +615,15 @@ exports.ip_in_list = function (ip) {
|
|
|
642
615
|
|
|
643
616
|
// Match patterns in the list against (end of) domain
|
|
644
617
|
exports.domain_in_list = function (list_name, domain) {
|
|
645
|
-
const
|
|
646
|
-
const list = plugin.list[list_name];
|
|
618
|
+
const list = this.list[list_name];
|
|
647
619
|
|
|
648
620
|
if (!list) {
|
|
649
|
-
|
|
621
|
+
this.logwarn(`List not defined: ${list_name}`);
|
|
650
622
|
return false;
|
|
651
623
|
}
|
|
652
624
|
|
|
653
|
-
for (
|
|
654
|
-
if (domain.length - domain.lastIndexOf(
|
|
625
|
+
for (const element of list) {
|
|
626
|
+
if (domain.length - domain.lastIndexOf(element) == element.length)
|
|
655
627
|
return true;
|
|
656
628
|
}
|
|
657
629
|
|
|
@@ -661,10 +633,9 @@ exports.domain_in_list = function (list_name, domain) {
|
|
|
661
633
|
// Check for special rDNS cases
|
|
662
634
|
// @return {type: 'dynamic'} if rnds is dynamic (hostid should be IP)
|
|
663
635
|
exports.check_rdns_for_special_cases = function (domain, label) {
|
|
664
|
-
const plugin = this;
|
|
665
636
|
|
|
666
637
|
// ptr for these is in fact dynamic
|
|
667
|
-
if (
|
|
638
|
+
if (this.domain_in_list('dyndom', domain))
|
|
668
639
|
return {
|
|
669
640
|
type : 'dynamic',
|
|
670
641
|
why : 'rDNS considered dynamic: listed in dynamic.domains config list'
|