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
package/plugins/clamd.js CHANGED
@@ -4,73 +4,71 @@ const sock = require('./line_socket');
4
4
  const utils = require('haraka-utils');
5
5
 
6
6
  exports.load_excludes = function () {
7
- const plugin = this;
8
7
 
9
- plugin.loginfo('Loading excludes file');
10
- const list = plugin.config.get('clamd.excludes','list', () => {
11
- plugin.load_excludes();
8
+ this.loginfo('Loading excludes file');
9
+ const list = this.config.get('clamd.excludes','list', () => {
10
+ this.load_excludes();
12
11
  });
13
12
 
14
13
  const new_skip_list_exclude = [];
15
14
  const new_skip_list = [];
16
- for (let i=0; i < list.length; i++) {
15
+ for (const element of list) {
17
16
  let re;
18
- switch (list[i][0]) {
17
+ switch (element[0]) {
19
18
  case '!':
20
19
 
21
- if (list[i][1] === '/') {
20
+ if (element[1] === '/') {
22
21
  // Regexp exclude
23
22
  try {
24
- re = new RegExp(list[i].substr(2, list[i].length-2),'i');
23
+ re = new RegExp(element.substr(2, element.length-2),'i');
25
24
  new_skip_list_exclude.push(re);
26
25
  }
27
26
  catch (e) {
28
- plugin.logerror(`${e.message} (entry: ${list[i]})`);
27
+ this.logerror(`${e.message} (entry: ${element})`);
29
28
  }
30
29
  }
31
30
  else {
32
31
  // Wildcard exclude
33
32
  try {
34
33
  re = new RegExp(
35
- utils.wildcard_to_regexp(list[i].substr(1)),'i');
34
+ utils.wildcard_to_regexp(element.substr(1)),'i');
36
35
  new_skip_list_exclude.push(re);
37
36
  }
38
37
  catch (e) {
39
- plugin.logerror(`${e.message} (entry: ${list[i]})`);
38
+ this.logerror(`${e.message} (entry: ${element})`);
40
39
  }
41
40
  }
42
41
  break;
43
42
  case '/':
44
43
  // Regexp skip
45
44
  try {
46
- re = new RegExp(list[i].substr(1, list[i].length-2),'i');
45
+ re = new RegExp(element.substr(1, element.length-2),'i');
47
46
  new_skip_list.push(re);
48
47
  }
49
48
  catch (e) {
50
- plugin.logerror(`${e.message} (entry: ${list[i]})`);
49
+ this.logerror(`${e.message} (entry: ${element})`);
51
50
  }
52
51
  break;
53
52
  default:
54
53
  // Wildcard skip
55
54
  try {
56
- re = new RegExp(utils.wildcard_to_regexp(list[i]),'i');
55
+ re = new RegExp(utils.wildcard_to_regexp(element),'i');
57
56
  new_skip_list.push(re);
58
57
  }
59
58
  catch (e) {
60
- plugin.logerror(`${e.message} (entry: ${list[i]})`);
59
+ this.logerror(`${e.message} (entry: ${element})`);
61
60
  }
62
61
  }
63
62
  }
64
63
 
65
64
  // Make the new lists visible
66
- plugin.skip_list_exclude = new_skip_list_exclude;
67
- plugin.skip_list = new_skip_list;
65
+ this.skip_list_exclude = new_skip_list_exclude;
66
+ this.skip_list = new_skip_list;
68
67
  }
69
68
 
70
69
  exports.load_clamd_ini = function () {
71
- const plugin = this;
72
70
 
73
- plugin.cfg = plugin.config.get('clamd.ini', {
71
+ this.cfg = this.config.get('clamd.ini', {
74
72
  booleans: [
75
73
  '-main.randomize_host_order',
76
74
  '-main.only_with_attachments',
@@ -96,7 +94,7 @@ exports.load_clamd_ini = function () {
96
94
  '+check.local_ip'
97
95
  ],
98
96
  }, () => {
99
- plugin.load_clamd_ini();
97
+ this.load_clamd_ini();
100
98
  });
101
99
 
102
100
  const defaults = {
@@ -107,8 +105,8 @@ exports.load_clamd_ini = function () {
107
105
  };
108
106
 
109
107
  for (const key in defaults) {
110
- if (plugin.cfg.main[key] === undefined) {
111
- plugin.cfg.main[key] = defaults[key];
108
+ if (this.cfg.main[key] === undefined) {
109
+ this.cfg.main[key] = defaults[key];
112
110
  }
113
111
  }
114
112
 
@@ -127,39 +125,37 @@ exports.load_clamd_ini = function () {
127
125
  const enabled_reject_opts = [];
128
126
  Object.keys(rejectPatterns).forEach(opt => {
129
127
  all_reject_opts.push(rejectPatterns[opt]);
130
- if (!plugin.cfg.reject[opt]) return;
128
+ if (!this.cfg.reject[opt]) return;
131
129
  enabled_reject_opts.push(rejectPatterns[opt]);
132
130
  });
133
131
 
134
132
  if (enabled_reject_opts.length) {
135
- plugin.allRE = new RegExp(all_reject_opts.join('|'));
136
- plugin.rejectRE = new RegExp(enabled_reject_opts.join('|'));
133
+ this.allRE = new RegExp(all_reject_opts.join('|'));
134
+ this.rejectRE = new RegExp(enabled_reject_opts.join('|'));
137
135
  }
138
136
 
139
137
  // resolve mismatch between docs (...attachment) and code (...attachments)
140
- if (plugin.cfg.main.only_with_attachment !== undefined) {
141
- plugin.cfg.main.only_with_attachments =
142
- !!plugin.cfg.main.only_with_attachment;
138
+ if (this.cfg.main.only_with_attachment !== undefined) {
139
+ this.cfg.main.only_with_attachments =
140
+ !!this.cfg.main.only_with_attachment;
143
141
  }
144
142
  }
145
143
 
146
144
  exports.register = function () {
147
- const plugin = this;
148
- plugin.load_excludes();
149
- plugin.load_clamd_ini();
145
+ this.load_excludes();
146
+ this.load_clamd_ini();
150
147
  }
151
148
 
152
149
  exports.hook_data = function (next, connection) {
153
- const plugin = this;
154
150
 
155
- if (!plugin.cfg.main.only_with_attachments) return next();
151
+ if (!this.cfg.main.only_with_attachments) return next();
156
152
 
157
- if (!plugin.should_check(connection)) return next();
153
+ if (!this.should_check(connection)) return next();
158
154
 
159
155
  const txn = connection.transaction;
160
156
  txn.parse_body = true;
161
157
  txn.attachment_hooks((ctype, filename, body) => {
162
- connection.logdebug(plugin,
158
+ connection.logdebug(this,
163
159
  `found ctype=${ctype}, filename=${filename}`);
164
160
  txn.notes.clamd_found_attachment = true;
165
161
  });
@@ -169,11 +165,10 @@ exports.hook_data = function (next, connection) {
169
165
 
170
166
  exports.hook_data_post = function (next, connection) {
171
167
  const plugin = this;
172
- const txn = connection.transaction;
173
- const cfg = plugin.cfg;
174
-
175
168
  if (!plugin.should_check(connection)) return next();
176
169
 
170
+ const txn = connection.transaction;
171
+ const { cfg } = plugin;
177
172
  // Do we need to run?
178
173
  if (cfg.main.only_with_attachments && !txn.notes.clamd_found_attachment) {
179
174
  connection.logdebug(plugin, 'skipping: no attachments found');
@@ -279,15 +274,15 @@ exports.hook_data_post = function (next, connection) {
279
274
  if (!plugin.cfg.reject.virus) { return next(); }
280
275
 
281
276
  // Check skip list exclusions
282
- for (let i=0; i < plugin.skip_list_exclude.length; i++) {
283
- if (!plugin.skip_list_exclude[i].test(virus)) continue;
277
+ for (const element of plugin.skip_list_exclude) {
278
+ if (!element.test(virus)) continue;
284
279
  return next(DENY,
285
280
  `Message is infected with ${virus || 'UNKNOWN'}`);
286
281
  }
287
282
 
288
283
  // Check skip list
289
- for (let j=0; j < plugin.skip_list.length; j++) {
290
- if (!plugin.skip_list[j].test(virus)) continue;
284
+ for (const element of plugin.skip_list) {
285
+ if (!element.test(virus)) continue;
291
286
  connection.logwarn(plugin, `${virus} matches exclusion`);
292
287
  txn.add_header('X-Haraka-Virus', virus);
293
288
  return next();
@@ -325,31 +320,31 @@ exports.hook_data_post = function (next, connection) {
325
320
  }
326
321
 
327
322
  exports.should_check = function (connection) {
328
- const plugin = this;
329
323
 
330
324
  let result = true; // default
325
+ if (!connection?.transaction) return false
331
326
 
332
- if (plugin.cfg.check.authenticated == false && connection.notes.auth_user) {
333
- connection.transaction.results.add(plugin, { skip: 'authed'});
327
+ if (this.cfg.check.authenticated == false && connection.notes.auth_user) {
328
+ connection.transaction.results.add(this, { skip: 'authed'});
334
329
  result = false;
335
330
  }
336
331
 
337
- if (plugin.cfg.check.relay == false && connection.relaying) {
338
- connection.transaction.results.add(plugin, { skip: 'relay'});
332
+ if (this.cfg.check.relay == false && connection.relaying) {
333
+ connection.transaction.results.add(this, { skip: 'relay'});
339
334
  result = false;
340
335
  }
341
336
 
342
- if (plugin.cfg.check.local_ip == false && connection.remote.is_local) {
343
- connection.transaction.results.add(plugin, { skip: 'local_ip'});
337
+ if (this.cfg.check.local_ip == false && connection.remote.is_local) {
338
+ connection.transaction.results.add(this, { skip: 'local_ip'});
344
339
  result = false;
345
340
  }
346
341
 
347
- if (plugin.cfg.check.private_ip == false && connection.remote.is_private) {
348
- if (plugin.cfg.check.local_ip == true && connection.remote.is_local) {
342
+ if (this.cfg.check.private_ip == false && connection.remote.is_private) {
343
+ if (this.cfg.check.local_ip == true && connection.remote.is_local) {
349
344
  // local IPs are included in private IPs
350
345
  }
351
346
  else {
352
- connection.transaction.results.add(plugin, { skip: 'private_ip'});
347
+ connection.transaction.results.add(this, { skip: 'private_ip'});
353
348
  result = false;
354
349
  }
355
350
  }
@@ -368,18 +363,19 @@ exports.send_clamd_predata = (socket, cb) => {
368
363
  }
369
364
 
370
365
  function clamd_connect (socket, host) {
371
- let match;
366
+
372
367
  if (host.match(/^\//)) {
373
- // assume unix socket
374
- socket.connect(host);
368
+ socket.connect(host); // starts with /, unix socket
369
+ return
375
370
  }
376
- else if ((match = /^\[([^\] ]+)\](?::(\d+))?/.exec(host))) {
377
- // IPv6 literal
378
- socket.connect((match[2] || 3310), match[1]);
379
- }
380
- else {
381
- // IP:port, hostname:port or hostname
382
- const hostport = host.split(/:/);
383
- socket.connect((hostport[1] || 3310), hostport[0]);
371
+
372
+ const match = /^\[([^\] ]+)\](?::(\d+))?/.exec(host);
373
+ if (match) {
374
+ socket.connect((match[2] || 3310), match[1]); // IPv6 literal
375
+ return
384
376
  }
377
+
378
+ // IP:port, hostname:port or hostname
379
+ const hostport = host.split(/:/);
380
+ socket.connect((hostport[1] || 3310), hostport[0]);
385
381
  }
@@ -3,11 +3,15 @@
3
3
 
4
4
  exports.hook_data = (next, connection) => {
5
5
  // enable mail body parsing
6
+ if (!connection?.transaction) return next();
7
+
6
8
  connection.transaction.parse_body = true;
7
9
  next();
8
10
  }
9
11
 
10
12
  exports.hook_data_post = function (next, connection) {
13
+ if (!connection?.transaction) return next();
14
+
11
15
  const sigs = this.config.get('data.signatures', 'list');
12
16
 
13
17
  if (check_sigs(sigs, connection.transaction.body)) {
@@ -18,15 +22,11 @@ exports.hook_data_post = function (next, connection) {
18
22
 
19
23
  function check_sigs (sigs, body) {
20
24
  for (let i=0,l=sigs.length; i < l; i++) {
21
- if (body.bodytext.includes(sigs[i])) {
22
- return 1;
23
- }
25
+ if (body.bodytext.includes(sigs[i])) return 1;
24
26
  }
25
27
 
26
28
  for (let i=0,l=body.children.length; i < l; i++) {
27
- if (check_sigs(sigs, body.children[i])) {
28
- return 1;
29
- }
29
+ if (check_sigs(sigs, body.children[i])) return 1;
30
30
  }
31
31
  return 0;
32
32
  }