Haraka 3.0.2 → 3.0.4

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 (267) hide show
  1. package/.eslintrc.yaml +5 -9
  2. package/.prettierrc.yml +1 -0
  3. package/CONTRIBUTORS.md +11 -0
  4. package/Changes.md +1393 -1211
  5. package/Dockerfile +3 -3
  6. package/Plugins.md +119 -106
  7. package/README.md +7 -16
  8. package/TODO +1 -24
  9. package/bin/haraka +197 -298
  10. package/config/auth_flat_file.ini +2 -0
  11. package/config/auth_vpopmaild.ini +4 -2
  12. package/config/dhparams.pem +8 -0
  13. package/config/mail_from.is_resolvable.ini +4 -2
  14. package/config/me +1 -0
  15. package/config/outbound.ini +0 -2
  16. package/config/plugins +36 -35
  17. package/config/rabbitmq_amqplib.ini +8 -1
  18. package/config/smtp.ini +0 -1
  19. package/config/smtp.json +17 -0
  20. package/config/tls_cert.pem +23 -0
  21. package/config/tls_key.pem +28 -0
  22. package/connection.js +46 -73
  23. package/contrib/bsd-rc.d/haraka +3 -1
  24. package/contrib/plugin2npm.sh +6 -36
  25. package/docs/Connection.md +1 -1
  26. package/docs/CoreConfig.md +2 -2
  27. package/docs/Logging.md +7 -21
  28. package/docs/Outbound.md +104 -210
  29. package/docs/Plugins.md +47 -40
  30. package/docs/Transaction.md +59 -82
  31. package/docs/{plugins → deprecated}/connect.rdns_access.md +1 -1
  32. package/docs/{plugins → deprecated}/mail_from.access.md +1 -1
  33. package/docs/{plugins → deprecated}/rcpt_to.access.md +1 -1
  34. package/docs/plugins/auth/auth_vpopmaild.md +15 -19
  35. package/docs/plugins/auth/flat_file.md +23 -30
  36. package/docs/plugins/queue/rabbitmq_amqplib.md +7 -0
  37. package/docs/plugins/queue/smtp_forward.md +1 -1
  38. package/docs/plugins/queue/smtp_proxy.md +5 -10
  39. package/docs/plugins/relay.md +2 -2
  40. package/docs/plugins/tls.md +29 -9
  41. package/endpoint.js +16 -13
  42. package/haraka.js +10 -14
  43. package/host_pool.js +5 -5
  44. package/line_socket.js +3 -4
  45. package/logger.js +44 -28
  46. package/outbound/client_pool.js +27 -23
  47. package/outbound/config.js +4 -6
  48. package/outbound/fsync_writestream.js +1 -1
  49. package/outbound/hmail.js +180 -220
  50. package/outbound/index.js +86 -99
  51. package/outbound/qfile.js +1 -1
  52. package/outbound/queue.js +55 -43
  53. package/outbound/timer_queue.js +3 -2
  54. package/outbound/tls.js +19 -7
  55. package/package.json +66 -55
  56. package/plugins/.eslintrc.yaml +0 -6
  57. package/plugins/auth/auth_base.js +30 -12
  58. package/plugins/auth/auth_proxy.js +14 -12
  59. package/plugins/auth/auth_vpopmaild.js +30 -20
  60. package/plugins/auth/flat_file.js +17 -12
  61. package/plugins/block_me.js +1 -1
  62. package/plugins/data.signatures.js +2 -4
  63. package/plugins/early_talker.js +2 -1
  64. package/plugins/mail_from.is_resolvable.js +65 -135
  65. package/plugins/queue/deliver.js +4 -5
  66. package/plugins/queue/lmtp.js +11 -14
  67. package/plugins/queue/qmail-queue.js +2 -2
  68. package/plugins/queue/quarantine.js +2 -2
  69. package/plugins/queue/rabbitmq.js +16 -17
  70. package/plugins/queue/rabbitmq_amqplib.js +1 -1
  71. package/plugins/queue/smtp_forward.js +6 -6
  72. package/plugins/queue/smtp_proxy.js +10 -1
  73. package/plugins/queue/test.js +2 -2
  74. package/plugins/rcpt_to.host_list_base.js +5 -5
  75. package/plugins/rcpt_to.in_host_list.js +2 -2
  76. package/plugins/relay.js +6 -7
  77. package/plugins/reseed_rng.js +1 -1
  78. package/plugins/status.js +37 -33
  79. package/plugins/tls.js +2 -2
  80. package/plugins/xclient.js +3 -2
  81. package/plugins.js +51 -54
  82. package/run_tests +3 -30
  83. package/server.js +190 -190
  84. package/smtp_client.js +30 -23
  85. package/{tests → test}/config/plugins +0 -2
  86. package/{tests → test}/config/smtp.ini +1 -1
  87. package/test/config/tls/example.com/_.example.com.key +28 -0
  88. package/test/config/tls/example.com/example.com.crt +25 -0
  89. package/test/connection.js +302 -0
  90. package/test/endpoint.js +94 -0
  91. package/{tests → test}/fixtures/line_socket.js +1 -1
  92. package/{tests → test}/fixtures/util_hmailitem.js +19 -25
  93. package/{tests → test}/host_pool.js +42 -57
  94. package/test/logger.js +258 -0
  95. package/test/outbound/hmail.js +141 -0
  96. package/test/outbound/index.js +220 -0
  97. package/test/outbound/qfile.js +126 -0
  98. package/test/outbound_bounce_net_errors.js +142 -0
  99. package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
  100. package/test/plugins/auth/auth_base.js +484 -0
  101. package/test/plugins/auth/auth_vpopmaild.js +83 -0
  102. package/test/plugins/early_talker.js +104 -0
  103. package/test/plugins/mail_from.is_resolvable.js +35 -0
  104. package/test/plugins/queue/smtp_forward.js +206 -0
  105. package/test/plugins/rcpt_to.host_list_base.js +122 -0
  106. package/test/plugins/rcpt_to.in_host_list.js +193 -0
  107. package/test/plugins/relay.js +303 -0
  108. package/test/plugins/status.js +130 -0
  109. package/test/plugins/tls.js +70 -0
  110. package/test/plugins.js +228 -0
  111. package/{tests → test}/queue/multibyte +0 -0
  112. package/{tests → test}/queue/plain +0 -0
  113. package/test/rfc1869.js +73 -0
  114. package/test/server.js +491 -0
  115. package/test/smtp_client.js +299 -0
  116. package/test/tls_socket.js +273 -0
  117. package/test/transaction.js +270 -0
  118. package/tls_socket.js +202 -252
  119. package/transaction.js +9 -24
  120. package/CONTRIBUTING.md +0 -1
  121. package/bin/dkimverify +0 -40
  122. package/config/access.domains +0 -13
  123. package/config/attachment.ctype.regex +0 -2
  124. package/config/attachment.filename.regex +0 -1
  125. package/config/avg.ini +0 -5
  126. package/config/bounce.ini +0 -15
  127. package/config/data.headers.ini +0 -61
  128. package/config/dkim/dkim_key_gen.sh +0 -78
  129. package/config/dkim_sign.ini +0 -4
  130. package/config/dkim_verify.ini +0 -7
  131. package/config/dnsbl.ini +0 -23
  132. package/config/greylist.ini +0 -43
  133. package/config/helo.checks.ini +0 -52
  134. package/config/lookup_rdns.strict.ini +0 -12
  135. package/config/lookup_rdns.strict.timeout +0 -1
  136. package/config/lookup_rdns.strict.whitelist +0 -1
  137. package/config/lookup_rdns.strict.whitelist_regex +0 -5
  138. package/config/messagesniffer.ini +0 -18
  139. package/config/rcpt_to.blocklist +0 -1
  140. package/config/rdns.allow_regexps +0 -0
  141. package/config/rdns.deny_regexps +0 -0
  142. package/config/spamassassin.ini +0 -56
  143. package/config.js +0 -6
  144. package/dkim.js +0 -614
  145. package/docs/plugins/avg.md +0 -35
  146. package/docs/plugins/bounce.md +0 -69
  147. package/docs/plugins/clamd.md +0 -147
  148. package/docs/plugins/esets.md +0 -8
  149. package/docs/plugins/greylist.md +0 -90
  150. package/docs/plugins/helo.checks.md +0 -135
  151. package/docs/plugins/messagesniffer.md +0 -163
  152. package/docs/plugins/relay_acl.md +0 -29
  153. package/docs/plugins/relay_all.md +0 -15
  154. package/docs/plugins/relay_force_routing.md +0 -33
  155. package/docs/plugins/spamassassin.md +0 -180
  156. package/outbound/mx_lookup.js +0 -70
  157. package/plugins/auth/auth_ldap.js +0 -3
  158. package/plugins/avg.js +0 -162
  159. package/plugins/backscatterer.js +0 -25
  160. package/plugins/bounce.js +0 -381
  161. package/plugins/clamd.js +0 -381
  162. package/plugins/data.headers.js +0 -4
  163. package/plugins/data.uribl.js +0 -4
  164. package/plugins/dkim_sign.js +0 -395
  165. package/plugins/dkim_verify.js +0 -62
  166. package/plugins/dns_list_base.js +0 -221
  167. package/plugins/dnsbl.js +0 -146
  168. package/plugins/dnswl.js +0 -58
  169. package/plugins/esets.js +0 -71
  170. package/plugins/graph.js +0 -5
  171. package/plugins/greylist.js +0 -645
  172. package/plugins/helo.checks.js +0 -533
  173. package/plugins/messagesniffer.js +0 -381
  174. package/plugins/rcpt_to.ldap.js +0 -3
  175. package/plugins/rcpt_to.max_count.js +0 -24
  176. package/plugins/relay_all.js +0 -13
  177. package/plugins/spamassassin.js +0 -384
  178. package/tests/config/dkim/example.com/dns +0 -29
  179. package/tests/config/dkim/example.com/private +0 -6
  180. package/tests/config/dkim/example.com/public +0 -4
  181. package/tests/config/dkim/example.com/selector +0 -1
  182. package/tests/config/dkim.private.key +0 -6
  183. package/tests/config/dkim_sign.ini +0 -4
  184. package/tests/config/helo.checks.ini +0 -52
  185. package/tests/connection.js +0 -327
  186. package/tests/endpoint.js +0 -128
  187. package/tests/fixtures/vm_harness.js +0 -59
  188. package/tests/logger.js +0 -327
  189. package/tests/outbound/hmail.js +0 -112
  190. package/tests/outbound/index.js +0 -324
  191. package/tests/outbound/qfile.js +0 -67
  192. package/tests/outbound_bounce_net_errors.js +0 -173
  193. package/tests/plugins/auth/auth_base.js +0 -463
  194. package/tests/plugins/auth/auth_vpopmaild.js +0 -91
  195. package/tests/plugins/bounce.js +0 -307
  196. package/tests/plugins/clamd.js +0 -224
  197. package/tests/plugins/deprecated/relay_acl.js +0 -140
  198. package/tests/plugins/deprecated/relay_all.js +0 -59
  199. package/tests/plugins/dkim_sign.js +0 -315
  200. package/tests/plugins/dkim_signer.js +0 -108
  201. package/tests/plugins/dns_list_base.js +0 -259
  202. package/tests/plugins/dnsbl.js +0 -101
  203. package/tests/plugins/early_talker.js +0 -115
  204. package/tests/plugins/greylist.js +0 -58
  205. package/tests/plugins/helo.checks.js +0 -525
  206. package/tests/plugins/mail_from.is_resolvable.js +0 -116
  207. package/tests/plugins/queue/smtp_forward.js +0 -221
  208. package/tests/plugins/rcpt_to.host_list_base.js +0 -132
  209. package/tests/plugins/rcpt_to.in_host_list.js +0 -218
  210. package/tests/plugins/relay.js +0 -339
  211. package/tests/plugins/spamassassin.js +0 -171
  212. package/tests/plugins/status.js +0 -138
  213. package/tests/plugins/tls.js +0 -84
  214. package/tests/plugins.js +0 -247
  215. package/tests/rfc1869.js +0 -61
  216. package/tests/server.js +0 -510
  217. package/tests/smtp_client/auth.js +0 -105
  218. package/tests/smtp_client/basic.js +0 -101
  219. package/tests/smtp_client.js +0 -80
  220. package/tests/tls_socket.js +0 -333
  221. package/tests/transaction.js +0 -284
  222. /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
  223. /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
  224. /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
  225. /package/docs/{plugins → deprecated}/dnswl.md +0 -0
  226. /package/docs/{plugins → deprecated}/rcpt_to.routes.md +0 -0
  227. /package/{tests → test}/.eslintrc.yaml +0 -0
  228. /package/{tests → test}/config/auth_flat_file.ini +0 -0
  229. /package/{tests → test}/config/dhparams.pem +0 -0
  230. /package/{tests → test}/config/host_list +0 -0
  231. /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
  232. /package/{tests → test}/config/outbound_tls_key.pem +0 -0
  233. /package/{tests → test}/config/smtp_forward.ini +0 -0
  234. /package/{tests → test}/config/tls/ec.pem +0 -0
  235. /package/{tests → test}/config/tls/haraka.local.pem +0 -0
  236. /package/{tests → test}/config/tls/mismatched.pem +0 -0
  237. /package/{tests → test}/config/tls.ini +0 -0
  238. /package/{tests → test}/config/tls_cert.pem +0 -0
  239. /package/{tests → test}/config/tls_key.pem +0 -0
  240. /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
  241. /package/{tests → test}/installation/config/test-plugin-flat +0 -0
  242. /package/{tests → test}/installation/config/test-plugin.ini +0 -0
  243. /package/{tests → test}/installation/config/tls.ini +0 -0
  244. /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
  245. /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
  246. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
  247. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
  248. /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
  249. /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
  250. /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
  251. /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
  252. /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
  253. /package/{tests → test}/installation/plugins/inherits.js +0 -0
  254. /package/{tests → test}/installation/plugins/load_first.js +0 -0
  255. /package/{tests → test}/installation/plugins/plugin.js +0 -0
  256. /package/{tests → test}/installation/plugins/tls.js +0 -0
  257. /package/{tests → test}/loud/config/dhparams.pem +0 -0
  258. /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
  259. /package/{tests → test}/loud/config/tls.ini +0 -0
  260. /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
  261. /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
  262. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  263. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  264. /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  265. /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  266. /package/{tests → test}/queue/zero-length +0 -0
  267. /package/{tests → test}/test-queue/delete-me +0 -0
