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/data.uribl.js
CHANGED
|
@@ -1,418 +1,4 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// Look up URLs in SURBL
|
|
3
|
-
|
|
4
|
-
const url = require('url');
|
|
5
|
-
const dns = require('dns');
|
|
6
|
-
const net = require('net');
|
|
7
|
-
const tlds = require('haraka-tld');
|
|
8
|
-
|
|
9
|
-
const net_utils = require('haraka-net-utils');
|
|
10
|
-
const utils = require('haraka-utils');
|
|
11
|
-
|
|
12
|
-
// Default regexps to extract the URIs from the message
|
|
13
|
-
const numeric_ip = /\w{3,16}:\/+(\S+@)?(\d+|0[xX][0-9A-Fa-f]+)\.(\d+|0[xX][0-9A-Fa-f]+)\.(\d+|0[xX][0-9A-Fa-f]+)\.(\d+|0[xX][0-9A-Fa-f]+)/gi;
|
|
14
|
-
let schemeless = /(?:%(?:25)?(?:2F|3D|40))?((?:www\.)?[a-zA-Z0-9][a-zA-Z0-9\-.]{0,250}\.(?:aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|[a-zA-Z]{2}))(?!\w)/gi;
|
|
15
|
-
let schemed = /(\w{3,16}:\/+(?:\S+@)?([a-zA-Z0-9][a-zA-Z0-9\-.]+\.(?:aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|[a-zA-Z]{2})))(?!\w)/gi;
|
|
16
|
-
|
|
17
|
-
let lists;
|
|
18
|
-
let zones;
|
|
19
|
-
const excludes = {};
|
|
20
|
-
|
|
21
|
-
function check_excludes_list (host) {
|
|
22
|
-
host = host.toLowerCase().split('.').reverse();
|
|
23
|
-
for (let i=0; i < host.length; i++) {
|
|
24
|
-
let check;
|
|
25
|
-
if (i === 0) {
|
|
26
|
-
check = host[i];
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
check = [ host[i], check ].join('.');
|
|
30
|
-
}
|
|
31
|
-
if (excludes[check]) {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
1
|
|
|
38
2
|
exports.register = function () {
|
|
39
|
-
|
|
40
|
-
if (!tlds.top_level_tlds) return;
|
|
41
|
-
if (!Object.keys(tlds.top_level_tlds).length) return;
|
|
42
|
-
|
|
43
|
-
this.logdebug('Building new regexps from TLD file');
|
|
44
|
-
const re_schemeless = `(?:%(?:25)?(?:2F|3D|40))?((?:www\\.)?[a-zA-Z0-9][a-zA-Z0-9\\-.]{0,250}\\.(?:${Object.keys(tlds.top_level_tlds).join('|')}))(?!\\w)`;
|
|
45
|
-
schemeless = new RegExp(re_schemeless, 'gi');
|
|
46
|
-
const re_schemed = `(\\w{3,16}:\\/+(?:\\S+@)?([a-zA-Z0-9][a-zA-Z0-9\\-.]+\\.(?:${Object.keys(tlds.top_level_tlds).join('|')})))(?!\\w)`;
|
|
47
|
-
schemed = new RegExp(re_schemed, 'gi');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
exports.load_uri_config = function (next) {
|
|
51
|
-
lists = this.config.get('data.uribl.ini');
|
|
52
|
-
zones = Object.keys(lists);
|
|
53
|
-
if (!zones || zones.length <= 1) {
|
|
54
|
-
this.logerror('aborting: no zones configured');
|
|
55
|
-
return next();
|
|
56
|
-
}
|
|
57
|
-
// Load excludes
|
|
58
|
-
this.config.get('data.uribl.excludes', 'list').forEach(domain => {
|
|
59
|
-
excludes[domain.toLowerCase()] = 1;
|
|
60
|
-
});
|
|
61
|
-
// Set defaults
|
|
62
|
-
if (lists.main && !lists.main.max_uris_per_list) {
|
|
63
|
-
lists.main.max_uris_per_list = 20;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// IS: IPv6 compatible (maybe; if the BL is support IPv6 requests)
|
|
69
|
-
exports.do_lookups = function (connection, next, hosts, type) {
|
|
70
|
-
const plugin = this;
|
|
71
|
-
|
|
72
|
-
// Store the results in the correct place based on the lookup type
|
|
73
|
-
let results = connection.results;
|
|
74
|
-
if (connection.transaction) {
|
|
75
|
-
results = connection.transaction.results;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (typeof hosts === 'string') {
|
|
79
|
-
hosts = [ hosts ];
|
|
80
|
-
}
|
|
81
|
-
if (!hosts || !hosts.length) {
|
|
82
|
-
connection.logdebug(plugin, `(${type}) no items found for lookup`);
|
|
83
|
-
results.add(plugin, {skip: type});
|
|
84
|
-
return next();
|
|
85
|
-
}
|
|
86
|
-
connection.logdebug(plugin, `(${type}) found ${hosts.length} items for lookup` );
|
|
87
|
-
utils.shuffle(hosts);
|
|
88
|
-
|
|
89
|
-
let j;
|
|
90
|
-
const queries = {};
|
|
91
|
-
for (let i=0; i < hosts.length; i++) {
|
|
92
|
-
let host = hosts[i].toLowerCase();
|
|
93
|
-
connection.logdebug(plugin, `(${type}) checking: ${host}`);
|
|
94
|
-
// Make sure we have a valid TLD
|
|
95
|
-
if (!net.isIPv4(host) && !net.isIPv6(host) && !tlds.top_level_tlds[(host.split('.').reverse())[0]]) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
// Check the exclusion list
|
|
99
|
-
if (check_excludes_list(host)) {
|
|
100
|
-
results.add(plugin, {skip: `excluded domain:${host}`});
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
// Loop through the zones
|
|
104
|
-
for (j=0; j < zones.length; j++) {
|
|
105
|
-
const zone = zones[j];
|
|
106
|
-
if (zone === 'main') continue; // skip config
|
|
107
|
-
if (!lists[zone] || (lists[zone] && !/^(?:1|true|yes|enabled|on)$/i.test(lists[zone][type]))) {
|
|
108
|
-
results.add(plugin, {skip: `${type} unsupported for ${zone}` });
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
// Convert in-addr.arpa into bare IPv4/v6 lookup
|
|
112
|
-
const arpa = host.split(/\./).reverse();
|
|
113
|
-
if (arpa.shift() === 'arpa'){
|
|
114
|
-
const ip_format = arpa.shift();
|
|
115
|
-
if ( ip_format === 'in-addr') {
|
|
116
|
-
if (arpa.length < 4) continue; // Only full IP addresses
|
|
117
|
-
host = arpa.join('.');
|
|
118
|
-
}
|
|
119
|
-
else if ( ip_format === 'ip6') {
|
|
120
|
-
if (arpa.length < 32) continue; // Only full IP addresses
|
|
121
|
-
host = arpa.join('.');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
let lookup;
|
|
125
|
-
// Handle zones that do not allow IP queries (e.g. Spamhaus DBL)
|
|
126
|
-
if (net.isIPv4(host)) {
|
|
127
|
-
if (/^(?:1|true|yes|enabled|on)$/i.test(lists[zone].no_ip_lookups)) {
|
|
128
|
-
results.add(plugin, {skip: `IP (${host}) not supported for ${zone}` });
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
// Skip any private IPs
|
|
132
|
-
if (net_utils.is_private_ip(host)) {
|
|
133
|
-
results.add(plugin, {skip: 'private IP' });
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
// Reverse IP for lookup
|
|
137
|
-
lookup = host.split(/\./).reverse().join('.');
|
|
138
|
-
}
|
|
139
|
-
if (net.isIPv6(host)) {
|
|
140
|
-
if (/^(?:1|true|yes|enabled|on)$/i.test(lists[zone].not_ipv6_compatible) || /^(?:1|true|yes|enabled|on)$/i.test(lists[zone].no_ip_lookups)) {
|
|
141
|
-
results.add(plugin, {skip: `IP (${host}) not supported for ${zone}` });
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
// Skip any private IPs
|
|
145
|
-
if (net_utils.is_private_ip(host)) {
|
|
146
|
-
results.add(plugin, {skip: 'private IP' });
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
// Reverse IP for lookup
|
|
150
|
-
lookup = net_utils.ipv6_reverse(host);
|
|
151
|
-
}
|
|
152
|
-
// Handle zones that require host to be stripped to a domain boundary
|
|
153
|
-
else if (/^(?:1|true|yes|enabled|on)$/i.test(lists[zone].strip_to_domain)) {
|
|
154
|
-
lookup = (tlds.split_hostname(host, 3))[1];
|
|
155
|
-
}
|
|
156
|
-
// Anything else..
|
|
157
|
-
else {
|
|
158
|
-
lookup = host;
|
|
159
|
-
}
|
|
160
|
-
if (!lookup) continue;
|
|
161
|
-
if (!queries[zone]) queries[zone] = {};
|
|
162
|
-
if (Object.keys(queries[zone]).length > lists.main.max_uris_per_list) {
|
|
163
|
-
connection.logwarn(plugin, `discarding lookup ${lookup} for zone ${zone} maximum query limit reached`);
|
|
164
|
-
results.add(plugin, {skip: `max query limit for ${zone}` });
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
queries[zone][lookup] = 1;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Flatten object into array for easier querying
|
|
172
|
-
const queries_to_run = [];
|
|
173
|
-
for (j=0; j < Object.keys(queries).length; j++) {
|
|
174
|
-
for (let k=0; k < Object.keys(queries[Object.keys(queries)[j]]).length; k++) {
|
|
175
|
-
// host/domain, zone
|
|
176
|
-
queries_to_run.push( [ Object.keys(queries[Object.keys(queries)[j]])[k], Object.keys(queries)[j] ] );
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!queries_to_run.length) {
|
|
181
|
-
results.add(plugin, {skip: `${type} (no queries)` });
|
|
182
|
-
return next();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
utils.shuffle(queries_to_run); // Randomize the order
|
|
186
|
-
|
|
187
|
-
// Perform the lookups
|
|
188
|
-
let pending_queries = 0;
|
|
189
|
-
let called_next = false;
|
|
190
|
-
let timer;
|
|
191
|
-
function call_next (code, msg) {
|
|
192
|
-
clearTimeout(timer);
|
|
193
|
-
if (called_next) return;
|
|
194
|
-
called_next = true;
|
|
195
|
-
next(code, msg);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
timer = setTimeout(() => {
|
|
199
|
-
connection.logdebug(plugin, 'timeout');
|
|
200
|
-
results.add(plugin, {err: `${type} timeout` });
|
|
201
|
-
call_next();
|
|
202
|
-
}, ((lists.main && lists.main.timeout) ? lists.main.timeout : 30) * 1000);
|
|
203
|
-
|
|
204
|
-
function conclude_if_no_pending () {
|
|
205
|
-
if (pending_queries !== 0) return;
|
|
206
|
-
results.add(plugin, {pass: type});
|
|
207
|
-
call_next();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
queries_to_run.forEach(query => {
|
|
211
|
-
let lookup = query.join('.');
|
|
212
|
-
// Add root dot if necessary
|
|
213
|
-
if (lookup[lookup.length-1] !== '.') {
|
|
214
|
-
lookup = `${lookup}.`;
|
|
215
|
-
}
|
|
216
|
-
pending_queries++;
|
|
217
|
-
dns.resolve4(lookup, (err, addrs) => {
|
|
218
|
-
pending_queries--;
|
|
219
|
-
connection.logdebug(plugin, `${lookup} => (${(err) ? err : addrs.join(', ')})`);
|
|
220
|
-
|
|
221
|
-
if (err) return conclude_if_no_pending();
|
|
222
|
-
|
|
223
|
-
let skip = false;
|
|
224
|
-
function do_reject (msg) {
|
|
225
|
-
if (skip) return;
|
|
226
|
-
if (called_next) return;
|
|
227
|
-
if (!msg) {
|
|
228
|
-
msg = `${query[0]} blacklisted in ${query[1]}`;
|
|
229
|
-
}
|
|
230
|
-
// Check for custom message
|
|
231
|
-
if (lists[query[1]] && lists[query[1]].custom_msg) {
|
|
232
|
-
msg = lists[query[1]].custom_msg
|
|
233
|
-
.replace(/\{uri\}/g, query[0])
|
|
234
|
-
.replace(/\{zone\}/g, query[1]);
|
|
235
|
-
}
|
|
236
|
-
results.add(plugin,
|
|
237
|
-
{fail: [type, query[0], query[1]].join('/') });
|
|
238
|
-
call_next(DENY, msg);
|
|
239
|
-
}
|
|
240
|
-
// Optionally validate first result against a regexp
|
|
241
|
-
if (lists[query[1]] && lists[query[1]].validate) {
|
|
242
|
-
const re = new RegExp(lists[query[1]].validate);
|
|
243
|
-
if (!re.test(addrs[0])) {
|
|
244
|
-
connection.logdebug(plugin, `ignoring result (${addrs[0]}) for: ${lookup} as it did not match validation rule`);
|
|
245
|
-
skip = true;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
// Check for optional bitmask
|
|
249
|
-
if (lists[query[1]] && lists[query[1]].bitmask) {
|
|
250
|
-
// A bitmask zone should only return a single result
|
|
251
|
-
// We only support a bitmask of up to 128 in a single octet
|
|
252
|
-
const last_octet = Number((addrs[0].split('.'))[3]);
|
|
253
|
-
const bitmask = Number(lists[query[1]].bitmask);
|
|
254
|
-
if ((last_octet & bitmask) > 0) {
|
|
255
|
-
connection.loginfo(plugin, `found ${query[0]} in zone ${query[1]} (${addrs.join(',')}; bitmask=${bitmask})`);
|
|
256
|
-
do_reject();
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
connection.logdebug(plugin, `ignoring result (${addrs[0]}) for: ${lookup} as the bitmask did not match`);
|
|
260
|
-
skip = true;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
connection.loginfo(plugin, `found ${query[0]} in zone ${query[1]} (${addrs.join(',')})`);
|
|
265
|
-
do_reject();
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
conclude_if_no_pending();
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
conclude_if_no_pending();
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
exports.hook_lookup_rdns = function (next, connection) {
|
|
276
|
-
this.load_uri_config(next);
|
|
277
|
-
const plugin = this;
|
|
278
|
-
dns.reverse(connection.remote.ip, (err, rdns) => {
|
|
279
|
-
if (err) {
|
|
280
|
-
if (err.code) {
|
|
281
|
-
if (err.code === dns.NXDOMAIN) return next();
|
|
282
|
-
if (err.code === dns.NOTFOUND) return next();
|
|
283
|
-
}
|
|
284
|
-
connection.results.add(plugin, {err });
|
|
285
|
-
return next();
|
|
286
|
-
}
|
|
287
|
-
plugin.do_lookups(connection, next, rdns, 'rdns');
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
exports.hook_ehlo = function (next, connection, helo) {
|
|
292
|
-
this.load_uri_config(next);
|
|
293
|
-
// Handle IP literals
|
|
294
|
-
let literal;
|
|
295
|
-
if ((literal = net_utils.get_ipany_re('^\\[(?:IPv6:)?', '\\]$','').exec(helo))) {
|
|
296
|
-
this.do_lookups(connection, next, literal[1], 'helo');
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
this.do_lookups(connection, next, helo, 'helo');
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
exports.hook_helo = exports.hook_ehlo;
|
|
303
|
-
|
|
304
|
-
exports.hook_mail = function (next, connection, params) {
|
|
305
|
-
this.load_uri_config(next);
|
|
306
|
-
this.do_lookups(connection, next, params[0].host, 'envfrom');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
exports.hook_data = (next, connection) => {
|
|
310
|
-
// enable mail body parsing
|
|
311
|
-
connection.transaction.parse_body = true;
|
|
312
|
-
return next();
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
exports.hook_data_post = function (next, connection) {
|
|
316
|
-
this.load_uri_config(next);
|
|
317
|
-
const email_re = /<?[^@]+@([^> ]+)>?/;
|
|
318
|
-
const plugin = this;
|
|
319
|
-
const trans = connection.transaction;
|
|
320
|
-
|
|
321
|
-
// From header
|
|
322
|
-
function do_from_header (cb) {
|
|
323
|
-
const from = trans.header.get_decoded('from');
|
|
324
|
-
const fmatch = email_re.exec(from);
|
|
325
|
-
if (fmatch) {
|
|
326
|
-
return plugin.do_lookups(connection, cb, fmatch[1], 'from');
|
|
327
|
-
}
|
|
328
|
-
cb();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Reply-To header
|
|
332
|
-
function do_replyto_header (cb) {
|
|
333
|
-
const replyto = trans.header.get('reply-to');
|
|
334
|
-
const rmatch = email_re.exec(replyto);
|
|
335
|
-
if (rmatch) {
|
|
336
|
-
return plugin.do_lookups(connection, cb, rmatch[1], 'replyto');
|
|
337
|
-
}
|
|
338
|
-
cb();
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Message-Id header
|
|
342
|
-
function do_msgid_header (cb) {
|
|
343
|
-
const msgid = trans.header.get('message-id');
|
|
344
|
-
const mmatch = /@([^>]+)>/.exec(msgid);
|
|
345
|
-
if (mmatch) {
|
|
346
|
-
return plugin.do_lookups(connection, cb, mmatch[1], 'msgid');
|
|
347
|
-
}
|
|
348
|
-
cb();
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Body
|
|
352
|
-
function do_body (cb) {
|
|
353
|
-
const urls = {};
|
|
354
|
-
extract_urls(urls, trans.body, connection, plugin);
|
|
355
|
-
return plugin.do_lookups(connection, cb, Object.keys(urls), 'body');
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const chain = [ do_from_header, do_replyto_header, do_msgid_header, do_body ];
|
|
359
|
-
function chain_caller (code, msg) {
|
|
360
|
-
if (code) {
|
|
361
|
-
return next(code, msg);
|
|
362
|
-
}
|
|
363
|
-
if (!chain.length) {
|
|
364
|
-
return next();
|
|
365
|
-
}
|
|
366
|
-
const next_in_chain = chain.shift();
|
|
367
|
-
next_in_chain(chain_caller);
|
|
368
|
-
}
|
|
369
|
-
chain_caller();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function extract_urls (urls, body, connection, self) {
|
|
373
|
-
// extract from body.bodytext
|
|
374
|
-
let match;
|
|
375
|
-
if (!body || !body.bodytext) { return; }
|
|
376
|
-
|
|
377
|
-
let uri;
|
|
378
|
-
// extract numeric URIs
|
|
379
|
-
while ((match = numeric_ip.exec(body.bodytext))) {
|
|
380
|
-
try {
|
|
381
|
-
uri = url.parse(match[0]);
|
|
382
|
-
// Don't reverse the IPs here; we do it in the lookup
|
|
383
|
-
urls[uri.hostname] = uri;
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
connection.logerror(self, `parse error: ${match[0]} ${error.message}`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// match plain hostname.tld
|
|
391
|
-
while ((match = schemeless.exec(body.bodytext))) {
|
|
392
|
-
try {
|
|
393
|
-
uri = url.parse(`http://${match[1]}`);
|
|
394
|
-
urls[uri.hostname] = uri;
|
|
395
|
-
}
|
|
396
|
-
catch (error) {
|
|
397
|
-
connection.logerror(self, `parse error: ${match[1]} ${error.message}`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// match scheme:// URI
|
|
402
|
-
while ((match = schemed.exec(body.bodytext))) {
|
|
403
|
-
try {
|
|
404
|
-
uri = url.parse(match[1]);
|
|
405
|
-
urls[uri.hostname] = uri;
|
|
406
|
-
}
|
|
407
|
-
catch (error) {
|
|
408
|
-
connection.logerror(self, `parse error: ${match[1]} ${error.message}`);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// TODO: URIHASH
|
|
413
|
-
// TODO: MAILHASH
|
|
414
|
-
|
|
415
|
-
for (let i=0,l=body.children.length; i < l; i++) {
|
|
416
|
-
extract_urls(urls, body.children[i], connection, self);
|
|
417
|
-
}
|
|
3
|
+
this.logerror(this, "data.uribl was moved to haraka-plugin-uribl. Update the plugin name in config/plugins to just 'uribl'");
|
|
418
4
|
}
|
package/plugins/delay_deny.js
CHANGED
|
@@ -17,8 +17,7 @@ exports.hook_deny = function (next, connection, params) {
|
|
|
17
17
|
// var pi_params = params[4];
|
|
18
18
|
const pi_hook = params[5];
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
const transaction = connection.transaction;
|
|
20
|
+
const { transaction } = connection;
|
|
22
21
|
|
|
23
22
|
// Don't delay ourselves...
|
|
24
23
|
if (pi_name == 'delay_deny') return next();
|
|
@@ -35,27 +34,27 @@ exports.hook_deny = function (next, connection, params) {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
// 'included' mode: only delay deny plugins in the included list
|
|
38
|
-
if (included
|
|
37
|
+
if (included?.length) {
|
|
39
38
|
if (!included.includes(pi_name) &&
|
|
40
39
|
!included.includes(`${pi_name}:${pi_hook}`) &&
|
|
41
40
|
!included.includes(`${pi_name}:${pi_hook}:${pi_function}`)) {
|
|
42
41
|
return next();
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
|
-
else if (skip
|
|
44
|
+
else if (skip?.length) { // 'excluded' mode: delay deny everything except in skip list
|
|
46
45
|
// Skip by <plugin name>
|
|
47
46
|
if (skip.includes(pi_name)) {
|
|
48
|
-
connection.logdebug(
|
|
47
|
+
connection.logdebug(this, `not delaying excluded plugin: ${pi_name}`);
|
|
49
48
|
return next();
|
|
50
49
|
}
|
|
51
50
|
// Skip by <plugin name>:<hook>
|
|
52
51
|
if (skip.includes(`${pi_name}:${pi_hook}`)) {
|
|
53
|
-
connection.logdebug(
|
|
52
|
+
connection.logdebug(this, `not delaying excluded hook: ${pi_hook} in plugin: ${pi_name}`);
|
|
54
53
|
return next();
|
|
55
54
|
}
|
|
56
55
|
// Skip by <plugin name>:<hook>:<function name>
|
|
57
56
|
if (skip.includes(`${pi_name}:${pi_hook}:${pi_function}`)) {
|
|
58
|
-
connection.logdebug(
|
|
57
|
+
connection.logdebug(this, `not delaying excluded function: ${pi_function} on hook: ${pi_hook} in plugin: ${pi_name}`);
|
|
59
58
|
return next();
|
|
60
59
|
}
|
|
61
60
|
}
|
|
@@ -94,31 +93,30 @@ exports.hook_deny = function (next, connection, params) {
|
|
|
94
93
|
// fall through
|
|
95
94
|
default:
|
|
96
95
|
// No delays
|
|
97
|
-
|
|
96
|
+
next();
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
exports.hook_rcpt_ok = function (next, connection, rcpt) {
|
|
102
|
-
const
|
|
103
|
-
|
|
101
|
+
const transaction = connection?.transaction;
|
|
102
|
+
if (!transaction) return next();
|
|
104
103
|
|
|
105
104
|
// Bypass all pre-DATA deny for AUTH/RELAY
|
|
106
105
|
if (connection.relaying) {
|
|
107
|
-
connection.loginfo(
|
|
106
|
+
connection.loginfo(this, 'bypassing all pre-DATA deny: AUTH/RELAY');
|
|
108
107
|
return next();
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
// Apply any delayed rejections
|
|
112
111
|
// Check connection level pre-DATA rejections first
|
|
113
|
-
if (connection.notes
|
|
114
|
-
for (
|
|
115
|
-
const params = connection.notes.delay_deny_pre[i];
|
|
112
|
+
if (connection.notes?.delay_deny_pre) {
|
|
113
|
+
for (const params of connection.notes.delay_deny_pre) {
|
|
116
114
|
return next(params[0], params[1]);
|
|
117
115
|
}
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
// Then check transaction level pre-DATA
|
|
121
|
-
if (transaction.notes
|
|
119
|
+
if (transaction.notes?.delay_deny_pre) {
|
|
122
120
|
for (let i=0; i<transaction.notes.delay_deny_pre.length; i++) {
|
|
123
121
|
const params = transaction.notes.delay_deny_pre[i];
|
|
124
122
|
|
|
@@ -130,21 +128,22 @@ exports.hook_rcpt_ok = function (next, connection, rcpt) {
|
|
|
130
128
|
return next(params[0], params[1]);
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
|
-
|
|
131
|
+
next();
|
|
134
132
|
}
|
|
135
133
|
|
|
136
134
|
exports.hook_data = (next, connection) => {
|
|
137
|
-
const transaction = connection
|
|
135
|
+
const transaction = connection?.transaction;
|
|
136
|
+
if (!transaction) return next();
|
|
138
137
|
|
|
139
138
|
// Add a header showing all pre-DATA rejections
|
|
140
139
|
const fails = [];
|
|
141
|
-
if (connection.notes
|
|
140
|
+
if (connection.notes?.delay_deny_pre_fail) {
|
|
142
141
|
fails.push.apply(Object.keys(connection.notes.delay_deny_pre_fail));
|
|
143
142
|
}
|
|
144
|
-
if (transaction.notes
|
|
143
|
+
if (transaction.notes?.delay_deny_pre_fail) {
|
|
145
144
|
fails.push.apply(Object.keys(transaction.notes.delay_deny_pre_fail));
|
|
146
145
|
}
|
|
147
146
|
if (fails.length) transaction.add_header('X-Haraka-Fail-Pre', fails.join(' '));
|
|
148
147
|
|
|
149
|
-
|
|
148
|
+
next();
|
|
150
149
|
}
|