Haraka 3.2.1 → 3.3.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.
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +1 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +29 -5
- package/CONTRIBUTORS.md +5 -5
- package/README.md +6 -3
- package/bin/haraka +12 -4
- package/config/connection.ini +6 -0
- package/connection.js +67 -68
- package/contrib/bsd-rc.d/haraka +2 -0
- package/docs/CoreConfig.md +2 -0
- package/docs/HAProxy.md +4 -1
- package/eslint.config.mjs +2 -30
- package/haraka.js +2 -2
- package/line_socket.js +6 -33
- package/outbound/hmail.js +18 -29
- package/outbound/index.js +3 -3
- package/outbound/queue.js +8 -5
- package/package.json +49 -46
- package/plugins/auth/auth_proxy.js +7 -4
- package/plugins/block_me.js +1 -1
- package/plugins/delay_deny.js +1 -1
- package/plugins/queue/qmail-queue.js +1 -1
- package/plugins/queue/quarantine.js +5 -5
- package/plugins/queue/smtp_bridge.js +1 -1
- package/plugins/queue/smtp_proxy.js +2 -2
- package/plugins/status.js +2 -2
- package/plugins/toobusy.js +1 -1
- package/plugins.js +4 -3
- package/server.js +172 -28
- package/smtp_client.js +2 -1
- package/test/connection.js +119 -2
- package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
- package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
- package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
- package/test/fixtures/line_socket.js +1 -1
- package/test/fixtures/util_hmailitem.js +2 -3
- package/test/outbound/index.js +6 -7
- package/test/outbound/qfile.js +1 -1
- package/test/outbound/queue.js +2 -2
- package/test/plugins/auth/auth_base.js +17 -17
- package/test/plugins/auth/auth_bridge.js +3 -3
- package/test/plugins/auth/auth_vpopmaild.js +3 -3
- package/test/plugins/auth/flat_file.js +16 -21
- package/test/plugins/block_me.js +7 -23
- package/test/plugins/data.signatures.js +17 -20
- package/test/plugins/delay_deny.js +3 -4
- package/test/plugins/prevent_credential_leaks.js +17 -21
- package/test/plugins/process_title.js +12 -6
- package/test/plugins/queue/deliver.js +7 -8
- package/test/plugins/queue/discard.js +3 -4
- package/test/plugins/queue/lmtp.js +5 -6
- package/test/plugins/queue/qmail-queue.js +7 -8
- package/test/plugins/queue/quarantine.js +3 -4
- package/test/plugins/queue/smtp_bridge.js +5 -7
- package/test/plugins/queue/smtp_forward.js +49 -60
- package/test/plugins/queue/smtp_proxy.js +6 -7
- package/test/plugins/rcpt_to.host_list_base.js +6 -9
- package/test/plugins/rcpt_to.in_host_list.js +6 -11
- package/test/plugins/record_envelope_addresses.js +33 -60
- package/test/plugins/reseed_rng.js +3 -3
- package/test/plugins/status.js +4 -5
- package/test/plugins/tarpit.js +3 -4
- package/test/plugins/tls.js +3 -5
- package/test/plugins/toobusy.js +186 -9
- package/test/plugins/xclient.js +7 -4
- package/test/server.js +425 -1
- package/test/smtp_client.js +11 -18
- package/test/tls_socket.js +3 -6
- package/tls_socket.js +3 -3
- package/transaction.js +3 -3
- package/address.js +0 -53
- package/endpoint.js +0 -96
- package/host_pool.js +0 -169
- package/outbound/fsync_writestream.js +0 -44
- package/outbound/timer_queue.js +0 -86
- package/rfc1869.js +0 -93
- package/test/endpoint.js +0 -128
- package/test/host_pool.js +0 -188
- package/test/rfc1869.js +0 -89
package/server.js
CHANGED
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
const cluster = require('node:cluster')
|
|
5
5
|
const { spawn } = require('node:child_process')
|
|
6
6
|
const fs = require('node:fs')
|
|
7
|
+
const net = require('node:net')
|
|
7
8
|
const os = require('node:os')
|
|
8
9
|
const path = require('node:path')
|
|
9
10
|
const tls = require('node:tls')
|
|
10
11
|
const constants = require('haraka-constants')
|
|
12
|
+
const net_utils = require('haraka-net-utils')
|
|
11
13
|
|
|
14
|
+
const { endpoint } = require('haraka-net-utils')
|
|
12
15
|
const tls_socket = require('./tls_socket')
|
|
13
16
|
const conn = require('./connection')
|
|
14
17
|
const outbound = require('./outbound')
|
|
15
|
-
const endpoint = require('./endpoint')
|
|
16
18
|
|
|
17
19
|
const Server = exports
|
|
18
20
|
Server.logger = require('./logger')
|
|
@@ -64,8 +66,16 @@ Server.load_http_ini = () => {
|
|
|
64
66
|
}).main
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
Server.load_connection_ini = () => {
|
|
70
|
+
Server.connection = {}
|
|
71
|
+
Server.connection.cfg = Server.config.get('connection.ini', {
|
|
72
|
+
booleans: ['+haproxy.enabled'],
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
Server.load_smtp_ini()
|
|
68
77
|
Server.load_http_ini()
|
|
78
|
+
Server.load_connection_ini()
|
|
69
79
|
|
|
70
80
|
Server.daemonize = function () {
|
|
71
81
|
const c = this.cfg.main
|
|
@@ -262,14 +272,7 @@ Server.receiveAsMaster = (command, params) => {
|
|
|
262
272
|
Server[command].apply(Server, params)
|
|
263
273
|
}
|
|
264
274
|
|
|
265
|
-
function messageHandler(worker, msg
|
|
266
|
-
// sunset Haraka v3 (Node < 6)
|
|
267
|
-
if (arguments.length === 2) {
|
|
268
|
-
handle = msg
|
|
269
|
-
msg = worker
|
|
270
|
-
worker = undefined
|
|
271
|
-
}
|
|
272
|
-
// console.log("received cmd: ", msg);
|
|
275
|
+
function messageHandler(worker, msg) {
|
|
273
276
|
if (msg?.cmd) {
|
|
274
277
|
Server.receiveAsMaster(msg.cmd, msg.params)
|
|
275
278
|
}
|
|
@@ -351,6 +354,142 @@ Server.load_default_tls_config = (done) => {
|
|
|
351
354
|
})
|
|
352
355
|
}
|
|
353
356
|
|
|
357
|
+
Server.create_smtps_server = (opts, onConnect) => {
|
|
358
|
+
let server
|
|
359
|
+
const socket_tls_state = new Map()
|
|
360
|
+
const proxyPrefix = Buffer.from('PROXY ')
|
|
361
|
+
// Defensive cap while waiting for a PROXY v1 line before TLS starts.
|
|
362
|
+
const proxyLineReadLimit = 512
|
|
363
|
+
|
|
364
|
+
const tlsServer = tls.createServer(opts, (cleartext) => {
|
|
365
|
+
const state_key = socket_tls_state_key(cleartext)
|
|
366
|
+
const smtps_state = socket_tls_state.get(state_key)
|
|
367
|
+
if (smtps_state) cleartext.haraka_smtps = smtps_state
|
|
368
|
+
socket_tls_state.delete(state_key)
|
|
369
|
+
|
|
370
|
+
onConnect(cleartext)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
function close_with_proxy_error(socket, timer, msg) {
|
|
374
|
+
clearTimeout(timer)
|
|
375
|
+
socket.removeAllListeners('data')
|
|
376
|
+
socket.end(`421 ${msg}\r\n`, () => {
|
|
377
|
+
socket.destroy()
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function socket_tls_state_key(socket) {
|
|
382
|
+
return JSON.stringify([socket.remoteAddress, socket.remotePort, socket.localAddress, socket.localPort])
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function start_tls(socket, proxy, peer_allowed) {
|
|
386
|
+
if (proxy || peer_allowed) {
|
|
387
|
+
const smtps_state = { peer_allowed }
|
|
388
|
+
if (proxy) {
|
|
389
|
+
smtps_state.proxy = {
|
|
390
|
+
...proxy,
|
|
391
|
+
proxy_ip: net_utils.normalize_ip(socket.remoteAddress) || socket.remoteAddress,
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
socket_tls_state.set(socket_tls_state_key(socket), smtps_state)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
tlsServer.emit('connection', socket)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
tlsServer.on('tlsClientError', (err, cleartext) => {
|
|
401
|
+
if (cleartext) socket_tls_state.delete(socket_tls_state_key(cleartext))
|
|
402
|
+
server.emit('tlsClientError', err, cleartext)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
tlsServer.on('secureConnection', (cleartext) => {
|
|
406
|
+
server.emit('secureConnection', cleartext)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
function starts_with_proxy_prefix(data) {
|
|
410
|
+
if (!data.length) return true
|
|
411
|
+
if (data.length > proxyPrefix.length) return data.subarray(0, proxyPrefix.length).equals(proxyPrefix)
|
|
412
|
+
|
|
413
|
+
return proxyPrefix.subarray(0, data.length).equals(data)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function start_tls_with_buffer(socket, data, proxy, peer_allowed) {
|
|
417
|
+
// Preserve bytes already read by the PROXY pre-parser, then hand the
|
|
418
|
+
// paused socket to TLS before letting it read again.
|
|
419
|
+
socket.pause()
|
|
420
|
+
if (data?.length) socket.unshift(data)
|
|
421
|
+
setImmediate(() => {
|
|
422
|
+
start_tls(socket, proxy, peer_allowed)
|
|
423
|
+
socket.resume()
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
server = net.createServer((socket) => {
|
|
428
|
+
const remote_ip = net_utils.normalize_ip(socket.remoteAddress) || socket.remoteAddress
|
|
429
|
+
|
|
430
|
+
if (!net_utils.is_haproxy_allowed(remote_ip)) {
|
|
431
|
+
start_tls(socket)
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let current_data = null
|
|
436
|
+
const proxy_timer = setTimeout(() => {
|
|
437
|
+
close_with_proxy_error(socket, proxy_timer, 'PROXY timeout')
|
|
438
|
+
}, 30 * 1000)
|
|
439
|
+
|
|
440
|
+
function cleanup() {
|
|
441
|
+
clearTimeout(proxy_timer)
|
|
442
|
+
// Stop flowing before removing the pre-parser listener so TLS bytes
|
|
443
|
+
// cannot arrive between listener removal and TLS attachment.
|
|
444
|
+
socket.pause()
|
|
445
|
+
socket.removeListener('data', on_data)
|
|
446
|
+
socket.removeListener('close', cleanup)
|
|
447
|
+
socket.removeListener('error', cleanup)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function on_data(data) {
|
|
451
|
+
current_data = current_data ? Buffer.concat([current_data, data]) : data
|
|
452
|
+
|
|
453
|
+
if (!starts_with_proxy_prefix(current_data)) {
|
|
454
|
+
cleanup()
|
|
455
|
+
start_tls_with_buffer(socket, current_data, null, true)
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const offset = current_data.indexOf(0x0a)
|
|
460
|
+
if (offset === -1) {
|
|
461
|
+
if (current_data.length > proxyLineReadLimit) {
|
|
462
|
+
close_with_proxy_error(socket, proxy_timer, 'Invalid PROXY format')
|
|
463
|
+
}
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
if (offset > proxyLineReadLimit) {
|
|
467
|
+
close_with_proxy_error(socket, proxy_timer, 'Invalid PROXY format')
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
cleanup()
|
|
472
|
+
|
|
473
|
+
const proxy = net_utils.parse_proxy_line(current_data.slice(0, offset + 1))
|
|
474
|
+
if (!proxy) {
|
|
475
|
+
close_with_proxy_error(socket, proxy_timer, 'Invalid PROXY format')
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const rest = current_data.slice(offset + 1)
|
|
480
|
+
start_tls_with_buffer(socket, rest, proxy, true)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
socket.once('close', cleanup)
|
|
484
|
+
socket.once('error', cleanup)
|
|
485
|
+
socket.on('data', on_data)
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
server.tlsServer = tlsServer
|
|
489
|
+
|
|
490
|
+
return server
|
|
491
|
+
}
|
|
492
|
+
|
|
354
493
|
Server.get_smtp_server = async (ep, inactivity_timeout) => {
|
|
355
494
|
let server
|
|
356
495
|
|
|
@@ -358,17 +497,19 @@ Server.get_smtp_server = async (ep, inactivity_timeout) => {
|
|
|
358
497
|
client.setTimeout(inactivity_timeout)
|
|
359
498
|
const connection = conn.createConnection(client, server, Server.cfg)
|
|
360
499
|
|
|
361
|
-
if (
|
|
500
|
+
if (server.has_tls) {
|
|
501
|
+
const cipher = client.getCipher()
|
|
502
|
+
cipher.version = client.getProtocol() // replace min with actual
|
|
362
503
|
|
|
363
|
-
|
|
364
|
-
|
|
504
|
+
connection.setTLS({
|
|
505
|
+
cipher,
|
|
506
|
+
verified: client.authorized,
|
|
507
|
+
verifyError: client.authorizationError,
|
|
508
|
+
peerCertificate: client.getPeerCertificate(),
|
|
509
|
+
})
|
|
510
|
+
}
|
|
365
511
|
|
|
366
|
-
connection.
|
|
367
|
-
cipher,
|
|
368
|
-
verified: client.authorized,
|
|
369
|
-
verifyError: client.authorizationError,
|
|
370
|
-
peerCertificate: client.getPeerCertificate(),
|
|
371
|
-
})
|
|
512
|
+
if (client.haraka_smtps?.proxy) connection.apply_proxy(client.haraka_smtps.proxy)
|
|
372
513
|
}
|
|
373
514
|
|
|
374
515
|
if (ep.port === parseInt(Server.cfg.main.smtps_port, 10)) {
|
|
@@ -382,10 +523,13 @@ Server.get_smtp_server = async (ep, inactivity_timeout) => {
|
|
|
382
523
|
tls_socket.cfg.main.requireAuthorized,
|
|
383
524
|
)
|
|
384
525
|
|
|
385
|
-
server =
|
|
386
|
-
|
|
526
|
+
server = Server.connection.cfg.haproxy.enabled
|
|
527
|
+
? Server.create_smtps_server(opts, onConnect)
|
|
528
|
+
: tls.createServer(opts, onConnect)
|
|
529
|
+
const tls_event_server = server.tlsServer || server
|
|
530
|
+
tls_socket.addOCSP(tls_event_server)
|
|
387
531
|
server.has_tls = true
|
|
388
|
-
|
|
532
|
+
tls_event_server.on('resumeSession', (id, rsDone) => {
|
|
389
533
|
Server.loginfo('client requested TLS resumeSession')
|
|
390
534
|
rsDone(null, null)
|
|
391
535
|
})
|
|
@@ -394,7 +538,7 @@ Server.get_smtp_server = async (ep, inactivity_timeout) => {
|
|
|
394
538
|
} else {
|
|
395
539
|
server = tls_socket.createServer(onConnect)
|
|
396
540
|
server.has_tls = false
|
|
397
|
-
|
|
541
|
+
await tls_socket.getSocketOpts('*')
|
|
398
542
|
Server.listeners.push(server)
|
|
399
543
|
return server
|
|
400
544
|
}
|
|
@@ -403,7 +547,7 @@ Server.get_smtp_server = async (ep, inactivity_timeout) => {
|
|
|
403
547
|
Server.setup_smtp_listeners = async (plugins2, type, inactivity_timeout) => {
|
|
404
548
|
const errors = []
|
|
405
549
|
|
|
406
|
-
for (const [
|
|
550
|
+
for (const [, ifObj] of Object.entries(os.networkInterfaces())) {
|
|
407
551
|
for (const addr of ifObj) {
|
|
408
552
|
if (addr.family === 'IPv6') {
|
|
409
553
|
if (!Server.notes.IPv6) Server.notes.IPv6 = true
|
|
@@ -476,7 +620,7 @@ Server.setup_http_listeners = async () => {
|
|
|
476
620
|
try {
|
|
477
621
|
Server.http.express = require('express')
|
|
478
622
|
Server.loginfo('express loaded at Server.http.express')
|
|
479
|
-
} catch
|
|
623
|
+
} catch {
|
|
480
624
|
Server.logerror('express failed to load. No http server. Install express with: npm install -g express')
|
|
481
625
|
return
|
|
482
626
|
}
|
|
@@ -531,7 +675,7 @@ Server.init_master_respond = async (retval, msg) => {
|
|
|
531
675
|
if (!(cluster && c.nodes)) {
|
|
532
676
|
try {
|
|
533
677
|
await outbound.init_queue()
|
|
534
|
-
} catch
|
|
678
|
+
} catch {
|
|
535
679
|
Server.logcrit('Loading queue failed. Shutting down.')
|
|
536
680
|
return logger.dump_and_exit(1)
|
|
537
681
|
}
|
|
@@ -566,7 +710,7 @@ Server.init_master_respond = async (retval, msg) => {
|
|
|
566
710
|
Server.lognotice(`worker ${worker.id} listening on ${endpoint(address)}`)
|
|
567
711
|
})
|
|
568
712
|
cluster.on('exit', cluster_exit_listener)
|
|
569
|
-
} catch
|
|
713
|
+
} catch {
|
|
570
714
|
Server.logcrit('Scanning queue failed. Shutting down.')
|
|
571
715
|
logger.dump_and_exit(1)
|
|
572
716
|
}
|
|
@@ -605,7 +749,7 @@ Server.init_child_respond = (retval, msg) => {
|
|
|
605
749
|
process.kill(pid)
|
|
606
750
|
Server.logerror(`Killing master (pid=${pid})`)
|
|
607
751
|
}
|
|
608
|
-
} catch
|
|
752
|
+
} catch {
|
|
609
753
|
Server.logerror('Terminating child')
|
|
610
754
|
}
|
|
611
755
|
logger.dump_and_exit(1)
|
|
@@ -635,7 +779,7 @@ Server.init_http_respond = () => {
|
|
|
635
779
|
let WebSocketServer
|
|
636
780
|
try {
|
|
637
781
|
WebSocketServer = require('ws').Server
|
|
638
|
-
} catch
|
|
782
|
+
} catch {
|
|
639
783
|
Server.logerror(`unable to load ws.\n did you: npm install -g ws?`)
|
|
640
784
|
return
|
|
641
785
|
}
|
package/smtp_client.js
CHANGED
|
@@ -15,7 +15,7 @@ const utils = require('haraka-utils')
|
|
|
15
15
|
|
|
16
16
|
const tls_socket = require('./tls_socket')
|
|
17
17
|
const logger = require('./logger')
|
|
18
|
-
const HostPool =
|
|
18
|
+
const HostPool = net_utils.HostPool
|
|
19
19
|
|
|
20
20
|
const smtp_regexp = /^(\d{3})([ -])(.*)/
|
|
21
21
|
const STATE = {
|
|
@@ -486,6 +486,7 @@ function get_hostport(connection, cfg) {
|
|
|
486
486
|
server.notes.host_pool = new HostPool(
|
|
487
487
|
cfg.forwarding_host_pool, // 1.2.3.4:420, 5.6.7.8:420
|
|
488
488
|
cfg.dead_forwarding_host_retry_secs,
|
|
489
|
+
{ logger },
|
|
489
490
|
)
|
|
490
491
|
}
|
|
491
492
|
|
package/test/connection.js
CHANGED
|
@@ -5,7 +5,7 @@ const assert = require('node:assert/strict')
|
|
|
5
5
|
|
|
6
6
|
const constants = require('haraka-constants')
|
|
7
7
|
const DSN = require('haraka-dsn')
|
|
8
|
-
const { Address } = require('
|
|
8
|
+
const { Address } = require('@haraka/email-address')
|
|
9
9
|
|
|
10
10
|
const connection = require('../connection')
|
|
11
11
|
const Server = require('../server')
|
|
@@ -508,7 +508,10 @@ describe('connection', () => {
|
|
|
508
508
|
assert.equal(this.connection.msg_count.tempfail, 1)
|
|
509
509
|
assert.equal(this.connection.transaction.msg_status, 'deferred')
|
|
510
510
|
assert.equal(harness.calls.reset, 1)
|
|
511
|
-
assert.deepEqual(harness.calls.results[0], {
|
|
511
|
+
assert.deepEqual(harness.calls.results[0], {
|
|
512
|
+
fail: 'Message denied temporarily',
|
|
513
|
+
soft: true,
|
|
514
|
+
})
|
|
512
515
|
} finally {
|
|
513
516
|
harness.restore()
|
|
514
517
|
}
|
|
@@ -654,6 +657,120 @@ describe('connection', () => {
|
|
|
654
657
|
assert.equal(responses[2][0], 503)
|
|
655
658
|
})
|
|
656
659
|
|
|
660
|
+
it('cmd_mail strips control chars from invalid address logs', () => {
|
|
661
|
+
const notices = []
|
|
662
|
+
const responses = []
|
|
663
|
+
this.connection.hello.host = 'example.test'
|
|
664
|
+
this.connection.lognotice = (msg) => notices.push(msg)
|
|
665
|
+
this.connection.respond = (code, msg) => {
|
|
666
|
+
responses.push({ code, msg })
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
this.connection.cmd_mail('FROM:<mail\x00@example.com>')
|
|
670
|
+
|
|
671
|
+
assert.equal(responses[0].code, 501)
|
|
672
|
+
assert.match(responses[0].msg, /^Invalid MAIL FROM address /)
|
|
673
|
+
assert.equal(responses[0].msg.includes('\r'), false)
|
|
674
|
+
assert.equal(responses[0].msg.includes('\n'), false)
|
|
675
|
+
assert.equal(responses[0].msg.includes('\\u0000'), false)
|
|
676
|
+
assert.equal(notices[0], responses[0].msg)
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
it('cmd_rcpt strips control chars from invalid address logs', () => {
|
|
680
|
+
const notices = []
|
|
681
|
+
const responses = []
|
|
682
|
+
this.connection.transaction = { mail_from: new Address('<from@example.com>'), rcpt_to: [] }
|
|
683
|
+
this.connection.lognotice = (msg) => notices.push(msg)
|
|
684
|
+
this.connection.respond = (code, msg) => {
|
|
685
|
+
responses.push({ code, msg })
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
this.connection.cmd_rcpt('TO:<rcpt\x00@example.com>')
|
|
689
|
+
|
|
690
|
+
assert.equal(responses[0].code, 501)
|
|
691
|
+
assert.match(responses[0].msg, /^Invalid RCPT TO address /)
|
|
692
|
+
assert.equal(responses[0].msg.includes('\r'), false)
|
|
693
|
+
assert.equal(responses[0].msg.includes('\n'), false)
|
|
694
|
+
assert.equal(responses[0].msg.includes('\\u0000'), false)
|
|
695
|
+
assert.equal(notices[0], responses[0].msg)
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
it('cmd_mail rejects a postel-only address when main.postel is false', () => {
|
|
699
|
+
const responses = []
|
|
700
|
+
this.connection.hello.host = 'example.test'
|
|
701
|
+
this.connection.lognotice = () => {}
|
|
702
|
+
this.connection.respond = (code, msg) => responses.push({ code, msg })
|
|
703
|
+
|
|
704
|
+
this.connection.cmd_mail('FROM:<foo@[IPv6:bogus::xyz]>')
|
|
705
|
+
|
|
706
|
+
assert.equal(responses[0].code, 501)
|
|
707
|
+
assert.match(responses[0].msg, /^Invalid MAIL FROM address /)
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
it('cmd_mail accepts a postel-only address when main.postel is true', () => {
|
|
711
|
+
const responses = []
|
|
712
|
+
let started = false
|
|
713
|
+
this.connection.hello.host = 'example.test'
|
|
714
|
+
this.connection.respond = (code, msg) => responses.push({ code, msg })
|
|
715
|
+
this.connection.init_transaction = () => {
|
|
716
|
+
started = true
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
connection.cfg.main.postel = true
|
|
720
|
+
try {
|
|
721
|
+
this.connection.cmd_mail('FROM:<foo@[IPv6:bogus::xyz]>')
|
|
722
|
+
} finally {
|
|
723
|
+
connection.cfg.main.postel = false
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
assert.equal(started, true)
|
|
727
|
+
assert.equal(
|
|
728
|
+
responses.some((r) => r.code === 501),
|
|
729
|
+
false,
|
|
730
|
+
)
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
it('cmd_rcpt rejects a postel-only address when main.postel is false', () => {
|
|
734
|
+
const responses = []
|
|
735
|
+
this.connection.transaction = {
|
|
736
|
+
mail_from: new Address('<from@example.com>'),
|
|
737
|
+
rcpt_to: [],
|
|
738
|
+
}
|
|
739
|
+
this.connection.lognotice = () => {}
|
|
740
|
+
this.connection.respond = (code, msg) => responses.push({ code, msg })
|
|
741
|
+
|
|
742
|
+
this.connection.cmd_rcpt('TO:<foo@[IPv6:bogus::xyz]>')
|
|
743
|
+
|
|
744
|
+
assert.equal(responses[0].code, 501)
|
|
745
|
+
assert.match(responses[0].msg, /^Invalid RCPT TO address /)
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
it('cmd_rcpt accepts a postel-only address when main.postel is true', () => {
|
|
749
|
+
const plugins = require('../plugins')
|
|
750
|
+
const originalRunHooks = plugins.run_hooks
|
|
751
|
+
const responses = []
|
|
752
|
+
this.connection.transaction = {
|
|
753
|
+
mail_from: new Address('<from@example.com>'),
|
|
754
|
+
rcpt_to: [],
|
|
755
|
+
}
|
|
756
|
+
this.connection.respond = (code, msg) => responses.push({ code, msg })
|
|
757
|
+
plugins.run_hooks = () => {}
|
|
758
|
+
|
|
759
|
+
connection.cfg.main.postel = true
|
|
760
|
+
try {
|
|
761
|
+
this.connection.cmd_rcpt('TO:<foo@[IPv6:bogus::xyz]>')
|
|
762
|
+
} finally {
|
|
763
|
+
connection.cfg.main.postel = false
|
|
764
|
+
plugins.run_hooks = originalRunHooks
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
assert.equal(this.connection.transaction.rcpt_to.length, 1)
|
|
768
|
+
assert.equal(
|
|
769
|
+
responses.some((r) => r.code === 501),
|
|
770
|
+
false,
|
|
771
|
+
)
|
|
772
|
+
})
|
|
773
|
+
|
|
657
774
|
it('data_respond denysoftdisconnect disconnects and default enters DATA', () => {
|
|
658
775
|
const responses = []
|
|
659
776
|
let disconnected = 0
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
4
|
|
|
5
|
-
const { Address } = require('
|
|
5
|
+
const { Address } = require('@haraka/email-address')
|
|
6
6
|
const fixtures = require('haraka-test-fixtures')
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -44,8 +44,7 @@ exports.createHMailItem = (outbound_context, options, callback) => {
|
|
|
44
44
|
const delivery_domain = options.delivery_domain || 'domain'
|
|
45
45
|
const mail_recipients = options.mail_recipients || [new Address('recipient@domain')]
|
|
46
46
|
|
|
47
|
-
const conn = fixtures.
|
|
48
|
-
conn.init_transaction()
|
|
47
|
+
const conn = fixtures.makeConnection({ withTxn: true })
|
|
49
48
|
conn.transaction.mail_from = new Address(mail_from)
|
|
50
49
|
|
|
51
50
|
const todo = new outbound_context.TODOItem(delivery_domain, mail_recipients, conn.transaction)
|
package/test/outbound/index.js
CHANGED
|
@@ -6,7 +6,6 @@ const fs = require('node:fs')
|
|
|
6
6
|
const path = require('node:path')
|
|
7
7
|
|
|
8
8
|
const constants = require('haraka-constants')
|
|
9
|
-
const logger = require('../../logger')
|
|
10
9
|
|
|
11
10
|
const lines = [
|
|
12
11
|
'From: John Johnson <john@example.com>',
|
|
@@ -211,7 +210,7 @@ describe('outbound', () => {
|
|
|
211
210
|
it('yields to setImmediate before opening process_delivery pipes', async () => {
|
|
212
211
|
const stream = require('node:stream')
|
|
213
212
|
const Transaction = require('../../transaction')
|
|
214
|
-
const Address = require('
|
|
213
|
+
const Address = require('@haraka/email-address').Address
|
|
215
214
|
const outbound = require('../../outbound')
|
|
216
215
|
const plugins = require('../../plugins')
|
|
217
216
|
|
|
@@ -271,7 +270,7 @@ describe('outbound', () => {
|
|
|
271
270
|
|
|
272
271
|
it('adds missing Message-Id/Date and prepends Received before queueing', async () => {
|
|
273
272
|
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
274
|
-
const Address = require('
|
|
273
|
+
const Address = require('@haraka/email-address').Address
|
|
275
274
|
const outbound = require('../../outbound')
|
|
276
275
|
const plugins = require('../../plugins')
|
|
277
276
|
|
|
@@ -281,7 +280,7 @@ describe('outbound', () => {
|
|
|
281
280
|
const transaction = {
|
|
282
281
|
uuid: 'txn-add-headers',
|
|
283
282
|
header: {
|
|
284
|
-
get_all(
|
|
283
|
+
get_all() {
|
|
285
284
|
return []
|
|
286
285
|
},
|
|
287
286
|
get() {
|
|
@@ -344,12 +343,12 @@ describe('outbound', () => {
|
|
|
344
343
|
})
|
|
345
344
|
|
|
346
345
|
describe('timer_queue', () => {
|
|
347
|
-
let
|
|
346
|
+
let ob_timer_queue
|
|
348
347
|
|
|
349
348
|
beforeEach(() => {
|
|
350
349
|
process.env.HARAKA_TEST_DIR = path.resolve('test')
|
|
351
|
-
|
|
352
|
-
const TimerQueue = require('
|
|
350
|
+
require('../../outbound')
|
|
351
|
+
const { TimerQueue } = require('haraka-utils')
|
|
353
352
|
ob_timer_queue = new TimerQueue(500)
|
|
354
353
|
})
|
|
355
354
|
|
package/test/outbound/qfile.js
CHANGED
package/test/outbound/queue.js
CHANGED
|
@@ -96,7 +96,7 @@ describe('outbound/queue', () => {
|
|
|
96
96
|
let renameAttempts = 0
|
|
97
97
|
|
|
98
98
|
const originalRename = queue.rename_to_actual_pid
|
|
99
|
-
queue.rename_to_actual_pid = (
|
|
99
|
+
queue.rename_to_actual_pid = () => {
|
|
100
100
|
renameAttempts++
|
|
101
101
|
throw new Error('test skip')
|
|
102
102
|
}
|
|
@@ -107,7 +107,7 @@ describe('outbound/queue', () => {
|
|
|
107
107
|
'1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka',
|
|
108
108
|
'1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka',
|
|
109
109
|
],
|
|
110
|
-
(
|
|
110
|
+
() => {},
|
|
111
111
|
)
|
|
112
112
|
queue.rename_to_actual_pid = originalRename
|
|
113
113
|
assert.equal(renameAttempts, 1)
|