haraka 0.0.32 → 3.3.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.
- package/.claude/settings.local.json +28 -0
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +6 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1872 -62
- package/CLAUDE.md +40 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/GEMINI.md +38 -0
- package/LICENSE +2 -1
- package/Plugins.md +227 -0
- package/README.md +100 -115
- package/SECURITY.md +178 -0
- package/TODO +22 -0
- package/address.js +53 -0
- package/bin/haraka +593 -0
- package/bin/haraka_grep +32 -0
- package/config/aliases +2 -0
- package/config/auth_flat_file.ini +7 -0
- package/config/auth_vpopmaild.ini +9 -0
- package/config/connection.ini +79 -0
- package/config/delay_deny.ini +7 -0
- package/config/dhparams.pem +8 -0
- package/config/host_list +3 -0
- package/config/host_list_regex +6 -0
- package/config/http.ini +11 -0
- package/config/lmtp.ini +7 -0
- package/config/log.ini +11 -0
- package/config/me +1 -0
- package/config/outbound.bounce_message +18 -0
- package/config/outbound.bounce_message_html +36 -0
- package/config/outbound.bounce_message_image +106 -0
- package/config/outbound.ini +24 -0
- package/config/plugins +67 -0
- package/config/smtp.ini +37 -0
- package/config/smtp_bridge.ini +4 -0
- package/config/smtp_forward.ini +31 -0
- package/config/smtp_proxy.ini +27 -0
- package/config/tarpit.timeout +1 -0
- package/config/tls.ini +83 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/config/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1863 -0
- package/contrib/Haraka.cf +6 -0
- package/contrib/Haraka.pm +35 -0
- package/contrib/bad_smtp_server.pl +25 -0
- package/contrib/bsd-rc.d/haraka +61 -0
- package/contrib/debian-init.d/haraka +87 -0
- package/contrib/haraka.init +96 -0
- package/contrib/haraka.service +23 -0
- package/contrib/plugin2npm.sh +81 -0
- package/contrib/ubuntu-upstart/haraka.conf +27 -0
- package/coverage/coverage-final.json +2 -0
- package/coverage/coverage-summary.json +33 -0
- package/coverage/tmp/coverage-79131-1779241025146-0.json +1 -0
- package/coverage/tmp/coverage-79132-1779240999690-0.json +1 -0
- package/coverage/tmp/coverage-79172-1779241000095-0.json +1 -0
- package/coverage/tmp/coverage-79210-1779241000156-0.json +1 -0
- package/coverage/tmp/coverage-79211-1779241000209-0.json +1 -0
- package/coverage/tmp/coverage-79212-1779241000266-0.json +1 -0
- package/coverage/tmp/coverage-79213-1779241000441-0.json +1 -0
- package/coverage/tmp/coverage-79214-1779241000626-0.json +1 -0
- package/coverage/tmp/coverage-79215-1779241000795-0.json +1 -0
- package/coverage/tmp/coverage-79216-1779241000965-0.json +1 -0
- package/coverage/tmp/coverage-79218-1779241001013-0.json +1 -0
- package/coverage/tmp/coverage-79219-1779241001179-0.json +1 -0
- package/coverage/tmp/coverage-79220-1779241006249-0.json +1 -0
- package/coverage/tmp/coverage-79227-1779241011453-0.json +1 -0
- package/coverage/tmp/coverage-79229-1779241011537-0.json +1 -0
- package/coverage/tmp/coverage-79230-1779241011647-0.json +1 -0
- package/coverage/tmp/coverage-79231-1779241011765-0.json +1 -0
- package/coverage/tmp/coverage-79232-1779241011841-0.json +1 -0
- package/coverage/tmp/coverage-79233-1779241011909-0.json +1 -0
- package/coverage/tmp/coverage-79234-1779241011984-0.json +1 -0
- package/coverage/tmp/coverage-79235-1779241012055-0.json +1 -0
- package/coverage/tmp/coverage-79236-1779241012230-0.json +1 -0
- package/coverage/tmp/coverage-79237-1779241012300-0.json +1 -0
- package/coverage/tmp/coverage-79238-1779241012368-0.json +1 -0
- package/coverage/tmp/coverage-79239-1779241012438-0.json +1 -0
- package/coverage/tmp/coverage-79240-1779241012511-0.json +1 -0
- package/coverage/tmp/coverage-79241-1779241012582-0.json +1 -0
- package/coverage/tmp/coverage-79242-1779241012652-0.json +1 -0
- package/coverage/tmp/coverage-79243-1779241012814-0.json +1 -0
- package/coverage/tmp/coverage-79244-1779241012931-0.json +1 -0
- package/coverage/tmp/coverage-79245-1779241013007-0.json +1 -0
- package/coverage/tmp/coverage-79246-1779241013106-0.json +1 -0
- package/coverage/tmp/coverage-79247-1779241013178-0.json +1 -0
- package/coverage/tmp/coverage-79248-1779241013244-0.json +1 -0
- package/coverage/tmp/coverage-79249-1779241013409-0.json +1 -0
- package/coverage/tmp/coverage-79250-1779241013697-0.json +1 -0
- package/coverage/tmp/coverage-79251-1779241013847-0.json +1 -0
- package/coverage/tmp/coverage-79252-1779241014288-0.json +1 -0
- package/coverage/tmp/coverage-79253-1779241014378-0.json +1 -0
- package/coverage/tmp/coverage-79254-1779241014428-0.json +1 -0
- package/coverage/tmp/coverage-79255-1779241021774-0.json +1 -0
- package/coverage/tmp/coverage-80382-1779241021949-0.json +1 -0
- package/coverage/tmp/coverage-80383-1779241025019-0.json +1 -0
- package/coverage/tmp/coverage-80384-1779241025133-0.json +1 -0
- package/docs/Body.md +1 -0
- package/docs/Config.md +1 -0
- package/docs/Connection.md +153 -0
- package/docs/CoreConfig.md +96 -0
- package/docs/CustomReturnCodes.md +3 -0
- package/docs/HAProxy.md +62 -0
- package/docs/Header.md +1 -0
- package/docs/Logging.md +129 -0
- package/docs/Outbound.md +210 -0
- package/docs/Plugins.md +372 -0
- package/docs/Results.md +7 -0
- package/docs/Transaction.md +135 -0
- package/docs/Tutorial.md +183 -0
- package/docs/deprecated/access.md +3 -0
- package/docs/deprecated/backscatterer.md +9 -0
- package/docs/deprecated/connect.rdns_access.md +53 -0
- package/docs/deprecated/data.headers.md +3 -0
- package/docs/deprecated/data.nomsgid.md +7 -0
- package/docs/deprecated/data.noreceived.md +11 -0
- package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
- package/docs/deprecated/dkim_sign.md +97 -0
- package/docs/deprecated/dkim_verify.md +28 -0
- package/docs/deprecated/dnsbl.md +80 -0
- package/docs/deprecated/dnswl.md +73 -0
- package/docs/deprecated/lookup_rdns.strict.md +67 -0
- package/docs/deprecated/mail_from.access.md +52 -0
- package/docs/deprecated/mail_from.blocklist.md +18 -0
- package/docs/deprecated/mail_from.nobounces.md +8 -0
- package/docs/deprecated/rcpt_to.access.md +53 -0
- package/docs/deprecated/rcpt_to.blocklist.md +18 -0
- package/docs/deprecated/rcpt_to.routes.md +3 -0
- package/docs/deprecated/rdns.regexp.md +30 -0
- package/docs/plugins/aliases.md +3 -0
- package/docs/plugins/auth/auth_bridge.md +34 -0
- package/docs/plugins/auth/auth_ldap.md +4 -0
- package/docs/plugins/auth/auth_proxy.md +36 -0
- package/docs/plugins/auth/auth_vpopmaild.md +33 -0
- package/docs/plugins/auth/flat_file.md +40 -0
- package/docs/plugins/block_me.md +18 -0
- package/docs/plugins/data.signatures.md +11 -0
- package/docs/plugins/delay_deny.md +23 -0
- package/docs/plugins/max_unrecognized_commands.md +6 -0
- package/docs/plugins/prevent_credential_leaks.md +22 -0
- package/docs/plugins/process_title.md +42 -0
- package/docs/plugins/queue/deliver.md +3 -0
- package/docs/plugins/queue/discard.md +32 -0
- package/docs/plugins/queue/lmtp.md +24 -0
- package/docs/plugins/queue/qmail-queue.md +16 -0
- package/docs/plugins/queue/quarantine.md +87 -0
- package/docs/plugins/queue/smtp_bridge.md +32 -0
- package/docs/plugins/queue/smtp_forward.md +127 -0
- package/docs/plugins/queue/smtp_proxy.md +68 -0
- package/docs/plugins/queue/test.md +7 -0
- package/docs/plugins/rcpt_to.in_host_list.md +34 -0
- package/docs/plugins/rcpt_to.max_count.md +3 -0
- package/docs/plugins/record_envelope_addresses.md +20 -0
- package/docs/plugins/relay.md +3 -0
- package/docs/plugins/reseed_rng.md +16 -0
- package/docs/plugins/status.md +41 -0
- package/docs/plugins/tarpit.md +50 -0
- package/docs/plugins/tls.md +235 -0
- package/docs/plugins/toobusy.md +27 -0
- package/docs/plugins/xclient.md +10 -0
- package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
- package/docs/tutorials/SettingUpOutbound.md +62 -0
- package/eslint.config.mjs +2 -0
- package/haraka.js +74 -0
- package/haraka.sh +2 -0
- package/http/html/404.html +58 -0
- package/http/html/index.html +47 -0
- package/http/package.json +21 -0
- package/line_socket.js +24 -0
- package/logger.js +322 -0
- package/outbound/client_pool.js +59 -0
- package/outbound/config.js +134 -0
- package/outbound/hmail.js +1504 -0
- package/outbound/index.js +349 -0
- package/outbound/qfile.js +93 -0
- package/outbound/queue.js +399 -0
- package/outbound/tls.js +85 -0
- package/outbound/todo.js +17 -0
- package/package.json +91 -29
- package/plugins/.eslintrc.yaml +3 -0
- package/plugins/auth/auth_base.js +261 -0
- package/plugins/auth/auth_bridge.js +20 -0
- package/plugins/auth/auth_proxy.js +227 -0
- package/plugins/auth/auth_vpopmaild.js +162 -0
- package/plugins/auth/flat_file.js +44 -0
- package/plugins/block_me.js +88 -0
- package/plugins/data.signatures.js +30 -0
- package/plugins/delay_deny.js +153 -0
- package/plugins/prevent_credential_leaks.js +61 -0
- package/plugins/process_title.js +197 -0
- package/plugins/profile.js +11 -0
- package/plugins/queue/deliver.js +12 -0
- package/plugins/queue/discard.js +27 -0
- package/plugins/queue/lmtp.js +45 -0
- package/plugins/queue/qmail-queue.js +93 -0
- package/plugins/queue/quarantine.js +133 -0
- package/plugins/queue/smtp_bridge.js +45 -0
- package/plugins/queue/smtp_forward.js +371 -0
- package/plugins/queue/smtp_proxy.js +142 -0
- package/plugins/queue/test.js +15 -0
- package/plugins/rcpt_to.host_list_base.js +65 -0
- package/plugins/rcpt_to.in_host_list.js +56 -0
- package/plugins/record_envelope_addresses.js +17 -0
- package/plugins/reseed_rng.js +7 -0
- package/plugins/status.js +274 -0
- package/plugins/tarpit.js +45 -0
- package/plugins/tls.js +164 -0
- package/plugins/toobusy.js +47 -0
- package/plugins/xclient.js +124 -0
- package/plugins.js +604 -0
- package/queue/1772642154987_1775581346001_4_82235_TGwgfd_2_mattbook-m3.home.simerson.net +0 -0
- package/run_tests +11 -0
- package/server.js +827 -0
- package/smtp_client.js +504 -0
- package/test/.eslintrc.yaml +11 -0
- package/test/config/auth_flat_file.ini +5 -0
- package/test/config/block_me.recipient +1 -0
- package/test/config/block_me.senders +1 -0
- package/test/config/dhparams.pem +8 -0
- package/test/config/host_list +2 -0
- package/test/config/outbound_tls_cert.pem +1 -0
- package/test/config/outbound_tls_key.pem +1 -0
- package/test/config/plugins +7 -0
- package/test/config/smtp.ini +11 -0
- package/test/config/smtp_forward.ini +30 -0
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/config/tls/haraka.local.pem +51 -0
- package/test/config/tls.ini +45 -0
- package/test/config/tls_cert.pem +21 -0
- package/test/config/tls_key.pem +28 -0
- package/test/connection.js +817 -0
- 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 +21 -0
- package/test/fixtures/todo_qfile.txt +0 -0
- package/test/fixtures/util_hmailitem.js +156 -0
- package/test/installation/config/test-plugin-flat +1 -0
- package/test/installation/config/test-plugin.ini +10 -0
- package/test/installation/config/tls.ini +1 -0
- package/test/installation/node_modules/load_first/index.js +5 -0
- package/test/installation/node_modules/load_first/package.json +11 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
- package/test/installation/node_modules/test-plugin/package.json +5 -0
- package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
- package/test/installation/plugins/base_plugin.js +3 -0
- package/test/installation/plugins/folder_plugin/index.js +3 -0
- package/test/installation/plugins/folder_plugin/package.json +11 -0
- package/test/installation/plugins/inherits.js +7 -0
- package/test/installation/plugins/load_first.js +3 -0
- package/test/installation/plugins/plugin.js +1 -0
- package/test/installation/plugins/tls.js +3 -0
- package/test/logger.js +217 -0
- package/test/loud/config/dhparams.pem +0 -0
- package/test/loud/config/tls/goobered.pem +45 -0
- package/test/loud/config/tls.ini +43 -0
- package/test/mail_specimen/base64-root-part.txt +23 -0
- package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
- package/test/outbound/bounce_net_errors.js +133 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +210 -0
- package/test/outbound/index.js +385 -0
- package/test/outbound/qfile.js +124 -0
- package/test/outbound/queue.js +325 -0
- package/test/plugins/auth/auth_base.js +620 -0
- package/test/plugins/auth/auth_bridge.js +80 -0
- package/test/plugins/auth/auth_vpopmaild.js +81 -0
- package/test/plugins/auth/flat_file.js +123 -0
- package/test/plugins/block_me.js +141 -0
- package/test/plugins/data.signatures.js +111 -0
- package/test/plugins/delay_deny.js +262 -0
- package/test/plugins/prevent_credential_leaks.js +174 -0
- package/test/plugins/process_title.js +141 -0
- package/test/plugins/queue/deliver.js +98 -0
- package/test/plugins/queue/discard.js +78 -0
- package/test/plugins/queue/lmtp.js +137 -0
- package/test/plugins/queue/qmail-queue.js +98 -0
- package/test/plugins/queue/quarantine.js +80 -0
- package/test/plugins/queue/smtp_bridge.js +152 -0
- package/test/plugins/queue/smtp_forward.js +1023 -0
- package/test/plugins/queue/smtp_proxy.js +138 -0
- package/test/plugins/rcpt_to.host_list_base.js +102 -0
- package/test/plugins/rcpt_to.in_host_list.js +186 -0
- package/test/plugins/record_envelope_addresses.js +66 -0
- package/test/plugins/reseed_rng.js +34 -0
- package/test/plugins/status.js +207 -0
- package/test/plugins/tarpit.js +90 -0
- package/test/plugins/tls.js +86 -0
- package/test/plugins/toobusy.js +21 -0
- package/test/plugins/xclient.js +119 -0
- package/test/plugins.js +230 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- package/test/queue/zero-length +0 -0
- package/test/server.js +1012 -0
- package/test/smtp_client.js +1303 -0
- package/test/tls_socket.js +321 -0
- package/test/transaction.js +554 -0
- package/tls_socket.js +771 -0
- package/transaction.js +267 -0
- package/lib/index.js +0 -371
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Queue to qmail-queue
|
|
2
|
+
|
|
3
|
+
const childproc = require('node:child_process')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
|
|
6
|
+
exports.register = function () {
|
|
7
|
+
this.queue_exec = this.config.get('qmail-queue.path') || '/var/qmail/bin/qmail-queue'
|
|
8
|
+
if (!fs.existsSync(this.queue_exec)) {
|
|
9
|
+
throw new Error(`Cannot find qmail-queue binary (${this.queue_exec})`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.load_qmail_queue_ini()
|
|
13
|
+
|
|
14
|
+
if (this.cfg.main.enable_outbound) {
|
|
15
|
+
this.register_hook('queue_outbound', 'hook_queue')
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.load_qmail_queue_ini = function () {
|
|
20
|
+
this.cfg = this.config.get(
|
|
21
|
+
'qmail-queue.ini',
|
|
22
|
+
{
|
|
23
|
+
booleans: ['+main.enable_outbound'],
|
|
24
|
+
},
|
|
25
|
+
() => {
|
|
26
|
+
this.load_qmail_queue_ini()
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// qmail-queue envelope: F<sender>\0 (T<rcpt>\0)* \0
|
|
32
|
+
// Built dynamically, sized to exactly the bytes needed.
|
|
33
|
+
// doesn't emit zero padding after the terminating NUL.
|
|
34
|
+
// encodes non-ASCII (SMTPUTF8) addresses correctly
|
|
35
|
+
exports.build_envelope = function (transaction) {
|
|
36
|
+
const NUL = Buffer.from([0])
|
|
37
|
+
const parts = [Buffer.from('F'), Buffer.from(transaction.mail_from.address), NUL]
|
|
38
|
+
for (const rcpt of transaction.rcpt_to) {
|
|
39
|
+
parts.push(Buffer.from('T'), Buffer.from(rcpt.address), NUL)
|
|
40
|
+
}
|
|
41
|
+
parts.push(NUL)
|
|
42
|
+
return Buffer.concat(parts)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
exports.hook_queue = function (next, connection) {
|
|
46
|
+
const plugin = this
|
|
47
|
+
|
|
48
|
+
const txn = connection?.transaction
|
|
49
|
+
if (!txn) return next()
|
|
50
|
+
|
|
51
|
+
const q_wants = txn.notes.get('queue.wants')
|
|
52
|
+
if (q_wants && q_wants !== 'qmail-queue') return next()
|
|
53
|
+
|
|
54
|
+
const qmail_queue = childproc.spawn(
|
|
55
|
+
this.queue_exec, // process name
|
|
56
|
+
[], // arguments
|
|
57
|
+
{ stdio: ['pipe', 'pipe', process.stderr] },
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
qmail_queue.on('exit', function finished(code) {
|
|
61
|
+
if (code !== 0) {
|
|
62
|
+
connection.logerror(plugin, `Unable to queue message to qmail-queue: ${code}`)
|
|
63
|
+
next()
|
|
64
|
+
} else {
|
|
65
|
+
next(OK, 'Queued!')
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
connection.transaction.message_stream.pipe(qmail_queue.stdin, {
|
|
70
|
+
line_endings: '\n',
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
qmail_queue.stdin.on('close', () => {
|
|
74
|
+
if (!connection?.transaction) {
|
|
75
|
+
plugin.logerror('Transaction went away while delivering mail to qmail-queue')
|
|
76
|
+
try {
|
|
77
|
+
qmail_queue.stdout.end()
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err.code !== 'ENOTCONN') {
|
|
80
|
+
// Ignore ENOTCONN and re throw anything else
|
|
81
|
+
throw err
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
connection.results.add(plugin, { err: 'dead sender' })
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
plugin.loginfo('Message Stream sent to qmail. Now sending envelope')
|
|
89
|
+
const buf = plugin.build_envelope(connection.transaction)
|
|
90
|
+
qmail_queue.stdout.on('error', () => {}) // stdout throws an error on close
|
|
91
|
+
qmail_queue.stdout.end(buf)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// quarantine
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
exports.register = function () {
|
|
7
|
+
this.load_quarantine_ini()
|
|
8
|
+
|
|
9
|
+
this.register_hook('queue', 'quarantine')
|
|
10
|
+
this.register_hook('queue_outbound', 'quarantine')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.hook_init_master = function (next) {
|
|
14
|
+
this.init_quarantine_dir(() => {
|
|
15
|
+
this.clean_tmp_directory(next)
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.load_quarantine_ini = function () {
|
|
20
|
+
this.cfg = this.config.get('quarantine.ini', () => {
|
|
21
|
+
this.load_quarantine_ini()
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const zeroPad = (exports.zeroPad = (n, digits) => {
|
|
26
|
+
n = n.toString()
|
|
27
|
+
while (n.length < digits) {
|
|
28
|
+
n = `0${n}`
|
|
29
|
+
}
|
|
30
|
+
return n
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
exports.clean_tmp_directory = function (next) {
|
|
34
|
+
const tmp_dir = path.join(this.get_base_dir(), 'tmp')
|
|
35
|
+
|
|
36
|
+
if (fs.existsSync(tmp_dir)) {
|
|
37
|
+
const dirent = fs.readdirSync(tmp_dir)
|
|
38
|
+
this.loginfo(`Removing temporary files from: ${tmp_dir}`)
|
|
39
|
+
for (const element of dirent) {
|
|
40
|
+
fs.unlinkSync(path.join(tmp_dir, element))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
next()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function wants_quarantine(connection) {
|
|
47
|
+
const { notes, transaction } = connection ?? {}
|
|
48
|
+
|
|
49
|
+
if (notes.quarantine) return notes.quarantine
|
|
50
|
+
|
|
51
|
+
if (transaction.notes.quarantine) return transaction.notes.quarantine
|
|
52
|
+
|
|
53
|
+
return transaction.notes.get('queue.wants') === 'quarantine'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
exports.get_base_dir = function () {
|
|
57
|
+
if (this.cfg.main.quarantine_path) return this.cfg.main.quarantine_path
|
|
58
|
+
return '/var/spool/haraka/quarantine'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.init_quarantine_dir = function (done) {
|
|
62
|
+
const tmp_dir = path.join(this.get_base_dir(), 'tmp')
|
|
63
|
+
fs.promises
|
|
64
|
+
.mkdir(tmp_dir, { recursive: true })
|
|
65
|
+
.then(() => this.loginfo(`created ${tmp_dir}`))
|
|
66
|
+
.catch(() => this.logerror(`Unable to create ${tmp_dir}`))
|
|
67
|
+
.finally(done)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
exports.quarantine = function (next, connection) {
|
|
71
|
+
const quarantine = wants_quarantine(connection)
|
|
72
|
+
this.logdebug(`quarantine: ${quarantine}`)
|
|
73
|
+
if (!quarantine) return next()
|
|
74
|
+
|
|
75
|
+
// Calculate date in YYYYMMDD format
|
|
76
|
+
const d = new Date()
|
|
77
|
+
const yyyymmdd = d.getFullYear() + zeroPad(d.getMonth() + 1, 2) + this.zeroPad(d.getDate(), 2)
|
|
78
|
+
|
|
79
|
+
let subdir = yyyymmdd
|
|
80
|
+
// Allow either boolean or a sub-directory to be specified
|
|
81
|
+
|
|
82
|
+
if (typeof quarantine !== 'boolean' && quarantine !== 1) {
|
|
83
|
+
subdir = path.join(quarantine, yyyymmdd)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const txn = connection?.transaction
|
|
87
|
+
if (!txn) return next()
|
|
88
|
+
|
|
89
|
+
const base_dir = this.get_base_dir()
|
|
90
|
+
const msg_dir = path.join(base_dir, subdir)
|
|
91
|
+
const tmp_path = path.join(base_dir, 'tmp', txn.uuid)
|
|
92
|
+
const msg_path = path.join(msg_dir, txn.uuid)
|
|
93
|
+
|
|
94
|
+
// Create all the directories recursively if they do not exist.
|
|
95
|
+
// Then write the file to a temporary directory first, once this is
|
|
96
|
+
// successful we hardlink the file to the final destination and then
|
|
97
|
+
// remove the temporary file to guarantee a complete file in the
|
|
98
|
+
// final destination.
|
|
99
|
+
fs.promises
|
|
100
|
+
.mkdir(msg_dir, { recursive: true })
|
|
101
|
+
.catch(() => {
|
|
102
|
+
connection.logerror(this, `Error creating directory: ${msg_dir}`)
|
|
103
|
+
next()
|
|
104
|
+
})
|
|
105
|
+
.then(() => {
|
|
106
|
+
const ws = fs.createWriteStream(tmp_path)
|
|
107
|
+
|
|
108
|
+
ws.on('error', (err) => {
|
|
109
|
+
connection.logerror(this, `Error writing quarantine file: ${err.message}`)
|
|
110
|
+
return next()
|
|
111
|
+
})
|
|
112
|
+
ws.on('close', () => {
|
|
113
|
+
fs.link(tmp_path, msg_path, (err) => {
|
|
114
|
+
if (err) {
|
|
115
|
+
connection.logerror(this, `Error writing quarantine file: ${err}`)
|
|
116
|
+
} else {
|
|
117
|
+
// Add a note to where we stored the message
|
|
118
|
+
txn.notes.quarantined = msg_path
|
|
119
|
+
txn.results.add(this, { pass: msg_path, emit: true })
|
|
120
|
+
// Now delete the temporary file
|
|
121
|
+
fs.unlink(tmp_path, () => {})
|
|
122
|
+
}
|
|
123
|
+
// Using notes.quarantine_action to decide what to do after the message is quarantined.
|
|
124
|
+
// Format can be either action = [ code, msg ] or action = code
|
|
125
|
+
const action = connection.notes.quarantine_action || txn.notes.quarantine_action
|
|
126
|
+
if (!action) return next()
|
|
127
|
+
if (Array.isArray(action)) return next(action[0], action[1])
|
|
128
|
+
return next(action)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
txn.message_stream.pipe(ws, { line_endings: '\n' })
|
|
132
|
+
})
|
|
133
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Bridge to an SMTP server
|
|
2
|
+
// Overrides the MX and sets the same AUTH user and password
|
|
3
|
+
|
|
4
|
+
exports.register = function () {
|
|
5
|
+
this.load_flat_ini()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
exports.load_flat_ini = function () {
|
|
9
|
+
this.cfg = this.config.get('smtp_bridge.ini', () => {
|
|
10
|
+
this.load_flat_ini()
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.hook_data_post = (next, connection) => {
|
|
15
|
+
const txn = connection?.transaction
|
|
16
|
+
if (!txn) return next()
|
|
17
|
+
|
|
18
|
+
// Copy auth notes to transaction notes so they're available in hmail.todo.notes
|
|
19
|
+
txn.notes.auth_user = connection.notes.auth_user
|
|
20
|
+
txn.notes.auth_passwd = connection.notes.auth_passwd
|
|
21
|
+
return next()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.hook_get_mx = function (next, hmail) {
|
|
25
|
+
let priority = 10
|
|
26
|
+
if (this.cfg.main.priority) {
|
|
27
|
+
priority = this.cfg.main.priority
|
|
28
|
+
}
|
|
29
|
+
let authType = null
|
|
30
|
+
if (this.cfg.main.auth_type) {
|
|
31
|
+
authType = this.cfg.main.auth_type
|
|
32
|
+
}
|
|
33
|
+
let port = null
|
|
34
|
+
if (this.cfg.main.port) {
|
|
35
|
+
port = this.cfg.main.port
|
|
36
|
+
}
|
|
37
|
+
return next(OK, {
|
|
38
|
+
priority,
|
|
39
|
+
exchange: this.cfg.main.host,
|
|
40
|
+
port,
|
|
41
|
+
auth_type: authType,
|
|
42
|
+
auth_user: hmail.todo.notes.auth_user,
|
|
43
|
+
auth_pass: hmail.todo.notes.auth_passwd,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Forward to an SMTP server
|
|
3
|
+
// Opens the connection to the ongoing SMTP server at queue time
|
|
4
|
+
// and passes back any errors seen on the ongoing server to the
|
|
5
|
+
// originating server.
|
|
6
|
+
|
|
7
|
+
const url = require('node:url')
|
|
8
|
+
|
|
9
|
+
const smtp_client_mod = require('../../smtp_client')
|
|
10
|
+
const tls_socket = require('../../tls_socket')
|
|
11
|
+
|
|
12
|
+
exports.register = function () {
|
|
13
|
+
this.load_errs = []
|
|
14
|
+
|
|
15
|
+
this.load_smtp_forward_ini()
|
|
16
|
+
|
|
17
|
+
if (this.load_errs.length > 0) return
|
|
18
|
+
|
|
19
|
+
if (this.cfg.main.check_sender) {
|
|
20
|
+
this.register_hook('mail', 'check_sender')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (this.cfg.main.check_recipient) {
|
|
24
|
+
this.register_hook('rcpt', 'check_recipient')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.register_hook('queue', 'queue_forward')
|
|
28
|
+
|
|
29
|
+
if (this.cfg.main.enable_outbound) {
|
|
30
|
+
// deliver local message via smtp forward when relaying=true
|
|
31
|
+
this.register_hook('queue_outbound', 'queue_forward')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// may specify more specific [per-domain] outbound routes
|
|
35
|
+
this.register_hook('get_mx', 'get_mx')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
exports.load_smtp_forward_ini = function () {
|
|
39
|
+
this.cfg = this.config.get(
|
|
40
|
+
'smtp_forward.ini',
|
|
41
|
+
{
|
|
42
|
+
booleans: [
|
|
43
|
+
'-main.enable_tls',
|
|
44
|
+
'-main.enable_outbound',
|
|
45
|
+
'main.one_message_per_rcpt',
|
|
46
|
+
'-main.check_sender',
|
|
47
|
+
'-main.check_recipient',
|
|
48
|
+
'*.enable_tls',
|
|
49
|
+
'*.enable_outbound',
|
|
50
|
+
'+tls.requestCert',
|
|
51
|
+
'+tls.honorCipherOrder',
|
|
52
|
+
'-tls.rejectUnauthorized',
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
() => {
|
|
56
|
+
this.load_smtp_forward_ini()
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Build backend TLS options from tls.ini [main] + this plugin's [tls] section.
|
|
61
|
+
// Re-derived on every (re)load so SIGHUP picks up edits.
|
|
62
|
+
this.tls_options = tls_socket.load_plugin_tls_options(this.cfg.tls || {})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
exports.get_config = function (conn) {
|
|
66
|
+
if (!conn.transaction) return this.cfg.main
|
|
67
|
+
|
|
68
|
+
let dom, address
|
|
69
|
+
if (this.cfg.main.domain_selector === 'mail_from') {
|
|
70
|
+
if (!conn.transaction.mail_from) return this.cfg.main
|
|
71
|
+
dom = conn.transaction.mail_from.host
|
|
72
|
+
address = conn.transaction.mail_from.address
|
|
73
|
+
} else {
|
|
74
|
+
if (!conn.transaction.rcpt_to[0]) return this.cfg.main
|
|
75
|
+
dom = conn.transaction.rcpt_to[0].host
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (address && this.cfg[address]) return this.cfg[address]
|
|
79
|
+
if (!dom) return this.cfg.main
|
|
80
|
+
if (!this.cfg[dom]) return this.cfg.main // no specific route
|
|
81
|
+
|
|
82
|
+
return this.cfg[dom]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
exports.is_outbound_enabled = function (dom_cfg) {
|
|
86
|
+
if ('enable_outbound' in dom_cfg) return dom_cfg.enable_outbound // per-domain flag
|
|
87
|
+
|
|
88
|
+
return this.cfg.main.enable_outbound // follow the global configuration
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
exports.check_sender = function (next, connection, params) {
|
|
92
|
+
const txn = connection?.transaction
|
|
93
|
+
if (!txn) return
|
|
94
|
+
|
|
95
|
+
const email = params[0].address
|
|
96
|
+
if (!email) {
|
|
97
|
+
txn.results.add(this, { skip: 'mail_from.null', emit: true })
|
|
98
|
+
return next()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const domain = params[0].host.toLowerCase()
|
|
102
|
+
if (!this.cfg[domain]) return next()
|
|
103
|
+
|
|
104
|
+
// domain is defined in smtp_forward.ini
|
|
105
|
+
txn.notes.local_sender = true
|
|
106
|
+
|
|
107
|
+
if (!connection.relaying) {
|
|
108
|
+
txn.results.add(this, { fail: 'mail_from!spoof' })
|
|
109
|
+
return next(DENY, 'Spoofed MAIL FROM')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
txn.results.add(this, { pass: 'mail_from' })
|
|
113
|
+
next()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
exports.set_queue = function (connection, queue_wanted, domain) {
|
|
117
|
+
let dom_cfg = this.cfg[domain]
|
|
118
|
+
if (dom_cfg === undefined) dom_cfg = {}
|
|
119
|
+
|
|
120
|
+
if (!queue_wanted) queue_wanted = dom_cfg.queue || this.cfg.main.queue
|
|
121
|
+
if (!queue_wanted) return true
|
|
122
|
+
|
|
123
|
+
let dst_host = dom_cfg.host || this.cfg.main.host
|
|
124
|
+
if (dst_host) dst_host = `smtp://${dst_host}`
|
|
125
|
+
|
|
126
|
+
const notes = connection?.transaction?.notes
|
|
127
|
+
if (!notes) return false
|
|
128
|
+
if (!notes.get('queue.wants')) {
|
|
129
|
+
notes.set('queue.wants', queue_wanted)
|
|
130
|
+
if (dst_host) notes.set('queue.next_hop', dst_host)
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// multiple recipients with same destination
|
|
135
|
+
if (notes.get('queue.wants') === queue_wanted) {
|
|
136
|
+
if (!dst_host) return true
|
|
137
|
+
|
|
138
|
+
const next_hop = notes.get('queue.next_hop')
|
|
139
|
+
if (!next_hop) return true
|
|
140
|
+
if (next_hop === dst_host) return true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// multiple recipients with different forward host, soft deny
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
exports.check_recipient = function (next, connection, params) {
|
|
148
|
+
const txn = connection?.transaction
|
|
149
|
+
if (!txn) return
|
|
150
|
+
|
|
151
|
+
const rcpt = params[0]
|
|
152
|
+
if (!rcpt.host) {
|
|
153
|
+
txn.results.add(this, { skip: 'rcpt!domain' })
|
|
154
|
+
return next()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (connection.relaying && txn.notes.local_sender) {
|
|
158
|
+
this.set_queue(connection, 'outbound')
|
|
159
|
+
txn.results.add(this, { pass: 'relaying local_sender' })
|
|
160
|
+
return next(OK)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const domain = rcpt.host.toLowerCase()
|
|
164
|
+
if (this.cfg[domain] !== undefined) {
|
|
165
|
+
if (this.set_queue(connection, 'smtp_forward', domain)) {
|
|
166
|
+
txn.results.add(this, { pass: 'rcpt_to' })
|
|
167
|
+
return next(OK)
|
|
168
|
+
}
|
|
169
|
+
txn.results.add(this, { pass: 'rcpt_to.split' })
|
|
170
|
+
return next(DENYSOFT, 'Split transaction, retry soon')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// the MAIL FROM domain is not local and neither is the RCPT TO
|
|
174
|
+
// Another RCPT plugin may vouch for this recipient.
|
|
175
|
+
txn.results.add(this, { msg: 'rcpt!local' })
|
|
176
|
+
next()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
exports.auth = function (cfg, connection, smtp_client) {
|
|
180
|
+
connection.loginfo(this, `Configuring authentication for SMTP server ${cfg.host}:${cfg.port}`)
|
|
181
|
+
smtp_client.on('capabilities', () => {
|
|
182
|
+
connection.loginfo(this, 'capabilities received')
|
|
183
|
+
|
|
184
|
+
if ('secured' in smtp_client) {
|
|
185
|
+
connection.loginfo(this, 'secured is pending')
|
|
186
|
+
if (smtp_client.secured === false) {
|
|
187
|
+
connection.loginfo(this, 'Waiting for STARTTLS to complete. AUTH postponed')
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function base64(str) {
|
|
193
|
+
const buffer = Buffer.from(str, 'UTF-8')
|
|
194
|
+
return buffer.toString('base64')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (cfg.auth_type === 'plain') {
|
|
198
|
+
connection.loginfo(this, `Authenticating with AUTH PLAIN ${cfg.auth_user}`)
|
|
199
|
+
smtp_client.send_command('AUTH', `PLAIN ${base64(`\0${cfg.auth_user}\0${cfg.auth_pass}`)}`)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (cfg.auth_type === 'login') {
|
|
204
|
+
smtp_client.authenticating = true
|
|
205
|
+
smtp_client.authenticated = false
|
|
206
|
+
|
|
207
|
+
connection.loginfo(this, `Authenticating with AUTH LOGIN ${cfg.auth_user}`)
|
|
208
|
+
smtp_client.send_command('AUTH', 'LOGIN')
|
|
209
|
+
smtp_client.on('auth', () => {
|
|
210
|
+
// do nothing
|
|
211
|
+
})
|
|
212
|
+
smtp_client.on('auth_username', () => {
|
|
213
|
+
smtp_client.send_command(base64(cfg.auth_user))
|
|
214
|
+
})
|
|
215
|
+
smtp_client.on('auth_password', () => {
|
|
216
|
+
smtp_client.send_command(base64(cfg.auth_pass))
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
exports.forward_enabled = function (conn, dom_cfg) {
|
|
223
|
+
const q_wants = conn.transaction.notes.get('queue.wants')
|
|
224
|
+
if (q_wants && q_wants !== 'smtp_forward') {
|
|
225
|
+
conn.logdebug(this, `skipping, unwanted (${q_wants})`)
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (conn.relaying && !this.is_outbound_enabled(dom_cfg)) {
|
|
230
|
+
conn.logdebug(this, 'skipping, outbound disabled')
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
exports.queue_forward = function (next, connection) {
|
|
238
|
+
const plugin = this
|
|
239
|
+
if (connection.remote.closed) return
|
|
240
|
+
const txn = connection?.transaction
|
|
241
|
+
|
|
242
|
+
const cfg = plugin.get_config(connection)
|
|
243
|
+
if (!plugin.forward_enabled(connection, cfg)) return next()
|
|
244
|
+
|
|
245
|
+
smtp_client_mod.get_client_plugin(plugin, connection, cfg, (err, smtp_client) => {
|
|
246
|
+
smtp_client.next = next
|
|
247
|
+
|
|
248
|
+
let rcpt = 0
|
|
249
|
+
|
|
250
|
+
if (cfg.auth_user) plugin.auth(cfg, connection, smtp_client)
|
|
251
|
+
|
|
252
|
+
connection.loginfo(
|
|
253
|
+
plugin,
|
|
254
|
+
`forwarding to ${cfg.forwarding_host_pool ? 'host_pool' : `${cfg.host}:${cfg.port}`}`,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
function get_rs() {
|
|
258
|
+
return txn?.results ?? connection.results
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function dead_sender() {
|
|
262
|
+
if (smtp_client.is_dead_sender(plugin, connection)) {
|
|
263
|
+
get_rs().add(plugin, { err: 'dead sender' })
|
|
264
|
+
return true
|
|
265
|
+
}
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function send_rcpt() {
|
|
270
|
+
if (dead_sender() || !txn) return
|
|
271
|
+
if (rcpt === txn.rcpt_to.length) {
|
|
272
|
+
smtp_client.send_command('DATA')
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
smtp_client.send_command('RCPT', `TO:${txn.rcpt_to[rcpt].format(!smtp_client.smtputf8)}`)
|
|
276
|
+
rcpt++
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
smtp_client.on('mail', send_rcpt)
|
|
280
|
+
|
|
281
|
+
if (cfg.one_message_per_rcpt) {
|
|
282
|
+
smtp_client.on('rcpt', () => {
|
|
283
|
+
smtp_client.send_command('DATA')
|
|
284
|
+
})
|
|
285
|
+
} else {
|
|
286
|
+
smtp_client.on('rcpt', send_rcpt)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
smtp_client.on('data', () => {
|
|
290
|
+
if (dead_sender()) return
|
|
291
|
+
smtp_client.start_data(txn.message_stream)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
smtp_client.on('dot', () => {
|
|
295
|
+
if (dead_sender() || !txn) return
|
|
296
|
+
|
|
297
|
+
get_rs().add(plugin, { pass: smtp_client.response })
|
|
298
|
+
if (rcpt < txn.rcpt_to.length) {
|
|
299
|
+
smtp_client.send_command('RSET')
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
smtp_client.call_next(OK, smtp_client.response)
|
|
303
|
+
smtp_client.release()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
smtp_client.on('rset', () => {
|
|
307
|
+
if (dead_sender() || !txn) return
|
|
308
|
+
smtp_client.send_command('MAIL', `FROM:${txn.mail_from}`)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
smtp_client.on('bad_code', (code, msg) => {
|
|
312
|
+
if (dead_sender() || !txn) return
|
|
313
|
+
smtp_client.call_next(code && code[0] === '5' ? DENY : DENYSOFT, msg)
|
|
314
|
+
smtp_client.release()
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
exports.get_mx_next_hop = (next_hop) => {
|
|
320
|
+
// queue.wants && queue.next_hop are mechanisms for fine-grained MX routing.
|
|
321
|
+
// Plugins can specify a queue to perform the delivery as well as a route. A
|
|
322
|
+
// plugin that uses this is qmail-deliverable, which can direct email delivery
|
|
323
|
+
// via smtp_forward, outbound (SMTP), and outbound (LMTP).
|
|
324
|
+
const dest = new url.URL(next_hop)
|
|
325
|
+
const mx = {
|
|
326
|
+
priority: 0,
|
|
327
|
+
port: dest.port || (dest.protocol === 'lmtp:' ? 24 : 25),
|
|
328
|
+
exchange: dest.hostname,
|
|
329
|
+
}
|
|
330
|
+
if (dest.protocol === 'lmtp:') mx.using_lmtp = true
|
|
331
|
+
if (dest.username) {
|
|
332
|
+
mx.auth_type = 'plain'
|
|
333
|
+
mx.auth_user = dest.username
|
|
334
|
+
mx.auth_pass = dest.password
|
|
335
|
+
}
|
|
336
|
+
return mx
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
exports.get_mx = function (next, hmail, domain) {
|
|
340
|
+
const qw = hmail.todo.notes.get('queue.wants')
|
|
341
|
+
if (qw && !['smtp_forward', 'outbound'].includes(qw)) return next()
|
|
342
|
+
|
|
343
|
+
if (qw === 'smtp_forward' && hmail.todo.notes.get('queue.next_hop')) {
|
|
344
|
+
return next(OK, this.get_mx_next_hop(hmail.todo.notes.get('queue.next_hop')))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const dom =
|
|
348
|
+
this.cfg.main.domain_selector === 'mail_from' ? hmail.todo.mail_from.host.toLowerCase() : domain.toLowerCase()
|
|
349
|
+
const cfg = this.cfg[dom]
|
|
350
|
+
|
|
351
|
+
if (cfg === undefined) {
|
|
352
|
+
this.logdebug(`using DNS MX for: ${domain}`)
|
|
353
|
+
return next()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const mx_opts = ['auth_type', 'auth_user', 'auth_pass', 'bind', 'bind_helo', 'using_lmtp']
|
|
357
|
+
|
|
358
|
+
const mx = {
|
|
359
|
+
priority: 0,
|
|
360
|
+
exchange: cfg.host || this.cfg.main.host,
|
|
361
|
+
port: cfg.port || this.cfg.main.port || 25,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// apply auth/mx options
|
|
365
|
+
for (const o of mx_opts) {
|
|
366
|
+
if (cfg[o] === undefined) continue
|
|
367
|
+
mx[o] = this.cfg[dom][o]
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
next(OK, mx)
|
|
371
|
+
}
|