Haraka 3.1.6 → 3.2.0

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 (69) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/CONTRIBUTORS.md +8 -8
  3. package/Plugins.md +99 -99
  4. package/address.js +53 -0
  5. package/bin/haraka +1 -1
  6. package/config/smtp_forward.ini +10 -0
  7. package/config/smtp_proxy.ini +10 -0
  8. package/connection.js +28 -11
  9. package/docs/Outbound.md +1 -1
  10. package/docs/Transaction.md +1 -1
  11. package/docs/plugins/queue/smtp_forward.md +19 -3
  12. package/docs/plugins/queue/smtp_proxy.md +10 -2
  13. package/docs/plugins/status.md +21 -5
  14. package/haraka.js +1 -1
  15. package/outbound/hmail.js +41 -41
  16. package/outbound/index.js +5 -5
  17. package/outbound/queue.js +1 -1
  18. package/outbound/tls.js +2 -43
  19. package/package.json +48 -48
  20. package/plugins/auth/auth_base.js +9 -3
  21. package/plugins/auth/auth_proxy.js +14 -11
  22. package/plugins/block_me.js +6 -4
  23. package/plugins/prevent_credential_leaks.js +3 -1
  24. package/plugins/process_title.js +6 -6
  25. package/plugins/queue/qmail-queue.js +15 -19
  26. package/plugins/queue/smtp_forward.js +14 -6
  27. package/plugins/queue/smtp_proxy.js +14 -3
  28. package/plugins/rcpt_to.host_list_base.js +1 -1
  29. package/plugins/record_envelope_addresses.js +2 -2
  30. package/plugins/status.js +34 -5
  31. package/plugins/tls.js +13 -5
  32. package/plugins/xclient.js +3 -1
  33. package/server.js +5 -3
  34. package/smtp_client.js +20 -11
  35. package/test/config/block_me.recipient +1 -0
  36. package/test/config/block_me.senders +1 -0
  37. package/test/connection.js +25 -1
  38. package/test/fixtures/util_hmailitem.js +1 -1
  39. package/test/outbound/bounce_net_errors.js +3 -2
  40. package/test/outbound/index.js +2 -2
  41. package/test/plugins/auth/auth_base.js +1 -1
  42. package/test/plugins/auth/auth_bridge.js +80 -0
  43. package/test/plugins/auth/flat_file.js +128 -0
  44. package/test/plugins/block_me.js +157 -0
  45. package/test/plugins/data.signatures.js +114 -0
  46. package/test/plugins/delay_deny.js +263 -0
  47. package/test/plugins/prevent_credential_leaks.js +178 -0
  48. package/test/plugins/process_title.js +135 -0
  49. package/test/plugins/queue/deliver.js +99 -0
  50. package/test/plugins/queue/discard.js +79 -0
  51. package/test/plugins/queue/lmtp.js +138 -0
  52. package/test/plugins/queue/qmail-queue.js +99 -0
  53. package/test/plugins/queue/quarantine.js +81 -0
  54. package/test/plugins/queue/smtp_bridge.js +154 -0
  55. package/test/plugins/queue/smtp_forward.js +43 -7
  56. package/test/plugins/queue/smtp_proxy.js +139 -0
  57. package/test/plugins/rcpt_to.host_list_base.js +1 -1
  58. package/test/plugins/rcpt_to.in_host_list.js +1 -1
  59. package/test/plugins/record_envelope_addresses.js +2 -2
  60. package/test/plugins/reseed_rng.js +34 -0
  61. package/test/plugins/status.js +71 -0
  62. package/test/plugins/tarpit.js +91 -0
  63. package/test/plugins/tls.js +25 -0
  64. package/test/plugins/toobusy.js +21 -0
  65. package/test/plugins/xclient.js +14 -0
  66. package/test/server.js +59 -0
  67. package/test/smtp_client.js +46 -13
  68. package/test/tls_socket.js +82 -0
  69. package/tls_socket.js +50 -0
@@ -239,4 +239,86 @@ test('tls_socket', async (t) => {
239
239
  }
240
240
  }
241
241
  })
