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
@@ -11,36 +11,36 @@ exports.register = function () {
11
11
  }
12
12
 
13
13
  exports.load_ini = function () {
14
- const plugin = this;
15
- plugin.cfg = plugin.config.get('mail_from.is_resolvable.ini', {
14
+ this.cfg = this.config.get('mail_from.is_resolvable.ini', {
16
15
  booleans: [
17
16
  '-main.allow_mx_ip',
18
17
  '+main.reject_no_mx',
19
18
  ],
20
19
  }, () => {
21
- plugin.load_ini();
20
+ this.load_ini();
22
21
  });
23
22
 
24
- if (isNaN(plugin.cfg.main.timeout)) {
25
- plugin.cfg.main.timeout = 29;
23
+ if (isNaN(this.cfg.main.timeout)) {
24
+ this.cfg.main.timeout = 29;
26
25
  }
27
26
 
28
- if (plugin.timeout) {
29
- if (plugin.timeout <= plugin.cfg.main.timeout) {
30
- plugin.cfg.main.timeout = plugin.timeout - 1;
31
- plugin.logwarn(`reducing plugin timeout to ${plugin.cfg.main.timeout}s`);
27
+ if (this.timeout) {
28
+ if (this.timeout <= this.cfg.main.timeout) {
29
+ this.cfg.main.timeout = this.timeout - 1;
30
+ this.logwarn(`reducing plugin timeout to ${this.cfg.main.timeout}s`);
32
31
  }
33
32
  }
34
33
 
35
- plugin.re_bogus_ip = new RegExp(plugin.cfg.main.re_bogus_ip ||
34
+ this.re_bogus_ip = new RegExp(this.cfg.main.re_bogus_ip ||
36
35
  '^(?:0\\.0\\.0\\.0|255\\.255\\.255\\.255|127\\.)' );
37
36
  }
38
37
 
39
38
  exports.hook_mail = function (next, connection, params) {
40
39
  const plugin = this;
41
40
  const mail_from = params[0];
42
- const txn = connection.transaction;
43
- const results = txn.results;
41
+ const txn = connection?.transaction;
42
+ if (!txn) return next();
43
+ const { results } = txn;
44
44
 
45
45
  // Check for MAIL FROM without an @ first - ignore those here
46
46
  if (!mail_from.host) {
@@ -83,7 +83,7 @@ exports.hook_mail = function (next, connection, params) {
83
83
  if (pending_queries !== 0) return;
84
84
 
85
85
  records = Object.keys(records);
86
- if (records && records.length) {
86
+ if (records?.length) {
87
87
  connection.logdebug(plugin, `${domain}: ${records}`);
88
88
  results.add(plugin, {pass: 'has_fwd_dns'});
89
89
  return mxDone();
@@ -114,21 +114,21 @@ exports.hook_mail = function (next, connection, params) {
114
114
  return;
115
115
  }
116
116
  connection.logdebug(plugin, `${domain}: MX ${addr.priority} ${addr.exchange} => ${addresses2}`);
117
- for (let i=0; i < addresses2.length; i++) {
117
+ for (const element of addresses2) {
118
118
  // Ignore anything obviously bogus
119
- if (net.isIPv4(addresses2[i])){
120
- if (plugin.re_bogus_ip.test(addresses2[i])) {
121
- connection.logdebug(plugin, `${addr.exchange}: discarding ${addresses2[i]}`);
119
+ if (net.isIPv4(element)){
120
+ if (plugin.re_bogus_ip.test(element)) {
121
+ connection.logdebug(plugin, `${addr.exchange}: discarding ${element}`);
122
122
  continue;
123
123
  }
124
124
  }
125
- if (net.isIPv6(addresses2[i])){
126
- if (net_utils.ipv6_bogus(addresses2[i])) {
127
- connection.logdebug(plugin, `${addr.exchange}: discarding ${addresses2[i]}`);
125
+ if (net.isIPv6(element)){
126
+ if (net_utils.ipv6_bogus(element)) {
127
+ connection.logdebug(plugin, `${addr.exchange}: discarding ${element}`);
128
128
  continue;
129
129
  }
130
130
  }
131
- records[addresses2[i]] = 1;
131
+ records[element] = 1;
132
132
  }
133
133
  check_results();
134
134
  });
@@ -139,11 +139,12 @@ exports.hook_mail = function (next, connection, params) {
139
139
  }
140
140
 
141
141
  exports.mxErr = function (connection, domain, type, err, mxDone) {
142
- const plugin = this;
143
- const txn = connection.transaction;
142
+
143
+ const txn = connection?.transaction;
144
144
  if (!txn) return;
145
- txn.results.add(plugin, {msg: `${domain}:${type}:${err.message}`});
146
- connection.logdebug(plugin, `${domain}:${type} => ${err.message}`);
145
+
146
+ txn.results.add(this, {msg: `${domain}:${type}:${err.message}`});
147
+ connection.logdebug(this, `${domain}:${type} => ${err.message}`);
147
148
  switch (err.code) {
148
149
  case dns.NXDOMAIN:
149
150
  case dns.NOTFOUND:
@@ -159,31 +160,30 @@ exports.mxErr = function (connection, domain, type, err, mxDone) {
159
160
 
160
161
  // IS: IPv6 compatible
161
162
  exports.implicit_mx = function (connection, domain, mxDone) {
162
- const plugin = this;
163
- const txn = connection.transaction;
163
+ const txn = connection?.transaction;
164
+ if (!txn) return;
164
165
 
165
166
  net_utils.get_ips_by_host(domain, (err, addresses) => {
166
167
  if (!txn) return;
167
168
  if (!addresses || !addresses.length) {
168
- txn.results.add(plugin, {fail: 'has_fwd_dns'});
169
- return mxDone(((plugin.cfg.main.reject_no_mx) ? DENY : DENYSOFT),
169
+ txn.results.add(this, {fail: 'has_fwd_dns'});
170
+ return mxDone(((this.cfg.main.reject_no_mx) ? DENY : DENYSOFT),
170
171
  'No MX for your FROM address');
171
172
  }
172
173
 
173
- connection.logdebug(plugin, `${domain}: A/AAAA => ${addresses}`);
174
+ connection.logdebug(this, `${domain}: A/AAAA => ${addresses}`);
174
175
  let records = {};
175
- for (let i=0; i < addresses.length; i++) {
176
- const addr = addresses[i];
176
+ for (const addr of addresses) {
177
177
  // Ignore anything obviously bogus
178
178
  if (net.isIPv4(addr)) {
179
- if (plugin.re_bogus_ip.test(addr)) {
180
- connection.logdebug(plugin, `${domain}: discarding ${addr}`);
179
+ if (this.re_bogus_ip.test(addr)) {
180
+ connection.logdebug(this, `${domain}: discarding ${addr}`);
181
181
  continue;
182
182
  }
183
183
  }
184
184
  if (net.isIPv6(addr)) {
185
185
  if (net_utils.ipv6_bogus(addr)) {
186
- connection.logdebug(plugin, `${domain}: discarding ${addr}`);
186
+ connection.logdebug(this, `${domain}: discarding ${addr}`);
187
187
  continue;
188
188
  }
189
189
  }
@@ -191,12 +191,12 @@ exports.implicit_mx = function (connection, domain, mxDone) {
191
191
  }
192
192
 
193
193
  records = Object.keys(records);
194
- if (records && records.length) {
195
- txn.results.add(plugin, {pass: 'implicit_mx'});
194
+ if (records?.length) {
195
+ txn.results.add(this, {pass: 'implicit_mx'});
196
196
  return mxDone();
197
197
  }
198
198
 
199
- txn.results.add(plugin, {fail: `implicit_mx(${domain})`});
199
+ txn.results.add(this, {fail: `implicit_mx(${domain})`});
200
200
  return mxDone();
201
201
  });
202
202
  }
@@ -13,27 +13,26 @@ exports.register = function () {
13
13
  }
14
14
 
15
15
  exports.hook_connect = function (next, connection) {
16
- const self = this;
17
16
  const cfg = this.config.get('messagesniffer.ini');
18
-
19
17
  // Skip any private IP ranges
20
- if (connection.remote.is_private) return next();
18
+ // Skip connection.transaction undefined
19
+ if (connection?.remote?.is_private || !connection?.transaction) return next();
21
20
 
22
21
  // Retrieve GBUdb information for the connecting IP
23
22
  SNFClient(`<snf><xci><gbudb><test ip='${connection.remote.ip}'/></gbudb></xci></snf>`, (err, result) => {
24
23
  if (err) {
25
- connection.logerror(self, err.message);
24
+ connection.logerror(this, err.message);
26
25
  return next();
27
26
  }
28
27
  let match;
29
28
  if ((match = /<result ((?:(?!\/>)[^])+)\/>/.exec(result))) {
30
29
  // Log result
31
- connection.loginfo(self, match[1]);
30
+ connection.loginfo(this, match[1]);
32
31
  // Populate result
33
32
  const gbudb = {};
34
33
  const split = match[1].toString().split(/\s+/);
35
- for (let i=0; i<split.length; i++) {
36
- const split2 = split[i].split(/=/);
34
+ for (const element of split) {
35
+ const split2 = element.split(/=/);
37
36
  gbudb[split2[0]] = split2[1].replace(/(?:^'|'$)/g,'');
38
37
  }
39
38
  // Set notes for other plugins
@@ -52,8 +51,8 @@ exports.hook_connect = function (next, connection) {
52
51
  case 'caution':
53
52
  case 'black':
54
53
  case 'truncate':
55
- if (cfg.gbudb && cfg.gbudb[gbudb.range]) {
56
- connection.loginfo(self, `range=${gbudb.range} action=${cfg.gbudb[gbudb.range]}`);
54
+ if (cfg.gbudb?.[gbudb.range]) {
55
+ connection.loginfo(this, `range=${gbudb.range} action=${cfg.gbudb[gbudb.range]}`);
57
56
  switch (cfg.gbudb[gbudb.range]) {
58
57
  case 'accept':
59
58
  // Whitelist
@@ -89,20 +88,19 @@ exports.hook_connect = function (next, connection) {
89
88
  return next();
90
89
  default:
91
90
  // Unknown
92
- connection.logerror(self, `Unknown GBUdb range: ${gbudb.range}`);
93
- return next();
91
+ connection.logerror(this, `Unknown GBUdb range: ${gbudb.range}`);
92
+ next();
94
93
  }
95
94
  }
96
95
  else {
97
- return next();
96
+ next();
98
97
  }
99
98
  });
100
99
  }
101
100
 
102
101
  exports.hook_data_post = function (next, connection) {
103
- const self = this;
104
102
  const cfg = this.config.get('messagesniffer.ini');
105
- const txn = connection.transaction;
103
+ const txn = connection?.transaction;
106
104
  if (!txn) return next();
107
105
 
108
106
  function tag_subject (){
@@ -120,7 +118,7 @@ exports.hook_data_post = function (next, connection) {
120
118
  }
121
119
 
122
120
  // Check GBUdb results
123
- if (connection.notes.gbudb && connection.notes.gbudb.action) {
121
+ if (connection.notes.gbudb?.action) {
124
122
  switch (connection.notes.gbudb.action) {
125
123
  case 'accept':
126
124
  case 'quarantine':
@@ -137,8 +135,8 @@ exports.hook_data_post = function (next, connection) {
137
135
  const ws = fs.createWriteStream(tmpfile);
138
136
 
139
137
  ws.once('error', err => {
140
- connection.logerror(self, `Error writing temporary file: ${err.message}`);
141
- return next();
138
+ connection.logerror(this, `Error writing temporary file: ${err.message}`);
139
+ next();
142
140
  });
143
141
 
144
142
  ws.once('close', () => {
@@ -162,8 +160,7 @@ exports.hook_data_post = function (next, connection) {
162
160
  // Parse the returned headers and add them to the message
163
161
  const xhdr = match[1].split('\r\n');
164
162
  const headers = [];
165
- for (let i=0; i < xhdr.length; i++) {
166
- const line = xhdr[i];
163
+ for (const line of xhdr) {
167
164
  // Check for continuation
168
165
  if (/^\s/.test(line)) {
169
166
  // Continuation; add to previous header value
@@ -180,8 +177,7 @@ exports.hook_data_post = function (next, connection) {
180
177
  }
181
178
  }
182
179
  // Add headers to message
183
- for (let h=0; h < headers.length; h++) {
184
- const header = headers[h];
180
+ for (const header of headers) {
185
181
  // If present save the group for logging purposes
186
182
  if (header.header === 'X-MessageSniffer-SNF-Group') {
187
183
  group = header.value.replace(/\r?\n/gm, '');
@@ -191,11 +187,11 @@ exports.hook_data_post = function (next, connection) {
191
187
  // Retrieve IP address determined by GBUdb
192
188
  const gbudb_split = header.value.split(/,\s*/);
193
189
  gbudb_ip = gbudb_split[1];
194
- connection.logdebug(self, `GBUdb: ${header.value.replace(/\r?\n/gm, '')}`);
190
+ connection.logdebug(this, `GBUdb: ${header.value.replace(/\r?\n/gm, '')}`);
195
191
  }
196
192
  if (header.header === 'X-MessageSniffer-Rules') {
197
193
  rules = header.value.replace(/\r?\n/gm, '').replace(/\s+/g,' ').trim();
198
- connection.logdebug(self, `rules: ${rules}`);
194
+ connection.logdebug(this, `rules: ${rules}`);
199
195
  }
200
196
  // Remove any existing headers
201
197
  txn.remove_header(header.header);
@@ -203,7 +199,7 @@ exports.hook_data_post = function (next, connection) {
203
199
  }
204
200
  }
205
201
  // Summary log
206
- connection.loginfo(self, `result: time=${elapsed}ms code=${code
202
+ connection.loginfo(this, `result: time=${elapsed}ms code=${code
207
203
  }${gbudb_ip ? ` ip="${gbudb_ip}"` : ''
208
204
  }${group ? ` group="${group}"` : ''
209
205
  }${rules ? ` rule_count=${rules.split(/\s+/).length}` : ''
@@ -249,7 +245,7 @@ exports.hook_data_post = function (next, connection) {
249
245
  action = cfg.message.nonzero;
250
246
  }
251
247
  else {
252
- return next(DENY, `${'Spam detected by MessageSniffer (code='}${code} group=${group})`);
248
+ return next(DENY, `Spam detected by MessageSniffer (code=${code} group=${group})`);
253
249
  }
254
250
  }
255
251
  }
@@ -258,7 +254,7 @@ exports.hook_data_post = function (next, connection) {
258
254
  else {
259
255
  // Default with no configuration
260
256
  if (code > 1 && code !== 40) {
261
- return next(DENY, `${'Spam detected by MessageSniffer (code='}${code} group=${group})`);
257
+ return next(DENY, `Spam detected by MessageSniffer (code=${code} group=${group})`);
262
258
  }
263
259
  else {
264
260
  return next();
@@ -318,7 +314,7 @@ exports.hook_data_post = function (next, connection) {
318
314
  }
319
315
  else {
320
316
  // Something must have gone wrong
321
- connection.logwarn(self, `unexpected response: ${result}`);
317
+ connection.logwarn(this, `unexpected response: ${result}`);
322
318
  }
323
319
  return next();
324
320
  });
@@ -329,7 +325,6 @@ exports.hook_data_post = function (next, connection) {
329
325
  }
330
326
 
331
327
  exports.hook_disconnect = function (next, connection) {
332
- const self = this;
333
328
  const cfg = this.config.get('messagesniffer.ini');
334
329
 
335
330
  // Train GBUdb on rejected messages and recipients
@@ -338,16 +333,16 @@ exports.hook_disconnect = function (next, connection) {
338
333
  const snfreq = `<snf><xci><gbudb><bad ip='${connection.remote.ip}'/></gbudb></xci></snf>`;
339
334
  SNFClient(snfreq, (err, result) => {
340
335
  if (err) {
341
- connection.logerror(self, err.message);
336
+ connection.logerror(this, err.message);
342
337
  }
343
338
  else {
344
- connection.logdebug(self, `GBUdb bad encounter added for ${connection.remote.ip}`);
339
+ connection.logdebug(this, `GBUdb bad encounter added for ${connection.remote.ip}`);
345
340
  }
346
- return next();
341
+ next();
347
342
  });
348
343
  }
349
344
  else {
350
- return next();
345
+ next();
351
346
  }
352
347
  }
353
348
 
@@ -357,7 +352,7 @@ function SNFClient (req, cb) {
357
352
  sock.setTimeout(30 * 1000); // Connection timeout
358
353
  sock.once('timeout', function () {
359
354
  this.destroy();
360
- return cb(new Error('connection timed out'));
355
+ cb(new Error('connection timed out'));
361
356
  });
362
357
  sock.once('error', err => cb(err));
363
358
  sock.once('connect', function () {
@@ -372,16 +367,14 @@ function SNFClient (req, cb) {
372
367
  });
373
368
  sock.once('end', () => {
374
369
  // Check for result
370
+ if (/<result /.exec(result)) return cb(null, result);
371
+
375
372
  let match;
376
- if (/<result /.exec(result)) {
377
- return cb(null, result);
378
- }
379
- else if ((match = /<error message='([^']+)'/.exec(result))) {
373
+ if ((match = /<error message='([^']+)'/.exec(result))) {
380
374
  return cb(new Error(match[1]));
381
375
  }
382
- else {
383
- return cb(new Error(`unexpected result: ${result}`));
384
- }
376
+
377
+ cb(new Error(`unexpected result: ${result}`));
385
378
  });
386
379
  // Start the sequence
387
380
  sock.connect(port);
@@ -6,8 +6,10 @@ function escapeRegExp (str) {
6
6
  }
7
7
 
8
8
  exports.hook_data = (next, connection) => {
9
- if (connection.notes.auth_user && connection.notes.auth_passwd) {
10
- connection.transaction.parse_body = true;
9
+ const { notes, transaction } = connection ?? {}
10
+
11
+ if (transaction && notes?.auth_user && notes.auth_passwd) {
12
+ transaction.parse_body = true;
11
13
  }
12
14
  next();
13
15
  }
@@ -19,8 +21,8 @@ exports.hook_data_post = (next, connection) => {
19
21
 
20
22
  let user = connection.notes.auth_user;
21
23
  let domain;
22
- let idx;
23
- if ((idx = user.indexOf('@'))) {
24
+ const idx = user.indexOf('@')
25
+ if (idx) {
24
26
  // If the username is qualified (e.g. user@domain.com)
25
27
  // then we make the @domain.com part optional in the regexp.
26
28
  domain = user.substr(idx);
@@ -34,7 +36,7 @@ exports.hook_data_post = (next, connection) => {
34
36
  (domain ? `(?:${escapeRegExp(domain)})?` : '') +
35
37
  bound_regexp, 'im');
36
38
 
37
- if (look_for_credentials(user_regexp, passwd_regexp, connection.transaction.body)) {
39
+ if (look_for_credentials(user_regexp, passwd_regexp, connection?.transaction?.body)) {
38
40
  return next(DENY, "Credential leak detected: never give out your username/password to anyone!");
39
41
  }
40
42
 
@@ -59,7 +59,7 @@ exports.hook_init_master = function (next, server) {
59
59
  process.title = title;
60
60
  server.notes.pt_concurrent_cluster = {};
61
61
  server.notes.pt_new_out_stats = [0,0,0,0];
62
- const cluster = server.cluster;
62
+ const { cluster } = server;
63
63
  const recvMsg = msg => {
64
64
  let count;
65
65
  switch (msg.event) {
@@ -120,7 +120,7 @@ exports.hook_init_master = function (next, server) {
120
120
  });
121
121
  }
122
122
  this._interval = setupInterval(title, server);
123
- return next();
123
+ next();
124
124
  }
125
125
 
126
126
  exports.hook_init_child = function (next, server) {
@@ -134,10 +134,9 @@ exports.hook_init_child = function (next, server) {
134
134
  server.notes.pt_messages = 0;
135
135
  server.notes.pt_mps_diff = 0;
136
136
  server.notes.pt_mps_max = 0;
137
- const title = 'Haraka (worker)';
138
- process.title = title;
139
- this._interval = setupInterval(title, server);
140
- return next();
137
+ process.title = 'Haraka (worker)';
138
+ this._interval = setupInterval(process.title, server);
139
+ next();
141
140
  }
142
141
 
143
142
  exports.shutdown = function () {
@@ -146,19 +145,19 @@ exports.shutdown = function () {
146
145
  }
147
146
 
148
147
  exports.hook_connect_init = (next, connection) => {
149
- const server = connection.server;
148
+ const { server } = connection;
150
149
  connection.notes.pt_connect_run = true;
151
150
  if (server.cluster) {
152
- const worker = server.cluster.worker;
151
+ const { worker } = server.cluster;
153
152
  worker.send({event: 'process_title.connect', wid: worker.id});
154
153
  }
155
154
  server.notes.pt_connections++;
156
155
  server.notes.pt_concurrent++;
157
- return next();
156
+ next();
158
157
  }
159
158
 
160
159
  exports.hook_disconnect = (next, connection) => {
161
- const server = connection.server;
160
+ const { server } = connection;
162
161
  // Check that the hook above ran
163
162
  // It might not if the disconnection is immediate
164
163
  // echo "QUIT" | nc localhost 25
@@ -177,25 +176,25 @@ exports.hook_disconnect = (next, connection) => {
177
176
  worker.send({event: 'process_title.disconnect', wid: worker.id});
178
177
  }
179
178
  server.notes.pt_concurrent--;
180
- return next();
179
+ next();
181
180
  }
182
181
 
183
182
  exports.hook_rcpt = (next, connection) => {
184
- const server = connection.server;
183
+ const { server } = connection;
185
184
  if (server.cluster) {
186
- const worker = server.cluster.worker;
185
+ const { worker } = server.cluster;
187
186
  worker.send({event: 'process_title.recipient'});
188
187
  }
189
188
  server.notes.pt_recipients++;
190
- return next();
189
+ next();
191
190
  }
192
191
 
193
192
  exports.hook_data = (next, connection) => {
194
- const server = connection.server;
193
+ const { server } = connection;
195
194
  if (server.cluster) {
196
- const worker = server.cluster.worker;
195
+ const { worker } = server.cluster;
197
196
  worker.send({event: 'process_title.message'});
198
197
  }
199
198
  server.notes.pt_messages++;
200
- return next();
199
+ next();
201
200
  }
@@ -5,9 +5,9 @@
5
5
  const outbound = require('./outbound');
6
6
 
7
7
  exports.hook_queue_outbound = (next, connection) => {
8
- if (!connection.relaying) {
8
+ if (!connection?.relaying) {
9
9
  return next(); // we're not relaying so don't deliver outbound
10
10
  }
11
11
 
12
- outbound.send_email(connection.transaction, next);
12
+ outbound.send_email(connection?.transaction, next);
13
13
  }
@@ -10,20 +10,18 @@ exports.register = function () {
10
10
  }
11
11
 
12
12
  exports.load_lmtp_ini = function () {
13
- const plugin = this;
14
- plugin.cfg = plugin.config.get('lmtp.ini', () => {
15
- plugin.load_lmtp_ini();
13
+ this.cfg = this.config.get('lmtp.ini', () => {
14
+ this.load_lmtp_ini();
16
15
  })
17
16
  }
18
17
 
19
18
  exports.hook_get_mx = function (next, hmail, domain) {
20
- const plugin = this;
21
19
 
22
20
  if (!hmail.todo.notes.using_lmtp) return next();
23
21
 
24
22
  const mx = { using_lmtp: true, priority: 0, exchange: '127.0.0.1' };
25
23
 
26
- const section = plugin.cfg[domain] || plugin.cfg.main;
24
+ const section = this.cfg[domain] || this.cfg.main;
27
25
  if (section.path) {
28
26
  Object.assign(mx, { path: section.path });
29
27
  return next(OK, mx);
@@ -38,7 +36,8 @@ exports.hook_get_mx = function (next, hmail, domain) {
38
36
  }
39
37
 
40
38
  exports.hook_queue = (next, connection) => {
41
- const txn = connection.transaction;
39
+ const txn = connection?.transaction;
40
+ if (!txn) return next();
42
41
 
43
42
  const q_wants = txn.notes.get('queue.wants');
44
43
  if (q_wants && q_wants !== 'lmtp') return next();
@@ -4,37 +4,36 @@ const childproc = require('child_process');
4
4
  const fs = require('fs');
5
5
 
6
6
  exports.register = function () {
7
- const plugin = this;
8
7
 
9
- plugin.queue_exec = plugin.config.get('qmail-queue.path') || '/var/qmail/bin/qmail-queue';
10
- if (!fs.existsSync(plugin.queue_exec)) {
11
- throw new Error(`Cannot find qmail-queue binary (${plugin.queue_exec})`);
8
+ this.queue_exec = this.config.get('qmail-queue.path') || '/var/qmail/bin/qmail-queue';
9
+ if (!fs.existsSync(this.queue_exec)) {
10
+ throw new Error(`Cannot find qmail-queue binary (${this.queue_exec})`);
12
11
  }
13
12
 
14
- plugin.load_qmail_queue_ini();
13
+ this.load_qmail_queue_ini();
15
14
 
16
- if (plugin.cfg.main.enable_outbound) {
17
- plugin.register_hook('queue_outbound', 'hook_queue');
15
+ if (this.cfg.main.enable_outbound) {
16
+ this.register_hook('queue_outbound', 'hook_queue');
18
17
  }
19
18
  }
20
19
 
21
20
  exports.load_qmail_queue_ini = function () {
22
- const plugin = this;
23
21
 
24
- plugin.cfg = plugin.config.get('qmail-queue.ini', {
22
+ this.cfg = this.config.get('qmail-queue.ini', {
25
23
  booleans: [
26
24
  '+main.enable_outbound',
27
25
  ],
28
26
  },
29
27
  () => {
30
- plugin.load_qmail_queue_ini();
28
+ this.load_qmail_queue_ini();
31
29
  });
32
30
  }
33
31
 
34
32
  exports.hook_queue = function (next, connection) {
35
33
  const plugin = this;
36
34
 
37
- const txn = connection.transaction;
35
+ const txn = connection?.transaction;
36
+ if (!txn) return next();
38
37
 
39
38
  const q_wants = txn.notes.get('queue.wants');
40
39
  if (q_wants && q_wants !== 'qmail-queue') return next();
@@ -58,9 +57,8 @@ exports.hook_queue = function (next, connection) {
58
57
  connection.transaction.message_stream.pipe(qmail_queue.stdin, { line_endings: '\n' });
59
58
 
60
59
  qmail_queue.stdin.on('close', () => {
61
- if (!connection.transaction) {
60
+ if (!connection?.transaction) {
62
61
  plugin.logerror("Transaction went away while delivering mail to qmail-queue");
63
-
64
62
  try {
65
63
  qmail_queue.stdout.end();
66
64
  }