package/outbound/hmail.js CHANGED
@@ -1,12 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const events = require('events');
4
- const fs = require('fs');
5
- const dns = require('dns');
6
- const path = require('path');
7
- const net = require('net');
3
+ const events = require('node:events');
4
+ const fs = require('node:fs');
5
+ const dns = require('node:dns');
6
+ const path = require('node:path');
8
7
 
9
- const { Address } = require('address-rfc2821');
8
+ const { Address } = require('address-rfc2821');
10
9
  const config = require('haraka-config');
11
10
  const constants = require('haraka-constants');
12
11
  const DSN = require('haraka-dsn');
@@ -20,7 +19,6 @@ const plugins = require('../plugins');
20
19
 
21
20
  const client_pool = require('./client_pool');
22
21
  const _qfile = require('./qfile');
23
- const mx_lookup = require('./mx_lookup');
24
22
  const outbound = require('./index');
25
23
  const obtls = require('./tls');
26
24
 
@@ -50,6 +48,9 @@ class HMailItem extends events.EventEmitter {
50
48
  const parts = _qfile.parts(filename);
51
49
  if (!parts) throw new Error(`Bad filename: ${filename}`);
52
50
 
51
+ this.cfg = obc.cfg;
52
+ this.obtls = obtls;
53
+ this.name = 'outbound';
53
54
  this.path = filePath;
54
55
  this.filename = filename;
55
56
  this.next_process = parts.next_attempt;
@@ -103,7 +104,7 @@ class HMailItem extends events.EventEmitter {
103
104
 
104
105
  this._stream_bytes_from(this.path, {start: 4, end: todo_len + 3}, (err2, todo_bytes) => {
105
106
  if (todo_bytes.length !== todo_len) {
106
- const wrongLength = `Didn't find right amount of data in todo!: ${err2}`;
107
+ const wrongLength = `Didn't find right amount of data in todo!: ${err2} ${this.path}`;
107
108
  this.logcrit(wrongLength);
108
109
  fs.rename(this.path, path.join(queue_dir, `error.${this.filename}`), (err3) => {
109
110
  if (err3) {
@@ -191,192 +192,132 @@ class HMailItem extends events.EventEmitter {
191
192
  plugins.run_hooks('get_mx', this, domain);
192
193
  }
193
194
 
194
- get_mx_respond (retval, mx) {
195
+ async get_mx_respond (retval, mx) {
195
196
  switch (retval) {
196
197
  case constants.ok: {
198
+ this.logdebug(`MX from Plugin: ${this.todo.domain} => 0 ${JSON.stringify(mx)}`);
197
199
  let mx_list;
198
200
  if (Array.isArray(mx)) {
199
- mx_list = mx;
200
- }
201
- else if (typeof mx === "object") {
202
- mx_list = [mx];
201
+ mx_list = mx.map(m => new net_utils.HarakaMx(m));
203
202
  }
204
203
  else {
205
- // assume string
206
- const matches = /^(.*?)(:(\d+))?$/.exec(mx);
207
- if (!matches) {
208
- throw ("get_mx returned something that doesn't match hostname or hostname:port");
209
- }
210
- mx_list = [{priority: 0, exchange: matches[1], port: matches[3]}];
204
+ mx_list = [new net_utils.HarakaMx(mx)];
211
205
  }
212
- this.logdebug(`Got a MX from Plugin: ${this.todo.domain} => 0 ${JSON.stringify(mx)}`);
213
- return this.found_mx(null, mx_list);
206
+ return this.found_mx(mx_list);
214
207
  }
215
208
  case constants.deny:
216
209
  this.logwarn(`get_mx plugin returned DENY: ${mx}`);
217
- this.todo.rcpt_to.forEach(rcpt => {
210
+ for (const rcpt of this.todo.rcpt_to) {
218
211
  this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No MX for ${this.todo.domain}`));
219
- });
212
+ }
220
213
  return this.bounce(`No MX for ${this.todo.domain}`);
221
214
  case constants.denysoft:
222
215
  this.logwarn(`get_mx plugin returned DENYSOFT: ${mx}`);
223
- this.todo.rcpt_to.forEach(rcpt => {
216
+ for (const rcpt of this.todo.rcpt_to) {
224
217
  this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Temporary MX lookup error for ${this.todo.domain}`, 450));
225
- });
218
+ }
226
219
  return this.temp_fail(`Temporary MX lookup error for ${this.todo.domain}`);
227
220
  }
228
221
 
229
- // if none of the above return codes, drop through to this...
230
- mx_lookup.lookup_mx(this.todo.domain, (err, mxs) => {
231
- this.found_mx(err, mxs);
232
- });
233
- }
234
-
235
- found_mx (err, mxs) {
236
- if (err) {
237
- this.lognotice(`MX Lookup for ${this.todo.domain} failed: ${err}`);
238
- if (err.code === dns.NXDOMAIN || err.code === dns.NOTFOUND) {
239
- this.todo.rcpt_to.forEach(rcpt => {
240
- this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No Such Domain: ${this.todo.domain}`));
241
- });
242
- this.bounce(`No Such Domain: ${this.todo.domain}`);
222
+ // none of the above return codes, drop through to DNS
223
+ try {
224
+ const exchanges = await net_utils.get_mx(this.todo.domain);
225
+
226
+ if (exchanges.length) {
227
+ this.found_mx(this.sort_mx(exchanges))
243
228
  }
244
- else if (err.code === 'NOMX') {
245
- this.todo.rcpt_to.forEach(rcpt => {
246
- this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Nowhere to deliver mail to for domain: ${this.todo.domain}`));
247
- });
229
+ else {
230
+ for (const rcpt of this.todo.rcpt_to) {
231
+ this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Nowhere to deliver mail to for domain: ${this.todo.domain}`))
232
+ }
248
233
  this.bounce(`Nowhere to deliver mail to for domain: ${this.todo.domain}`);
249
234
  }
250
- else {
251
- // every other error is transient
252
- this.todo.rcpt_to.forEach(rcpt => {
253
- this.extend_rcpt_with_dsn(rcpt, DSN.addr_unspecified(`DNS lookup failure: ${this.todo.domain}`));
254
- });
255
- this.temp_fail(`DNS lookup failure: ${err}`);
235
+ } catch (e) {
236
+ this.get_mx_error(e);
237
+ }
238
+ }
239
+
240
+ get_mx_error (err) {
241
+ this.lognotice(`MX Lookup for ${this.todo.domain} failed: ${err}`);
242
+
243
+ if (err.code === dns.NXDOMAIN || err.code === dns.NOTFOUND) {
244
+ for (const rcpt of this.todo.rcpt_to) {
245
+ this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No Such Domain: ${this.todo.domain}`));
256
246
  }
247
+ this.bounce(`No Such Domain: ${this.todo.domain}`);
257
248
  }
258
249
  else {
259
- // got MXs
260
- const mxlist = sort_mx(mxs);
261
- // support draft-delany-nullmx-02
262
- if (mxlist.length === 1 && mxlist[0].priority === 0 && mxlist[0].exchange === '') {
263
- this.todo.rcpt_to.forEach(rcpt => {
264
- this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`));
265
- });
266
- return this.bounce(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`);
267
- }
268
- // duplicate each MX for each ip address family
269
- this.mxlist = [];
270
- for (const mx in mxlist) {
271
- // Handle UNIX sockets for LMTP
272
- if (mxlist[mx].path) {
273
- this.mxlist.push(mxlist[mx]);
274
- }
275
- else if (obc.cfg.ipv6_enabled) {
276
- this.mxlist.push(
277
- { exchange: mxlist[mx].exchange, priority: mxlist[mx].priority, port: mxlist[mx].port, using_lmtp: mxlist[mx].using_lmtp, family: 'AAAA' },
278
- { exchange: mxlist[mx].exchange, priority: mxlist[mx].priority, port: mxlist[mx].port, using_lmtp: mxlist[mx].using_lmtp, family: 'A' }
279
- );
280
- }
281
- else {
282
- mxlist[mx].family = 'A';
283
- this.mxlist.push(mxlist[mx]);
284
- }
250
+ // every other error is transient
251
+ for (const rcpt of this.todo.rcpt_to) {
252
+ this.extend_rcpt_with_dsn(rcpt, DSN.addr_unspecified(`DNS lookup failure: ${this.todo.domain}`));
285
253
  }
286
- this.try_deliver();
254
+ this.temp_fail(`DNS lookup failure: ${err}`);
287
255
  }
288
256
  }
289
257
 
290
- try_deliver () {
258
+ async found_mx (mxs) {
291
259
 
292
- // check if there are any MXs left
293
- if (this.mxlist.length === 0) {
294
- this.todo.rcpt_to.forEach(rcpt => {
295
- this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Tried all MXs ${this.todo.domain}`));
296
- });
297
- return this.temp_fail("Tried all MXs");
260
+ // support draft-delany-nullmx-02
261
+ if (mxs.length === 1 && mxs[0].priority === 0 && mxs[0].exchange === '') {
262
+ for (const rcpt of this.todo.rcpt_to) {
263
+ this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`));
264
+ }
265
+ return this.bounce(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`);
298
266
  }
299
267
 
300
- const mx = this.mxlist.shift();
301
- const host = mx.exchange;
268
+ // resolves the MX hostnames to IPs
269
+ this.mxlist = await net_utils.resolve_mx_hosts(mxs);
302
270
 
303
- this.force_tls = this.todo.force_tls;
304
- if (!this.force_tls) {
305
- if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, host)) {
306
- this.logdebug(`Forcing TLS for host ${host}`);
307
- this.force_tls = true;
308
- }
309
- if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, this.todo.domain)) {
310
- this.logdebug(`Forcing TLS for domain ${this.todo.domain}`);
311
- this.force_tls = true;
312
- }
271
+ this.try_deliver();
272
+ }
313
273
 
314
- // IP or IP:port
315
- if (net.isIP(host)) {
316
- this.hostlist = [ host ];
317
- return this.try_deliver_host(mx);
318
- }
319
- }
274
+ async try_deliver () {
320
275
 
321
- // we have a host, look up the addresses for the host
322
- // and try each in order they appear
323
- dns.resolve(host, mx.family, (err, addresses) => {
324
- if (err) {
325
- this.lognotice(`DNS (${mx.family}) for ${host} failed: ${err}`);
326
- return this.try_deliver(); // try next MX
327
- }
328
- if (addresses.length === 0) {
329
- // NODATA or empty host list
330
- this.lognotice(`DNS (${mx.family}) for ${host} resulted in no data`);
331
- return this.try_deliver(); // try next MX
276
+ // are any MXs left?
277
+ if (this.mxlist.length === 0) {
278
+ for (const rcpt of this.todo.rcpt_to) {
279
+ this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Tried all MXs ${this.todo.domain}`));
332
280
  }
333
- this.logdebug(`DNS (${mx.family}) for ${host} -> ${addresses.join(',')}`);
334
- this.hostlist = addresses;
335
- this.try_deliver_host(mx);
336
- });
337
- }
281
+ return this.temp_fail("Tried all MXs");
282
+ }
338
283
 
339
- try_deliver_host (mx) {
284
+ const mx = this.mxlist.shift();
340
285
 
341
- if (this.hostlist.length === 0) {
286
+ if (!obc.cfg.local_mx_ok && mx.from_dns && await net_utils.is_local_host(mx.exchange)) {
287
+ this.loginfo(`MX ${mx.exchange} is local, skipping since local_mx_ok=false`)
342
288
  return this.try_deliver(); // try next MX
343
289
  }
344
290
 
345
- // Allow transaction notes to set outbound IP
346
- if (!mx.bind && this.todo.notes.outbound_ip) {
347
- mx.bind = this.todo.notes.outbound_ip;
291
+ this.force_tls = this.get_force_tls(mx)
292
+
293
+ if (this.todo.notes.outbound_ip) {
294
+ this.logerror(`notes.outbound_ip is deprecated. Use get_mx.bind instead!`);
295
+ if (!mx.bind) mx.bind = this.todo.notes.outbound_ip;
348
296
  }
349
297
 
350
298
  // Allow transaction notes to set outbound IP helo
351
- if (!mx.bind_helo){
352
- if (this.todo.notes.outbound_helo) {
353
- mx.bind_helo = this.todo.notes.outbound_helo;
354
- }
355
- else {
356
- mx.bind_helo = net_utils.get_primary_host_name();
357
- }
299
+ if (this.todo.notes.outbound_helo) {
300
+ mx.bind_helo = this.todo.notes.outbound_helo;
358
301
  }
359
302
 
360
- let host = this.hostlist.shift();
361
- const port = mx.port || 25;
303
+ const host = mx.path ? mx.path : mx.exchange;
304
+ const lmtp = mx.using_lmtp ? ' using LMTP' : ''
305
+ if (!mx.port) mx.port = mx.using_lmtp ? 24 : 25
306
+ const from_dns = mx.from_dns ? ' (via DNS)' : ''
362
307
 
363
- if (mx.path) {
364
- host = mx.path;
365
- }
366
-
367
- this.logdebug(`delivering from: ${mx.bind_helo} to: ${host}:${port}${mx.using_lmtp ? " using LMTP" : ""} (${delivery_queue.length()}) (${temp_fail_queue.length()})`)
368
- client_pool.get_client(port, host, mx.bind, !!mx.path, (err, socket) => {
308
+ this.logdebug(`deliver: ${mx.bind_helo} -> ${host}${lmtp}${from_dns} (${delivery_queue.length()}) (${temp_fail_queue.length()})`)
309
+ client_pool.get_client(mx, (err, socket) => {
369
310
  if (err) {
370
311
  if (/connection timed out|connect ECONNREFUSED/.test(err)) {
371
- logger.lognotice(`[outbound] Failed to get socket: ${err}`);
312
+ logger.notice(this, `Failed to get socket: ${err}`);
372
313
  }
373
314
  else {
374
- logger.logerror(`[outbound] Failed to get socket: ${err}`);
315
+ logger.error(this, `Failed to get socket: ${err}`);
375
316
  }
376
- // try next host
377
- return this.try_deliver_host(mx);
317
+
318
+ return this.try_deliver(); // try next MX
378
319
  }
379
- this.try_deliver_host_on_socket(mx, host, port, socket);
320
+ this.try_deliver_host_on_socket(mx, host, mx.port, socket);
380
321
  });
381
322
  }
382
323
 
@@ -385,10 +326,9 @@ class HMailItem extends events.EventEmitter {
385
326
  let processing_mail = true;
386
327
  let command = mx.using_lmtp ? 'connect_lmtp' : 'connect';
387
328
 
388
- socket.removeAllListeners('error');
389
- socket.removeAllListeners('timeout');
390
- socket.removeAllListeners('close');
391
- socket.removeAllListeners('end');
329
+ for (const l of ['error', 'timeout', 'close', 'end']) {
330
+ socket.removeAllListeners(l);
331
+ }
392
332
 
393
333
  socket.once('timeout', function () {
394
334
  socket.emit('error', `socket timeout waiting on ${command}`);
@@ -399,11 +339,10 @@ class HMailItem extends events.EventEmitter {
399
339
 
400
340
  self.logerror(`Ongoing connection failed to ${host}:${port} : ${err}`);
401
341
  processing_mail = false;
402
- client_pool.release_client(socket, port, host, mx.bind, true);
342
+ client_pool.release_client(socket, mx);
403
343
  if (err.source === 'tls') // exception thrown from tls_socket during tls upgrade
404
- return obtls.mark_tls_nogo(host, () => { return self.try_deliver_host(mx); });
405
- // try the next MX
406
- self.try_deliver_host(mx);
344
+ return obtls.mark_tls_nogo(host, () => { return self.try_deliver(); });
345
+ self.try_deliver(); // try the next MX
407
346
  })
408
347
 
409
348
  socket.once('close', () => {
@@ -411,18 +350,14 @@ class HMailItem extends events.EventEmitter {
411
350
 
412
351
  self.logerror(`Remote end ${host}:${port} closed connection while we were processing mail. Trying next MX.`);
413
352
  processing_mail = false;
414
- client_pool.release_client(socket, port, host, mx.bind, true);
415
- self.try_deliver_host(mx);
353
+ client_pool.release_client(socket, mx);
354
+ self.try_deliver();
416
355
  });
417
356
 
418
- let fin_sent = false;
419
357
  socket.once('end', () => {
420
- fin_sent = true;
421
358
  socket.writable = false;
422
- if (!processing_mail) {
423
- client_pool.release_client(socket, port, host, mx.bind, true);
424
- }
425
- });
359
+ if (!processing_mail) client_pool.release_client(socket, mx);
360
+ })
426
361
 
427
362
  let response = [];
428
363
 
@@ -450,16 +385,16 @@ class HMailItem extends events.EventEmitter {
450
385
  self.logerror("Socket writability went away");
451
386
  if (processing_mail) {
452
387
  processing_mail = false;
453
- client_pool.release_client(socket, port, host, mx.bind, true);
454
- return self.try_deliver_host(mx);
388
+ client_pool.release_client(socket, mx);
389
+ return self.try_deliver();
455
390
  }
456
391
  return;
457
392
  }
458
- if (self.force_tls && (cmd != 'EHLO' && cmd != 'STARTTLS') && !socket.isSecure()) {
393
+ if (self.force_tls && !['EHLO', 'LHLO', 'STARTTLS'].includes(cmd.toUpperCase()) && !socket.isSecure()) {
459
394
  // For safety against programming mistakes
460
395
  self.logerror("Blocking attempt to send unencrypted data to forced TLS socket. This message indicates a programming error in the software.");
461
396
  processing_mail = false;
462
- client_pool.release_client(socket, port, host, mx.bind, true);
397
+ client_pool.release_client(socket, mx);
463
398
  return;
464
399
  }
465
400
 
@@ -475,7 +410,7 @@ class HMailItem extends events.EventEmitter {
475
410
  // We may want to release client here - but I want to get this
476
411
  // line of code in before we do that so we might see some logging
477
412
  // in case of errors.
478
- // client_pool.release_client(socket, port, host, mx.bind, fin_sent);
413
+ // client_pool.release_client(socket, mx);
479
414
  }
480
415
  });
481
416
  command = cmd.toLowerCase();
@@ -512,14 +447,26 @@ class HMailItem extends events.EventEmitter {
512
447
  }
513
448
  }
514
449
 
450
+ function get_reverse_path_with_params () {
451
+ const rp = self.todo.mail_from.format(!smtp_properties.smtp_utf8)
452
+ let rp_params = ''
453
+ if (smtp_properties.smtp_utf8 && has_non_ascii(rp)) rp_params += ' SMTPUTF8'
454
+ return `FROM:${rp}${rp_params}`
455
+ }
456
+
457
+ function has_non_ascii (string) {
458
+ return [...string].some(char => char.charCodeAt(0) > 127)
459
+ }
460
+
515
461
  function auth_and_mail_phase () {
516
462
  if (!authenticated && (mx.auth_user && mx.auth_pass)) {
517
463
  // We have AUTH credentials to send for this domain
464
+
518
465
  if (!(Array.isArray(smtp_properties.auth) && smtp_properties.auth.length)) {
519
466
  // AUTH not offered
520
467
  self.logwarn(`AUTH configured for domain ${self.todo.domain} but host ${host} did not advertise AUTH capability`);
521
468
  // Try and send the message without authentication
522
- return send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
469
+ return send_command('MAIL', get_reverse_path_with_params());
523
470
  }
524
471
 
525
472
  if (!mx.auth_type) {
@@ -543,7 +490,7 @@ class HMailItem extends events.EventEmitter {
543
490
  // No compatible authentication types offered by the server
544
491
  self.logwarn(`AUTH configured for domain ${self.todo.domain} but host ${host}did not offer any compatible types${(mx.auth_type) ? ` (requested: ${mx.auth_type})` : ''} (offered: ${smtp_properties.auth.join(',')})`);
545
492
  // Proceed without authentication
546
- return send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
493
+ return send_command('MAIL', get_reverse_path_with_params());
547
494
  }
548
495
 
549
496
  switch (mx.auth_type.toUpperCase()) {
@@ -558,12 +505,12 @@ class HMailItem extends events.EventEmitter {
558
505
  default:
559
506
  // Unsupported AUTH type
560
507
  self.logwarn(`Unsupported authentication type ${mx.auth_type.toUpperCase()} requested for domain ${self.todo.domain}`);
561
- return send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
508
+ return send_command('MAIL', get_reverse_path_with_params());
562
509
  }
563
510
  }
564
511
 
565
- return send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
566
- } // auth_and_mail_phase()
512
+ return send_command('MAIL', get_reverse_path_with_params());
513
+ }
567
514
 
568
515
  // IMPORTANT: do STARTTLS before AUTH for security
569
516
  function process_ehlo_data () {
@@ -579,7 +526,7 @@ class HMailItem extends events.EventEmitter {
579
526
  processing_mail = false;
580
527
  socket.write("QUIT\r\n", "utf8"); // courtesy
581
528
  socket.end();
582
- client_pool.release_client(socket, port, host, mx.bind, true);
529
+ client_pool.release_client(socket, mx);
583
530
  return self.temp_fail(`No TLS available but required by configuration.`);
584
531
  }
585
532
 
@@ -591,7 +538,7 @@ class HMailItem extends events.EventEmitter {
591
538
  });
592
539
  return send_command('STARTTLS');
593
540
  }
594
- if (!obc.cfg.enable_tls) return auth_and_mail_phase(); // TLS not enabled
541
+ if (!obc.cfg.enable_tls) return auth_and_mail_phase(); // TLS not enabled
595
542
  if (!smtp_properties.tls) return auth_and_mail_phase(); // TLS not advertised by remote
596
543
 
597
544
  if (obtls.cfg === undefined) {
@@ -623,7 +570,7 @@ class HMailItem extends events.EventEmitter {
623
570
  return auth_and_mail_phase();
624
571
  }
625
572
  );
626
- } // process_ehlo_data()
573
+ }
627
574
 
628
575
  let fp_called = false;
629
576
 
@@ -673,7 +620,7 @@ class HMailItem extends events.EventEmitter {
673
620
  self.logerror(`Unrecognized response from upstream server: ${line}`);
674
621
  processing_mail = false;
675
622
  // Release back to the pool and instruct it to terminate this connection
676
- client_pool.release_client(socket, port, host, mx.bind, true);
623
+ client_pool.release_client(socket, mx);
677
624
  self.todo.rcpt_to.forEach(rcpt => {
678
625
  self.extend_rcpt_with_dsn(rcpt, DSN.proto_invalid_command(`Unrecognized response from upstream server: ${line}`));
679
626
  });
@@ -710,7 +657,7 @@ class HMailItem extends events.EventEmitter {
710
657
  // The response is our challenge
711
658
  return send_command(cram_md5_response(mx.auth_user, mx.auth_pass, resp));
712
659
  default:
713
- // This shouldn't happen...
660
+ // This shouldn't happen...
714
661
  }
715
662
  }
716
663
  // Error
@@ -768,8 +715,7 @@ class HMailItem extends events.EventEmitter {
768
715
  else {
769
716
  reason = response.join(' ');
770
717
  self.lognotice(`Error - but not processing mail: ${code} ${((extc) ? `${extc} ` : '')}${reason}`);
771
- // Release back to the pool and instruct it to terminate this connection
772
- return client_pool.release_client(socket, port, host, mx.bind, true);
718
+ return client_pool.release_client(socket, mx);
773
719
  }
774
720
  }
775
721
  else if (code.match(/^5/)) {
@@ -780,7 +726,7 @@ class HMailItem extends events.EventEmitter {
780
726
  }
781
727
  if (command === 'rset') {
782
728
  // Broken server doesn't accept RSET, terminate the connection
783
- return client_pool.release_client(socket, port, host, mx.bind, true);
729
+ return client_pool.release_client(socket, mx);
784
730
  }
785
731
  reason = `${code} ${(extc) ? `${extc} ` : ''}${response.join(' ')}`;
786
732
  if (/^rcpt/.test(command) || command === 'dot_lmtp') {
@@ -856,7 +802,7 @@ class HMailItem extends events.EventEmitter {
856
802
  processing_mail = false;
857
803
  socket.end();
858
804
  self.temp_fail('Host failed TLS verification required by configuration.');
859
- client_pool.release_client(socket, port, host, mx.bind, true);
805
+ client_pool.release_client(socket, mx);
860
806
  }
861
807
  });
862
808
  break;
@@ -864,10 +810,10 @@ class HMailItem extends events.EventEmitter {
864
810
  case 'auth':
865
811
  authenticating = false;
866
812
  authenticated = true;
867
- send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
813
+ send_command('MAIL', get_reverse_path_with_params());
868
814
  break;
869
815
  case 'helo':
870
- send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
816
+ send_command('MAIL', get_reverse_path_with_params());
871
817
  break;
872
818
  case 'mail':
873
819
  last_recip = recipients[recip_index];
@@ -917,7 +863,7 @@ class HMailItem extends events.EventEmitter {
917
863
  break;
918
864
  case 'quit':
919
865
  case 'rset':
920
- client_pool.release_client(socket, port, host, mx.bind, fin_sent);
866
+ client_pool.release_client(socket, mx);
921
867
  break;
922
868
  default:
923
869
  // should never get here - means we did something
@@ -927,10 +873,10 @@ class HMailItem extends events.EventEmitter {
927
873
  });
928
874
 
929
875
  if (socket.__fromPool) {
930
- logger.logdebug('[outbound] got socket, trying to deliver');
876
+ logger.debug(this, 'got socket, trying to deliver');
931
877
  secured = socket.isEncrypted();
932
- logger.logdebug(`[outbound] got ${secured ? 'TLS ' : '' }socket, trying to deliver`);
933
- send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
878
+ logger.debug(this, `got ${secured ? 'TLS ' : '' }socket, trying to deliver`);
879
+ send_command('MAIL', get_reverse_path_with_params());
934
880
  }
935
881
  }
936
882
 
@@ -1062,7 +1008,7 @@ class HMailItem extends events.EventEmitter {
1062
1008
  "\r": '#10',
1063
1009
  "\n": '#13'
1064
1010
  };
1065
- const escape_pattern = new RegExp(`[${Object.keys(escaped_chars).join()}]`, 'g');
1011
+ const escape_pattern = new RegExp(`[${Object.keys(escaped_chars).join('')}]`, 'g');
1066
1012
 
1067
1013
  bounce_msg_html_.forEach(line => {
1068
1014
  line = line.replace(/\{(\w+)\}/g, (i, word) => {
@@ -1334,7 +1280,7 @@ class HMailItem extends events.EventEmitter {
1334
1280
  }
1335
1281
 
1336
1282
  temp_fail (err, extra) {
1337
- logger.logdebug(`Temp fail for: ${err}`);
1283
+ logger.debug(this, `Temp fail for: ${err}`);
1338
1284
  this.num_failures++;
1339
1285
 
1340
1286
  // Test for max failures which is configurable.
@@ -1379,7 +1325,7 @@ class HMailItem extends events.EventEmitter {
1379
1325
  });
1380
1326
  }
1381
1327
 
1382
- // The following handler has an impact on outgoing mail. It does remove the queue file.
1328
+ // The following handler impacts outgoing mail. It removes the queue file.
1383
1329
  delivered_respond (retval, msg) {
1384
1330
  if (retval !== constants.cont && retval !== constants.ok) {
1385
1331
  this.logwarn(
@@ -1390,6 +1336,51 @@ class HMailItem extends events.EventEmitter {
1390
1336
  this.discard();
1391
1337
  }
1392
1338
 
1339
+ get_force_tls (mx) {
1340
+ if (!mx.exchange) return false
1341
+ if (!obtls.cfg.force_tls_hosts) return false
1342
+
1343
+ if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, mx.exchange)) {
1344
+ this.logdebug(`Forcing TLS for host ${mx.exchange}`);
1345
+ return true;
1346
+ }
1347
+
1348
+ if (mx.from_dns) {
1349
+ // the MX was looked up in DNS and already resolved to IP(s).
1350
+ // This checks the hostname.
1351
+ if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, mx.from_dns)) {
1352
+ this.logdebug(`Forcing TLS for host ${mx.from_dns}`);
1353
+ return true;
1354
+ }
1355
+ }
1356
+
1357
+ if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, this.todo.domain)) {
1358
+ this.logdebug(`Forcing TLS for domain ${this.todo.domain}`);
1359
+ return true;
1360
+ }
1361
+
1362
+ return false
1363
+ }
1364
+
1365
+ sort_mx (mx_list) {
1366
+ // MXs must be sorted by priority.
1367
+ const sorted = mx_list.sort((a,b) => a.priority - b.priority);
1368
+
1369
+ // Matched priorities must be randomly shuffled.
1370
+ // This isn't a very good shuffle but it'll do for now.
1371
+ for (let i=0,l=sorted.length-1; i<l; i++) {
1372
+ if (sorted[i].priority === sorted[i+1].priority) {
1373
+ if (Math.round(Math.random())) { // 0 or 1
1374
+ const j = sorted[i];
1375
+ sorted[i] = sorted[i+1];
1376
+ sorted[i+1] = j;
1377
+ }
1378
+ }
1379
+ }
1380
+
1381
+ return sorted;
1382
+ }
1383
+
1393
1384
  split_to_new_recipients (recipients, response, cb) {
1394
1385
  const hmail = this;
1395
1386
  if (recipients.length === hmail.todo.rcpt_to.length) {
@@ -1401,7 +1392,7 @@ class HMailItem extends events.EventEmitter {
1401
1392
  const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`);
1402
1393
  const ws = new FsyncWriteStream(tmp_path, { flags: constants.WRITE_EXCL });
1403
1394
  function err_handler (err, location) {
1404
- logger.logerror(`[outbound] Error while splitting to new recipients (${location}): ${err}`);
1395
+ logger.error(this, `Error while splitting to new recipients (${location}): ${err}`);
1405
1396
  hmail.todo.rcpt_to.forEach(rcpt => {
1406
1397
  hmail.extend_rcpt_with_dsn(rcpt, DSN.sys_unspecified(`Error splitting to new recipients: ${err}`));
1407
1398
  });
@@ -1439,7 +1430,7 @@ class HMailItem extends events.EventEmitter {
1439
1430
  }
1440
1431
 
1441
1432
  ws.on('error', err => {
1442
- logger.logerror(`[outbound] Unable to write queue file (${fname}): ${err}`);
1433
+ logger.error(this, `Unable to write queue file (${fname}): ${err}`);
1443
1434
  ws.destroy();
1444
1435
  hmail.todo.rcpt_to.forEach(rcpt => {
1445
1436
  hmail.extend_rcpt_with_dsn(rcpt, DSN.sys_unspecified(`Error re-queueing some recipients: ${err}`));
@@ -1456,38 +1447,7 @@ class HMailItem extends events.EventEmitter {
1456
1447
  module.exports = HMailItem;
1457
1448
  module.exports.obtls = obtls;
1458
1449
 
1459
- // copy logger methods into HMailItem:
1460
- for (const key in logger) {
1461
- if (!/^log\w/.test(key)) continue;
1462
- HMailItem.prototype[key] = (function (level) {
1463
- return function () {
1464
- // pass the HMailItem instance to logger
1465
- const args = [ this ];
1466
- for (let i=0, l=arguments.length; i<l; i++) {
1467
- args.push(arguments[i]);
1468
- }
1469
- logger[level].apply(logger, args);
1470
- };
1471
- })(key);
1472
- }
1473
-
1474
- // MXs must be sorted by priority order, but matched priorities must be
1475
- // randomly shuffled in that list, so this is a bit complex.
1476
- function sort_mx (mx_list) {
1477
- const sorted = mx_list.sort((a,b) => a.priority - b.priority);
1478
-
1479
- // This isn't a very good shuffle but it'll do for now.
1480
- for (let i=0,l=sorted.length-1; i<l; i++) {
1481
- if (sorted[i].priority === sorted[i+1].priority) {
1482
- if (Math.round(Math.random())) { // 0 or 1
1483
- const j = sorted[i];
1484
- sorted[i] = sorted[i+1];
1485
- sorted[i+1] = j;
1486
- }
1487
- }
1488
- }
1489
- return sorted;
1490
- }
1450
+ logger.add_log_methods(HMailItem)
1491
1451
 
1492
1452
  const smtp_regexp = /^([2345]\d\d)([ -])#?(?:(\d\.\d\.\d)\s)?(.*)/;
1493
1453