haraka 0.0.33 → 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 +7 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1898 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/LICENSE +22 -0
- package/Plugins.md +227 -0
- package/README.md +119 -4
- package/SECURITY.md +178 -0
- package/TODO +22 -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/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/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/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1865 -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 +63 -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/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 +100 -4
- 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 +605 -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 +820 -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 +198 -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/plugins/tls.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// TLS is built into Haraka. This plugin conditionally advertises STARTTLS.
|
|
3
|
+
// see 'haraka -h tls' for help
|
|
4
|
+
|
|
5
|
+
/* global server */
|
|
6
|
+
|
|
7
|
+
const tls_socket = require('../tls_socket')
|
|
8
|
+
|
|
9
|
+
// exported so tests can override config dir
|
|
10
|
+
exports.net_utils = require('haraka-net-utils')
|
|
11
|
+
|
|
12
|
+
exports.register = function () {
|
|
13
|
+
tls_socket.load_tls_ini()
|
|
14
|
+
|
|
15
|
+
if (tls_socket.cfg.redis.disable_for_failed_hosts) this.logdebug('Will disable STARTTLS for failing TLS hosts')
|
|
16
|
+
|
|
17
|
+
this.register_hook('capabilities', 'advertise_starttls')
|
|
18
|
+
this.register_hook('unrecognized_command', 'upgrade_connection')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.shutdown = () => {
|
|
22
|
+
if (tls_socket.shutdown) tls_socket.shutdown()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
exports.advertise_starttls = function (next, connection) {
|
|
26
|
+
// if no TLS setup incomplete/invalid, don't advertise
|
|
27
|
+
if (!tls_socket.tls_valid) {
|
|
28
|
+
this.logerror('no valid TLS config')
|
|
29
|
+
return next()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Caution: do not advertise STARTTLS if already TLS upgraded */
|
|
33
|
+
if (connection.tls.enabled) return next()
|
|
34
|
+
|
|
35
|
+
if (this.net_utils.ip_in_list(tls_socket.cfg.no_tls_hosts, connection.remote.ip)) {
|
|
36
|
+
return next()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function enable_tls() {
|
|
40
|
+
connection.capabilities.push('STARTTLS')
|
|
41
|
+
connection.tls.advertised = true
|
|
42
|
+
next()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// check if local port is excluded from starttls advertisement
|
|
46
|
+
if (tls_socket.cfg.main.no_starttls_ports.includes(connection.local.port)) return next()
|
|
47
|
+
|
|
48
|
+
// exclude port 587 from NO-GO
|
|
49
|
+
if (connection.local.port === 587) return enable_tls()
|
|
50
|
+
|
|
51
|
+
if (!tls_socket.cfg.redis || !server.notes.redis) {
|
|
52
|
+
return enable_tls()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { redis } = server.notes
|
|
56
|
+
const dbkey = `no_tls|${connection.remote.ip}`
|
|
57
|
+
|
|
58
|
+
redis
|
|
59
|
+
.get(dbkey)
|
|
60
|
+
.then((dbr) => {
|
|
61
|
+
if (!dbr) return enable_tls()
|
|
62
|
+
connection.results.add(this, { msg: 'no_tls' })
|
|
63
|
+
next(CONT, 'STARTTLS disabled because previous attempt failed')
|
|
64
|
+
})
|
|
65
|
+
.catch((err) => {
|
|
66
|
+
connection.results.add(this, { err })
|
|
67
|
+
enable_tls()
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
exports.set_notls = function (connection) {
|
|
72
|
+
if (!tls_socket.cfg.redis.disable_for_failed_hosts) return
|
|
73
|
+
if (!server.notes.redis) return
|
|
74
|
+
|
|
75
|
+
const expiry = tls_socket.cfg.redis.disable_inbound_expiry || 3600
|
|
76
|
+
|
|
77
|
+
this.lognotice(connection, `STARTTLS failed. Marking ${connection.remote.ip} as non-TLS host for ${expiry} seconds`)
|
|
78
|
+
|
|
79
|
+
server.notes.redis.setEx(`no_tls|${connection.remote.ip}`, expiry, new Date().toISOString())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
exports.upgrade_connection = function (next, connection, params) {
|
|
83
|
+
if (!connection.tls.advertised) return next()
|
|
84
|
+
|
|
85
|
+
/* Watch for STARTTLS directive from client. */
|
|
86
|
+
if (params[0].toUpperCase() !== 'STARTTLS') return next()
|
|
87
|
+
|
|
88
|
+
// RFC 3207 §4: discard any plaintext the client pipelined after
|
|
89
|
+
// STARTTLS. Otherwise connection.respond() restores state to CMD and
|
|
90
|
+
// re-enters _process_data(), parsing and executing those buffered
|
|
91
|
+
// cleartext commands before the TLS handshake completes (STARTTLS
|
|
92
|
+
// command injection). Mirrors the buffer-nuke already used for
|
|
93
|
+
// SSL-over-plaintext detection in connection.process_line().
|
|
94
|
+
connection.current_data = null
|
|
95
|
+
|
|
96
|
+
/* Respond to STARTTLS command. */
|
|
97
|
+
connection.respond(220, 'Go ahead.')
|
|
98
|
+
|
|
99
|
+
const plugin = this
|
|
100
|
+
let called_next = false
|
|
101
|
+
// adjust plugin.timeout like so: echo '45' > config/tls.timeout
|
|
102
|
+
const timeout = plugin.timeout - 1
|
|
103
|
+
|
|
104
|
+
function nextOnce(disconnected) {
|
|
105
|
+
if (called_next) return
|
|
106
|
+
called_next = true
|
|
107
|
+
clearTimeout(connection.notes.tls_timer)
|
|
108
|
+
if (!disconnected) connection.lognotice(plugin, 'timeout setting up TLS')
|
|
109
|
+
plugin.set_notls(connection)
|
|
110
|
+
return next(DENYSOFTDISCONNECT)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (timeout && timeout > 0) {
|
|
114
|
+
connection.notes.tls_timer = setTimeout(nextOnce, timeout * 1000)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
connection.notes.cleanUpDisconnect = nextOnce
|
|
118
|
+
|
|
119
|
+
/* Upgrade the connection to TLS. */
|
|
120
|
+
connection.client.upgrade((verified, verifyError, cert, cipher) => {
|
|
121
|
+
if (called_next) return
|
|
122
|
+
clearTimeout(connection.notes.tls_timer)
|
|
123
|
+
called_next = true
|
|
124
|
+
connection.reset_transaction(() => {
|
|
125
|
+
connection.setTLS({
|
|
126
|
+
cipher,
|
|
127
|
+
verified,
|
|
128
|
+
verifyError,
|
|
129
|
+
peerCertificate: cert,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
connection.results.add(plugin, connection.tls)
|
|
133
|
+
plugin.emit_upgrade_msg(connection, verified, verifyError, cert, cipher)
|
|
134
|
+
next(OK)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
exports.hook_disconnect = (next, connection) => {
|
|
140
|
+
if (connection.notes.cleanUpDisconnect) {
|
|
141
|
+
connection.notes.cleanUpDisconnect(true)
|
|
142
|
+
}
|
|
143
|
+
next()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
exports.emit_upgrade_msg = function (conn, verified, verifyError, cert, cipher) {
|
|
147
|
+
let msg = 'secured:'
|
|
148
|
+
if (cipher) {
|
|
149
|
+
msg += ` cipher=${cipher.name} version=${cipher.version}`
|
|
150
|
+
}
|
|
151
|
+
msg += ` verified=${verified}`
|
|
152
|
+
if (verifyError) msg += ` error="${verifyError}"`
|
|
153
|
+
if (cert) {
|
|
154
|
+
if (cert.subject) {
|
|
155
|
+
msg += ` cn="${cert.subject.CN}" organization="${cert.subject.O}"`
|
|
156
|
+
}
|
|
157
|
+
if (cert.issuer) msg += ` issuer="${cert.issuer.O}"`
|
|
158
|
+
if (cert.valid_to) msg += ` expires="${cert.valid_to}"`
|
|
159
|
+
if (cert.fingerprint) msg += ` fingerprint=${cert.fingerprint}`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
conn.loginfo(this, msg)
|
|
163
|
+
return msg
|
|
164
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Stop accepting new connections when we are too busy
|
|
2
|
+
|
|
3
|
+
let toobusy
|
|
4
|
+
let was_busy = false
|
|
5
|
+
|
|
6
|
+
exports.register = function () {
|
|
7
|
+
try {
|
|
8
|
+
toobusy = require('toobusy-js')
|
|
9
|
+
} catch (e) {
|
|
10
|
+
this.logerror(e)
|
|
11
|
+
this.logerror("try: 'npm install -g toobusy-js'")
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
this.loadConfig()
|
|
16
|
+
|
|
17
|
+
this.register_hook('connect', 'check_busy', -100)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
exports.loadConfig = function () {
|
|
21
|
+
let maxLag = this.config.get('toobusy.maxlag', 'value', () => {
|
|
22
|
+
this.loadConfig()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
maxLag = parseInt(maxLag)
|
|
26
|
+
if (maxLag) {
|
|
27
|
+
// This will throw an exception on error
|
|
28
|
+
toobusy.maxLag(maxLag)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.check_busy = function (next) {
|
|
33
|
+
if (!toobusy()) {
|
|
34
|
+
was_busy = false
|
|
35
|
+
return next()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!was_busy) {
|
|
39
|
+
was_busy = true
|
|
40
|
+
// Log a CRIT error at the first occurrence
|
|
41
|
+
const currentLag = toobusy.lag()
|
|
42
|
+
const maxLag = toobusy.maxLag()
|
|
43
|
+
this.logcrit(`deferring connections: lag=${currentLag} max=${maxLag}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return next(DENYSOFTDISCONNECT, 'Too busy; please try again later')
|
|
47
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Implementation of XCLIENT protocol
|
|
2
|
+
// See http://www.postfix.org/XCLIENT_README.html
|
|
3
|
+
|
|
4
|
+
const net = require('node:net')
|
|
5
|
+
|
|
6
|
+
const utils = require('haraka-utils')
|
|
7
|
+
const DSN = require('haraka-dsn')
|
|
8
|
+
let allowed_hosts = {}
|
|
9
|
+
|
|
10
|
+
exports.register = function () {
|
|
11
|
+
this.load_xclient_hosts()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.load_xclient_hosts = function () {
|
|
15
|
+
const cfg = this.config.get('xclient.hosts', 'list', () => {
|
|
16
|
+
this.load_xclient_hosts()
|
|
17
|
+
})
|
|
18
|
+
const ah = {}
|
|
19
|
+
for (const i in cfg) {
|
|
20
|
+
ah[cfg[i]] = true
|
|
21
|
+
}
|
|
22
|
+
allowed_hosts = ah
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function xclient_allowed(ip) {
|
|
26
|
+
return !!(ip === '127.0.0.1' || ip === '::1' || allowed_hosts[ip])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.hook_capabilities = (next, connection) => {
|
|
30
|
+
if (xclient_allowed(connection.remote.ip)) {
|
|
31
|
+
connection.capabilities.push('XCLIENT NAME ADDR PROTO HELO LOGIN')
|
|
32
|
+
}
|
|
33
|
+
next()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
37
|
+
if (params[0] !== 'XCLIENT') return next()
|
|
38
|
+
|
|
39
|
+
// XCLIENT is not allowed after transaction start
|
|
40
|
+
if (connection?.transaction) {
|
|
41
|
+
return next(DENY, DSN.proto_unspecified('Mail transaction in progress', 503))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!xclient_allowed(connection?.remote?.ip)) {
|
|
45
|
+
return next(DENY, DSN.proto_unspecified('Not authorized', 550))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If we get here - the client is allowed to use XCLIENT
|
|
49
|
+
// Process arguments
|
|
50
|
+
const args = new String(params[1]).toLowerCase().split(/ /)
|
|
51
|
+
const xclient = {}
|
|
52
|
+
for (const arg of args) {
|
|
53
|
+
const match = /^([^=]+)=([^ ]+)/.exec(arg)
|
|
54
|
+
if (match) {
|
|
55
|
+
connection.logdebug(this, `found key=${match[1]} value=${match[2]}`)
|
|
56
|
+
switch (match[1]) {
|
|
57
|
+
case 'destaddr':
|
|
58
|
+
case 'addr': {
|
|
59
|
+
// IPv6 is prefixed in the XCLIENT protocol
|
|
60
|
+
let ipv6
|
|
61
|
+
if ((ipv6 = /^IPV6:(.+)$/i.exec(match[2]))) {
|
|
62
|
+
// Validate
|
|
63
|
+
if (net.isIPv6(ipv6[1])) {
|
|
64
|
+
xclient[match[1]] = ipv6[1]
|
|
65
|
+
}
|
|
66
|
+
} else if (!/\[UNAVAILABLE\]/i.test(match[2])) {
|
|
67
|
+
// IPv4
|
|
68
|
+
if (net.isIPv4(match[2])) {
|
|
69
|
+
xclient[match[1]] = match[2]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
case 'proto':
|
|
75
|
+
// SMTP or ESMTP
|
|
76
|
+
if (/^e?smtp/i.test(match[2])) {
|
|
77
|
+
xclient[match[1]] = match[2]
|
|
78
|
+
}
|
|
79
|
+
break
|
|
80
|
+
case 'name':
|
|
81
|
+
case 'port':
|
|
82
|
+
case 'helo':
|
|
83
|
+
case 'login':
|
|
84
|
+
case 'destport':
|
|
85
|
+
if (!/\[(UNAVAILABLE|TEMPUNAVAIL)\]/i.test(match[2])) {
|
|
86
|
+
xclient[match[1]] = match[2]
|
|
87
|
+
}
|
|
88
|
+
break
|
|
89
|
+
default:
|
|
90
|
+
connection.logwarn(this, `unknown argument: ${arg}`)
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
connection.logwarn(this, `unknown argument: ${arg}`)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Abort if we don't have a valid IP address
|
|
98
|
+
if (!xclient.addr) {
|
|
99
|
+
return next(DENY, DSN.proto_invalid_cmd_args('No valid IP address found', 501))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Apply changes
|
|
103
|
+
const new_uuid = utils.uuid()
|
|
104
|
+
connection.loginfo(this, `new uuid=${new_uuid}`)
|
|
105
|
+
connection.uuid = new_uuid
|
|
106
|
+
connection.reset_transaction()
|
|
107
|
+
connection.relaying = false
|
|
108
|
+
connection.set('remote.ip', xclient.addr)
|
|
109
|
+
connection.set('remote.host', xclient.name ? xclient.name : undefined)
|
|
110
|
+
connection.set('remote.login', xclient.login ? xclient.login : undefined)
|
|
111
|
+
connection.set('hello.host', xclient.helo ? xclient.helo : undefined)
|
|
112
|
+
connection.set('local.ip', xclient.destaddr ? xclient.destaddr : undefined)
|
|
113
|
+
// parseInt so downstream numeric checks (e.g. the 587/465 auth-required
|
|
114
|
+
// gate in connection.cmd_mail) work; matches the PROXY path.
|
|
115
|
+
connection.set('local.port', xclient.destport ? parseInt(xclient.destport, 10) : undefined)
|
|
116
|
+
if (xclient.proto) {
|
|
117
|
+
connection.set('hello', 'verb', xclient.proto === 'esmtp' ? 'EHLO' : 'HELO')
|
|
118
|
+
}
|
|
119
|
+
connection.esmtp = xclient.proto === 'esmtp'
|
|
120
|
+
connection.xclient = true
|
|
121
|
+
if (!xclient.name) return next(NEXT_HOOK, 'lookup_rdns')
|
|
122
|
+
|
|
123
|
+
next(NEXT_HOOK, 'connect')
|
|
124
|
+
}
|