242
+
243
+ await t.test('load_plugin_tls_options', async (t) => {
244
+ // Point haraka-config at test/config so tls.ini fixtures load.
245
+ const origConfig = tls_socket.config
246
+ const origCfg = tls_socket.cfg
247
+ const test_config = require('haraka-config').module_config(path.resolve(__dirname))
248
+
249
+ t.beforeEach(() => {
250
+ tls_socket.config = test_config
251
+ tls_socket.cfg = undefined // bust load_tls_ini cache between cases
252
+ })
253
+
254
+ t.after(() => {
255
+ tls_socket.config = origConfig
256
+ tls_socket.cfg = origCfg
257
+ })
258
+
259
+ await t.test('inherits tls.ini [main] when plugin cfg is empty', () => {
260
+ const opts = tls_socket.load_plugin_tls_options({})
261
+ // From test/config/tls.ini [main]
262
+ assert.equal(opts.rejectUnauthorized, false)
263
+ assert.equal(opts.minVersion, 'TLSv1')
264
+ assert.equal(opts.honorCipherOrder, true)
265
+ assert.ok(opts.ciphers && opts.ciphers.length)
266
+ assert.ok(Buffer.isBuffer(opts.key), 'key resolved to Buffer')
267
+ assert.ok(Buffer.isBuffer(opts.cert), 'cert resolved to Buffer')
268
+ })
269
+
270
+ await t.test('plugin cfg overrides [main]', () => {
271
+ const opts = tls_socket.load_plugin_tls_options({
272
+ rejectUnauthorized: true,
273
+ minVersion: 'TLSv1.3',
274
+ ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
275
+ })
276
+ assert.equal(opts.rejectUnauthorized, true)
277
+ assert.equal(opts.minVersion, 'TLSv1.3')
278
+ assert.equal(opts.ciphers, 'ECDHE-RSA-AES256-GCM-SHA384')
279
+ })
280
+
281
+ await t.test('resolves key/cert/dhparam file refs to Buffers', () => {
282
+ const opts = tls_socket.load_plugin_tls_options({
283
+ key: 'outbound_tls_key.pem',
284
+ cert: 'outbound_tls_cert.pem',
285
+ dhparam: 'dhparams.pem',
286
+ })
287
+ assert.ok(Buffer.isBuffer(opts.key) && opts.key.length > 0)
288
+ assert.ok(Buffer.isBuffer(opts.cert) && opts.cert.length > 0)
289
+ assert.ok(Buffer.isBuffer(opts.dhparam) && opts.dhparam.length > 0)
290
+ })
291
+
292
+ await t.test('drops missing dhparam rather than leaving null', () => {
293
+ const opts = tls_socket.load_plugin_tls_options({
294
+ dhparam: 'does_not_exist.pem',
295
+ })
296
+ assert.equal(opts.dhparam, undefined)
297
+ })
298
+
299
+ await t.test('normalises no_tls_hosts / force_tls_hosts to arrays', () => {
300
+ const opts = tls_socket.load_plugin_tls_options({
301
+ no_tls_hosts: '10.0.0.5',
302
+ force_tls_hosts: ['a.example.com', 'b.example.com'],
303
+ })
304
+ assert.deepEqual(opts.no_tls_hosts, ['10.0.0.5'])
305
+ assert.deepEqual(opts.force_tls_hosts, ['a.example.com', 'b.example.com'])
306
+
307
+ const opts2 = tls_socket.load_plugin_tls_options({})
308
+ assert.deepEqual(opts2.no_tls_hosts, [])
309
+ assert.deepEqual(opts2.force_tls_hosts, [])
310
+ })
311
+
312
+ await t.test('does not set servername', () => {
313
+ const opts = tls_socket.load_plugin_tls_options({})
314
+ assert.equal(opts.servername, undefined)
315
+ })
316
+
317
+ await t.test('does not mutate the input plugin cfg', () => {
318
+ const input = { rejectUnauthorized: true, no_tls_hosts: '10.0.0.5' }
319
+ const before = JSON.stringify(input)
320
+ tls_socket.load_plugin_tls_options(input)
321
+ assert.equal(JSON.stringify(input), before)
322
+ })
323
+ })
242
324
  })
package/tls_socket.js CHANGED
@@ -251,6 +251,56 @@ exports.load_tls_ini = (opts) => {
251
251
  return cfg
252
252
  }
253
253
 
254
+ // Build a client tls_options, merges a consumers own [tls] section
255
+ // over tls.ini [main].
256
+ exports.load_plugin_tls_options = (plugin_tls_cfg = {}) => {
257
+ const tls_cfg = exports.load_tls_ini({ role: 'client' })
258
+ const cfg = JSON.parse(JSON.stringify(plugin_tls_cfg))
259
+
260
+ // Inheritance from tls.ini [main] deliberately omits no_tls_hosts: the
261
+ // [main].no_tls_hosts list is documented as inbound-only; outbound and
262
+ // queue plugins should opt in explicitly via their own section.
263
+ const inheritable_opts = [
264
+ 'key',
265
+ 'cert',
266
+ 'ciphers',
267
+ 'minVersion',
268
+ 'dhparam',
269
+ 'requestCert',
270
+ 'honorCipherOrder',
271
+ 'rejectUnauthorized',
272
+ 'force_tls_hosts',
273
+ ]
274
+ for (const opt of inheritable_opts) {
275
+ if (cfg[opt] !== undefined) continue // set in plugin [tls]
276
+ if (tls_cfg.main[opt] === undefined) continue // unset in tls.ini [main]
277
+ cfg[opt] = tls_cfg.main[opt]
278
+ }
279
+
280
+ // Resolve key/cert/dhparam file references to buffers. Drop empty results
281
+ // so we never pass null to tls.connect.
282
+ for (const k of ['key', 'cert', 'dhparam']) {
283
+ if (!cfg[k]) {
284
+ delete cfg[k]
285
+ continue
286
+ }
287
+ const ref = Array.isArray(cfg[k]) ? cfg[k][0] : cfg[k]
288
+ const bin = exports.config.get(ref, 'binary')
289
+ if (bin) cfg[k] = bin
290
+ else delete cfg[k]
291
+ }
292
+
293
+ for (const k of ['no_tls_hosts', 'force_tls_hosts']) {
294
+ if (!cfg[k]) {
295
+ cfg[k] = []
296
+ continue
297
+ }
298
+ if (!Array.isArray(cfg[k])) cfg[k] = [cfg[k]]
299
+ }
300
+
301
+ return cfg
302
+ }
303
+
254
304
  exports.applySocketOpts = (name) => {
255
305
  // https://nodejs.org/api/tls.html#tls_new_tls_tlssocket_socket_options
256
306
  const TLSSocketOptions = [