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.
Files changed (135) hide show
  1. package/.eslintrc.yaml +2 -10
  2. package/Changes.md +84 -2
  3. package/Dockerfile +1 -1
  4. package/Plugins.md +9 -4
  5. package/README.md +2 -6
  6. package/bin/haraka +5 -4
  7. package/config/outbound.ini +0 -7
  8. package/config/plugins +1 -1
  9. package/config/smtp.ini +1 -1
  10. package/config/smtp_forward.ini +2 -8
  11. package/config/smtp_proxy.ini +0 -6
  12. package/connection.js +178 -204
  13. package/coverage/lcov.info +13863 -0
  14. package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
  15. package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
  16. package/dkim.js +66 -73
  17. package/docs/Body.md +1 -22
  18. package/docs/CoreConfig.md +2 -2
  19. package/docs/Header.md +1 -47
  20. package/docs/Outbound.md +8 -36
  21. package/endpoint.js +1 -1
  22. package/haraka.js +1 -1
  23. package/host_pool.js +8 -12
  24. package/logger.js +25 -32
  25. package/outbound/client_pool.js +11 -153
  26. package/outbound/config.js +5 -11
  27. package/outbound/hmail.js +109 -143
  28. package/outbound/index.js +13 -25
  29. package/outbound/mx_lookup.js +10 -7
  30. package/outbound/queue.js +8 -12
  31. package/outbound/timer_queue.js +2 -4
  32. package/outbound/tls.js +17 -18
  33. package/outbound/todo.js +1 -0
  34. package/package.json +57 -55
  35. package/plugins/auth/auth_base.js +39 -63
  36. package/plugins/auth/auth_bridge.js +3 -4
  37. package/plugins/auth/auth_proxy.js +16 -16
  38. package/plugins/auth/auth_vpopmaild.js +30 -37
  39. package/plugins/auth/flat_file.js +9 -13
  40. package/plugins/avg.js +9 -11
  41. package/plugins/backscatterer.js +1 -1
  42. package/plugins/block_me.js +2 -6
  43. package/plugins/bounce.js +106 -124
  44. package/plugins/clamd.js +59 -63
  45. package/plugins/data.signatures.js +6 -6
  46. package/plugins/data.uribl.js +1 -415
  47. package/plugins/delay_deny.js +19 -20
  48. package/plugins/dkim_sign.js +56 -62
  49. package/plugins/dkim_verify.js +9 -8
  50. package/plugins/dns_list_base.js +43 -42
  51. package/plugins/dnsbl.js +41 -46
  52. package/plugins/dnswl.js +23 -26
  53. package/plugins/early_talker.js +24 -28
  54. package/plugins/esets.js +8 -11
  55. package/plugins/greylist.js +161 -190
  56. package/plugins/helo.checks.js +175 -197
  57. package/plugins/mail_from.is_resolvable.js +38 -38
  58. package/plugins/messagesniffer.js +33 -40
  59. package/plugins/prevent_credential_leaks.js +7 -5
  60. package/plugins/process_title.js +16 -17
  61. package/plugins/queue/deliver.js +2 -2
  62. package/plugins/queue/lmtp.js +5 -6
  63. package/plugins/queue/qmail-queue.js +11 -13
  64. package/plugins/queue/quarantine.js +25 -34
  65. package/plugins/queue/rabbitmq.js +3 -2
  66. package/plugins/queue/rabbitmq_amqplib.js +9 -9
  67. package/plugins/queue/smtp_bridge.js +5 -4
  68. package/plugins/queue/smtp_forward.js +81 -89
  69. package/plugins/queue/smtp_proxy.js +21 -22
  70. package/plugins/queue/test.js +2 -1
  71. package/plugins/rcpt_to.host_list_base.js +20 -30
  72. package/plugins/rcpt_to.in_host_list.js +12 -14
  73. package/plugins/rcpt_to.max_count.js +7 -5
  74. package/plugins/record_envelope_addresses.js +4 -6
  75. package/plugins/relay.js +64 -74
  76. package/plugins/reseed_rng.js +1 -2
  77. package/plugins/spamassassin.js +56 -68
  78. package/plugins/status.js +2 -3
  79. package/plugins/tarpit.js +8 -11
  80. package/plugins/tls.js +14 -17
  81. package/plugins/toobusy.js +6 -8
  82. package/plugins/xclient.js +14 -25
  83. package/plugins.js +24 -29
  84. package/rfc1869.js +2 -2
  85. package/server.js +3 -13
  86. package/smtp_client.js +138 -215
  87. package/tests/config/smtp_forward.ini +0 -6
  88. package/tests/fixtures/line_socket.js +1 -1
  89. package/tests/fixtures/util_hmailitem.js +5 -7
  90. package/tests/fixtures/vm_harness.js +2 -2
  91. package/tests/host_pool.js +13 -14
  92. package/tests/installation/plugins/inherits.js +1 -2
  93. package/tests/logger.js +2 -2
  94. package/tests/plugins/bounce.js +6 -8
  95. package/tests/plugins/dkim_signer.js +7 -7
  96. package/tests/plugins/dns_list_base.js +7 -7
  97. package/tests/plugins/helo.checks.js +1 -1
  98. package/tests/plugins/mail_from.is_resolvable.js +10 -54
  99. package/tests/plugins/queue/smtp_forward.js +11 -11
  100. package/tests/plugins/rcpt_to.host_list_base.js +1 -1
  101. package/tests/plugins/rcpt_to.in_host_list.js +1 -1
  102. package/tests/plugins/spamassassin.js +1 -1
  103. package/tests/queue/multibyte +0 -0
  104. package/tests/queue/plain +0 -0
  105. package/tests/rfc1869.js +4 -1
  106. package/tests/server.js +15 -9
  107. package/tests/smtp_client/auth.js +4 -14
  108. package/tests/smtp_client/basic.js +5 -15
  109. package/tests/smtp_client.js +7 -3
  110. package/tests/transaction.js +72 -19
  111. package/tls_socket.js +75 -85
  112. package/transaction.js +7 -9
  113. package/attachment_stream.js +0 -118
  114. package/bin/spf +0 -48
  115. package/chunkemitter.js +0 -75
  116. package/config/data.uribl.excludes +0 -202
  117. package/config/data.uribl.ini +0 -37
  118. package/config/spf.ini +0 -1
  119. package/docs/plugins/attachment.md +0 -92
  120. package/docs/plugins/data.uribl.md +0 -120
  121. package/docs/plugins/spf.md +0 -142
  122. package/mailbody.js +0 -502
  123. package/mailheader.js +0 -304
  124. package/messagestream.js +0 -441
  125. package/plugins/aliases.js +0 -120
  126. package/plugins/attachment.js +0 -503
  127. package/plugins/connect.p0f.js +0 -5
  128. package/plugins/spf.js +0 -327
  129. package/spf.js +0 -689
  130. package/tests/mailbody.js +0 -348
  131. package/tests/mailheader.js +0 -138
  132. package/tests/messagestream.js +0 -34
  133. package/tests/plugins/aliases.js +0 -376
  134. package/tests/plugins/spf.js +0 -251
  135. package/tests/spf.js +0 -96
@@ -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
- // Override regexps if top_level_tlds file is present
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
  }
@@ -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 plugin = this;
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 && included.length) {
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 && skip.length) { // 'excluded' mode: delay deny everything except in skip list
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(plugin, `not delaying excluded plugin: ${pi_name}`);
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(plugin, `not delaying excluded hook: ${pi_hook} in plugin: ${pi_name}`);
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(plugin, `not delaying excluded function: ${pi_function} on hook: ${pi_hook} in plugin: ${pi_name}`);
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
- return next();
96
+ next();
98
97
  }
99
98
  }
100
99
 
101
100
  exports.hook_rcpt_ok = function (next, connection, rcpt) {
102
- const plugin = this;
103
- const transaction = connection.transaction;
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(plugin, 'bypassing all pre-DATA deny: AUTH/RELAY');
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.delay_deny_pre) {
114
- for (let i=0; i<connection.notes.delay_deny_pre.length; i++) {
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.delay_deny_pre) {
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
- return next();
131
+ next();
134
132
  }
135
133
 
136
134
  exports.hook_data = (next, connection) => {
137
- const transaction = connection.transaction;
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.delay_deny_pre_fail) {
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.delay_deny_pre_fail) {
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
- return next();
148
+ next();
150
149
  }