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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Proxy to an SMTP server
|
|
3
|
+
// Opens the connection to the ongoing SMTP server at MAIL FROM time
|
|
4
|
+
// and passes back any errors seen on the ongoing server to the
|
|
5
|
+
// originating server.
|
|
6
|
+
|
|
7
|
+
const smtp_client_mod = require('../../smtp_client')
|
|
8
|
+
const tls_socket = require('../../tls_socket')
|
|
9
|
+
|
|
10
|
+
exports.register = function () {
|
|
11
|
+
this.load_smtp_proxy_ini()
|
|
12
|
+
|
|
13
|
+
if (this.cfg.main.enable_outbound) {
|
|
14
|
+
this.register_hook('queue_outbound', 'hook_queue')
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.load_smtp_proxy_ini = function () {
|
|
19
|
+
this.cfg = this.config.get(
|
|
20
|
+
'smtp_proxy.ini',
|
|
21
|
+
{
|
|
22
|
+
booleans: [
|
|
23
|
+
'-main.enable_tls',
|
|
24
|
+
'+main.enable_outbound',
|
|
25
|
+
'+tls.requestCert',
|
|
26
|
+
'+tls.honorCipherOrder',
|
|
27
|
+
'-tls.rejectUnauthorized',
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
() => {
|
|
31
|
+
this.load_smtp_proxy_ini()
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
// Build backend TLS options from tls.ini [main] + this plugin's [tls] section.
|
|
36
|
+
// Re-derived on every (re)load so SIGHUP picks up edits.
|
|
37
|
+
this.tls_options = tls_socket.load_plugin_tls_options(this.cfg.tls || {})
|
|
38
|
+
|
|
39
|
+
if (this.cfg.main.enable_outbound) {
|
|
40
|
+
this.lognotice('outbound enabled, will default to disabled in Haraka v3 (see #1472)')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exports.hook_mail = function (next, connection) {
|
|
45
|
+
const c = this.cfg.main
|
|
46
|
+
connection.loginfo(
|
|
47
|
+
this,
|
|
48
|
+
`forwarding to ${c.forwarding_host_pool ? 'configured forwarding_host_pool' : `${c.host}:${c.port}`}`,
|
|
49
|
+
)
|
|
50
|
+
smtp_client_mod.get_client_plugin(this, connection, c, (err, smtp_client) => {
|
|
51
|
+
connection.notes.smtp_client = smtp_client
|
|
52
|
+
smtp_client.next = next
|
|
53
|
+
|
|
54
|
+
smtp_client.on('mail', smtp_client.call_next)
|
|
55
|
+
smtp_client.on('rcpt', smtp_client.call_next)
|
|
56
|
+
smtp_client.on('data', smtp_client.call_next)
|
|
57
|
+
|
|
58
|
+
smtp_client.on('dot', () => {
|
|
59
|
+
if (smtp_client.is_dead_sender(this, connection)) {
|
|
60
|
+
delete connection.notes.smtp_client
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
smtp_client.call_next(OK, smtp_client.response)
|
|
65
|
+
smtp_client.release()
|
|
66
|
+
delete connection.notes.smtp_client
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
smtp_client.on('error', () => {
|
|
70
|
+
delete connection.notes.smtp_client
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
smtp_client.on('bad_code', (code) => {
|
|
74
|
+
smtp_client.call_next(code.match(/^4/) ? DENYSOFT : DENY, smtp_client.response.slice())
|
|
75
|
+
|
|
76
|
+
if (smtp_client.command !== 'rcpt') {
|
|
77
|
+
// errors are OK for rcpt, but nothing else
|
|
78
|
+
// this can also happen if the destination server
|
|
79
|
+
// times out, but that is okay.
|
|
80
|
+
connection.loginfo(this, 'message denied, proxying failed')
|
|
81
|
+
smtp_client.release()
|
|
82
|
+
delete connection.notes.smtp_client
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.hook_rcpt_ok = (next, connection, recipient) => {
|
|
89
|
+
const { smtp_client } = connection.notes
|
|
90
|
+
if (!smtp_client) return next()
|
|
91
|
+
if (smtp_client.is_dead_sender(this, connection)) {
|
|
92
|
+
delete connection.notes.smtp_client
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
smtp_client.next = next
|
|
96
|
+
smtp_client.send_command('RCPT', `TO:${recipient.format(!smtp_client.smtputf8)}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
exports.hook_data = (next, connection) => {
|
|
100
|
+
const { smtp_client } = connection.notes
|
|
101
|
+
if (!smtp_client) return next()
|
|
102
|
+
|
|
103
|
+
if (smtp_client.is_dead_sender(this, connection)) {
|
|
104
|
+
delete connection.notes.smtp_client
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
smtp_client.next = next
|
|
108
|
+
smtp_client.send_command('DATA')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
exports.hook_queue = function (next, connection) {
|
|
112
|
+
if (!connection?.transaction || !connection?.notes) return next()
|
|
113
|
+
|
|
114
|
+
const { smtp_client } = connection.notes
|
|
115
|
+
if (!smtp_client) return next()
|
|
116
|
+
|
|
117
|
+
if (smtp_client.is_dead_sender(this, connection)) {
|
|
118
|
+
delete connection.notes.smtp_client
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
smtp_client.next = next
|
|
122
|
+
smtp_client.start_data(connection.transaction.message_stream)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
exports.hook_rset = (next, connection) => {
|
|
126
|
+
const { smtp_client } = connection.notes
|
|
127
|
+
if (!smtp_client) return next()
|
|
128
|
+
smtp_client.release()
|
|
129
|
+
delete connection.notes.smtp_client
|
|
130
|
+
next()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
exports.hook_quit = exports.hook_rset
|
|
134
|
+
|
|
135
|
+
exports.hook_disconnect = (next, connection) => {
|
|
136
|
+
const { smtp_client } = connection.notes
|
|
137
|
+
if (!smtp_client) return next()
|
|
138
|
+
smtp_client.release()
|
|
139
|
+
delete connection.notes.smtp_client
|
|
140
|
+
smtp_client.call_next()
|
|
141
|
+
next()
|
|
142
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const os = require('node:os')
|
|
3
|
+
|
|
4
|
+
const tempDir = os.tmpdir()
|
|
5
|
+
|
|
6
|
+
exports.hook_queue = function (next, connection) {
|
|
7
|
+
const txn = connection?.transaction
|
|
8
|
+
if (!txn) return next()
|
|
9
|
+
|
|
10
|
+
const file_path = `${tempDir}/mail_${txn.uuid}.eml`
|
|
11
|
+
const ws = fs.createWriteStream(file_path)
|
|
12
|
+
connection.logdebug(this, `Saving to ${file_path}`)
|
|
13
|
+
ws.once('close', () => next(OK))
|
|
14
|
+
connection.transaction.message_stream.pipe(ws)
|
|
15
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Base class for plugins that use config/host_list
|
|
3
|
+
|
|
4
|
+
exports.load_host_list = function () {
|
|
5
|
+
const lowered_list = {} // assemble
|
|
6
|
+
const raw_list = this.config.get('host_list', 'list', () => {
|
|
7
|
+
this.load_host_list()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
for (const i in raw_list) {
|
|
11
|
+
lowered_list[raw_list[i].toLowerCase()] = true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.host_list = lowered_list
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.load_host_list_regex = function () {
|
|
18
|
+
this.host_list_regex = this.config.get('host_list_regex', 'list', () => {
|
|
19
|
+
this.load_host_list_regex()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
this.hl_re = new RegExp(`^(?:${this.host_list_regex.join('|')})$`, 'i')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
exports.hook_mail = function (next, connection, params) {
|
|
26
|
+
const txn = connection?.transaction
|
|
27
|
+
if (!txn) return
|
|
28
|
+
|
|
29
|
+
const email = params[0].address
|
|
30
|
+
if (!email) {
|
|
31
|
+
txn.results.add(this, { skip: 'mail_from.null', emit: true })
|
|
32
|
+
return next()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const domain = params[0].host.toLowerCase()
|
|
36
|
+
|
|
37
|
+
const anti_spoof = this.config.get('host_list.anti_spoof') || false
|
|
38
|
+
|
|
39
|
+
if (this.in_host_list(domain, connection) || this.in_host_regex(domain, connection)) {
|
|
40
|
+
if (anti_spoof && !connection.relaying) {
|
|
41
|
+
txn.results.add(this, { fail: 'mail_from.anti_spoof' })
|
|
42
|
+
return next(DENY, `Mail from domain '${domain}' is not allowed from your host`)
|
|
43
|
+
}
|
|
44
|
+
txn.results.add(this, { pass: 'mail_from' })
|
|
45
|
+
txn.notes.local_sender = true
|
|
46
|
+
return next()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
txn.results.add(this, { msg: 'mail_from!local' })
|
|
50
|
+
return next()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.in_host_list = function (domain, connection) {
|
|
54
|
+
this.logdebug(connection, `checking ${domain} in config/host_list`)
|
|
55
|
+
return !!this.host_list[domain]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.in_host_regex = function (domain, connection) {
|
|
59
|
+
if (!this.host_list_regex) return false
|
|
60
|
+
if (!this.host_list_regex.length) return false
|
|
61
|
+
|
|
62
|
+
this.logdebug(connection, `checking ${domain} against config/host_list_regex `)
|
|
63
|
+
|
|
64
|
+
return !!this.hl_re.test(domain)
|
|
65
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Check RCPT TO domain is in config/host_list
|
|
3
|
+
|
|
4
|
+
// Previous versions of this plugin (Haraka <= 2.4.0) did not account for
|
|
5
|
+
// relaying users. This plugin now permits relaying clients to send if
|
|
6
|
+
// the message is destined to or originating from a local domain.
|
|
7
|
+
//
|
|
8
|
+
// The mail hook always checks the MAIL FROM address and when detected, sets
|
|
9
|
+
// connection.transaction.notes.local_sender=true. During RCPT TO, if relaying
|
|
10
|
+
// is enabled and the sending domain is local, the receipt is OK.
|
|
11
|
+
|
|
12
|
+
exports.register = function () {
|
|
13
|
+
this.inherits('rcpt_to.host_list_base')
|
|
14
|
+
|
|
15
|
+
this.load_host_list()
|
|
16
|
+
this.load_host_list_regex()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.hook_rcpt = function (next, connection, params) {
|
|
20
|
+
const txn = connection?.transaction
|
|
21
|
+
if (!txn) return
|
|
22
|
+
|
|
23
|
+
const rcpt = params[0]
|
|
24
|
+
|
|
25
|
+
// Check for RCPT TO without an @ first - ignore those here
|
|
26
|
+
if (!rcpt.host) {
|
|
27
|
+
txn.results.add(this, { fail: 'rcpt!domain' })
|
|
28
|
+
return next()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
connection.logdebug(this, `Checking if ${rcpt} host is in host_list`)
|
|
32
|
+
|
|
33
|
+
const domain = rcpt.host.toLowerCase()
|
|
34
|
+
|
|
35
|
+
if (this.in_host_list(domain, connection)) {
|
|
36
|
+
txn.results.add(this, { pass: 'rcpt_to' })
|
|
37
|
+
return next(OK)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (this.in_host_regex(domain, connection)) {
|
|
41
|
+
txn.results.add(this, { pass: 'rcpt_to' })
|
|
42
|
+
return next(OK)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// in this case, a client with relaying privileges is sending FROM a local
|
|
46
|
+
// domain. For them, any RCPT address is accepted.
|
|
47
|
+
if (connection.relaying && txn.notes.local_sender) {
|
|
48
|
+
txn.results.add(this, { pass: 'relaying local_sender' })
|
|
49
|
+
return next(OK)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// the MAIL FROM domain is not local and neither is the RCPT TO
|
|
53
|
+
// Another RCPT plugin may yet vouch for this recipient.
|
|
54
|
+
txn.results.add(this, { msg: 'rcpt!local' })
|
|
55
|
+
return next()
|
|
56
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// record_envelope_addresses
|
|
2
|
+
|
|
3
|
+
// documentation via: haraka -h plugins/record_envelope_addresses
|
|
4
|
+
|
|
5
|
+
exports.hook_rcpt = (next, connection, params) => {
|
|
6
|
+
if (connection?.transaction) {
|
|
7
|
+
connection.transaction.add_header('X-Envelope-To', params[0].address)
|
|
8
|
+
}
|
|
9
|
+
next()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.hook_mail = (next, connection, params) => {
|
|
13
|
+
if (connection?.transaction) {
|
|
14
|
+
connection.transaction.add_header('X-Envelope-From', params[0].address)
|
|
15
|
+
}
|
|
16
|
+
next()
|
|
17
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* global server */
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
|
|
7
|
+
exports.register = function () {
|
|
8
|
+
this.outbound = require('../outbound')
|
|
9
|
+
this.queue_dir = require('../outbound/queue').queue_dir
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.hook_capabilities = (next, connection) => {
|
|
13
|
+
if (connection.remote.is_local) {
|
|
14
|
+
connection.capabilities.push('STATUS')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
next()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
21
|
+
if (params[0] !== 'STATUS') return next()
|
|
22
|
+
if (!connection.remote.is_local) return next(DENY, 'STATUS not allowed remotely')
|
|
23
|
+
|
|
24
|
+
this.run(params[1], (err, result) => {
|
|
25
|
+
if (err) return next(DENY, err.message)
|
|
26
|
+
|
|
27
|
+
connection.respond(211, result ? JSON.stringify(result) : 'null', () => next(OK))
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
exports.run = function (cmd, cb) {
|
|
32
|
+
if (server.cluster && !/^QUEUE LIST/.test(cmd)) {
|
|
33
|
+
this.call_master(cmd, cb)
|
|
34
|
+
} else {
|
|
35
|
+
this.command_action(cmd, cb)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exports.command_action = function (cmd, cb) {
|
|
40
|
+
const params = cmd.split(' ')
|
|
41
|
+
|
|
42
|
+
switch (params.shift()) {
|
|
43
|
+
case 'POOL':
|
|
44
|
+
return this.pool_action(params, cb)
|
|
45
|
+
case 'QUEUE':
|
|
46
|
+
return this.queue_action(params, cb)
|
|
47
|
+
default:
|
|
48
|
+
cb('unknown STATUS command')
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
exports.pool_action = function (params, cb) {
|
|
53
|
+
switch (params.shift()) {
|
|
54
|
+
case 'LIST':
|
|
55
|
+
return this.pool_list(cb)
|
|
56
|
+
default:
|
|
57
|
+
cb('unknown POOL command')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.queue_action = function (params, cb) {
|
|
62
|
+
switch (params.shift()) {
|
|
63
|
+
case 'LIST':
|
|
64
|
+
return this.queue_list(cb)
|
|
65
|
+
case 'STATS':
|
|
66
|
+
return this.queue_stats(cb)
|
|
67
|
+
case 'INSPECT':
|
|
68
|
+
return this.queue_inspect(cb)
|
|
69
|
+
case 'DISCARD':
|
|
70
|
+
return this.queue_discard(params.shift(), cb)
|
|
71
|
+
case 'PUSH':
|
|
72
|
+
return this.queue_push(params.shift(), cb)
|
|
73
|
+
default:
|
|
74
|
+
cb('unknown QUEUE command')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
exports.pool_list = (cb) => {
|
|
79
|
+
const result = {}
|
|
80
|
+
|
|
81
|
+
if (server.notes.pool) {
|
|
82
|
+
for (const name of Object.keys(server.notes.pool)) {
|
|
83
|
+
const instance = server.notes.pool[name]
|
|
84
|
+
|
|
85
|
+
result[name] = {
|
|
86
|
+
inUse: instance.inUseObjectsCount(),
|
|
87
|
+
size: instance.getPoolSize(),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
cb(null, result)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exports.queue_list = async function (cb) {
|
|
96
|
+
try {
|
|
97
|
+
const qlist = await this.outbound.list_queue()
|
|
98
|
+
const result = []
|
|
99
|
+
|
|
100
|
+
for (const todo of qlist) {
|
|
101
|
+
result.push({
|
|
102
|
+
file: todo.file,
|
|
103
|
+
uuid: todo.uuid,
|
|
104
|
+
queue_time: todo.queue_time,
|
|
105
|
+
domain: todo.domain,
|
|
106
|
+
from: todo.mail_from.toString(),
|
|
107
|
+
to: todo.rcpt_to.map((r) => r.toString()),
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
cb(null, result)
|
|
112
|
+
} catch (err) {
|
|
113
|
+
cb(err)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
exports.queue_stats = function (cb) {
|
|
118
|
+
cb(null, this.outbound.get_stats())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
exports.queue_inspect = function (cb) {
|
|
122
|
+
const delivery_queue_items = this.outbound.delivery_queue.tasks
|
|
123
|
+
const fail_queue_items = this.outbound.temp_fail_queue.queue
|
|
124
|
+
|
|
125
|
+
cb(null, {
|
|
126
|
+
delivery_queue: delivery_queue_items.map((hmail) => ({
|
|
127
|
+
id: hmail.file,
|
|
128
|
+
})),
|
|
129
|
+
temp_fail_queue: fail_queue_items.map((tqtimer) => ({
|
|
130
|
+
id: tqtimer.id,
|
|
131
|
+
fire_time: tqtimer.fire_time,
|
|
132
|
+
})),
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
exports.queue_discard = function (file, cb) {
|
|
137
|
+
try {
|
|
138
|
+
this.outbound.temp_fail_queue.discard(file)
|
|
139
|
+
} catch {
|
|
140
|
+
// ignore not found error
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fs.unlink(path.join(this.queue_dir || '', file), () => {
|
|
144
|
+
cb(null, 'OK')
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
exports.queue_push = function (file, cb) {
|
|
149
|
+
const { queue } = this.outbound.temp_fail_queue
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < queue.length; i++) {
|
|
152
|
+
if (queue[i].id !== file) continue
|
|
153
|
+
|
|
154
|
+
const item = queue.splice(i, 1)[0]
|
|
155
|
+
item.cb()
|
|
156
|
+
|
|
157
|
+
break
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
cb(null, 'OK')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// cluster IPC
|
|
164
|
+
|
|
165
|
+
exports.hook_init_master = function (next) {
|
|
166
|
+
const plugin = this
|
|
167
|
+
|
|
168
|
+
if (!server.cluster) return next()
|
|
169
|
+
|
|
170
|
+
function message_handler(sender, msg) {
|
|
171
|
+
if (msg.event !== 'status.request') return
|
|
172
|
+
|
|
173
|
+
plugin.call_workers(msg, (response) => {
|
|
174
|
+
const valid = response.filter((el) => el != null)
|
|
175
|
+
msg.result = plugin.merge_worker_responses(msg.params, valid)
|
|
176
|
+
msg.event = 'status.result'
|
|
177
|
+
sender.send(msg)
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
server.cluster.on('message', message_handler)
|
|
182
|
+
next()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
exports.hook_init_child = function (next) {
|
|
186
|
+
const self = this
|
|
187
|
+
|
|
188
|
+
function message_handler(msg) {
|
|
189
|
+
if (msg.event !== 'status.request') return
|
|
190
|
+
|
|
191
|
+
self.command_action(msg.params, (err, result) => {
|
|
192
|
+
msg.event = 'status.response'
|
|
193
|
+
msg.result = result
|
|
194
|
+
process.send(msg)
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
process.on('message', message_handler)
|
|
199
|
+
next()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
exports.call_master = (cmd, cb) => {
|
|
203
|
+
function message_handler(msg) {
|
|
204
|
+
if (msg.event !== 'status.result') return
|
|
205
|
+
|
|
206
|
+
process.removeListener('message', message_handler)
|
|
207
|
+
cb(null, msg.result)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
process.on('message', message_handler)
|
|
211
|
+
process.send({ event: 'status.request', params: cmd })
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
exports.call_workers = function (cmd, cb) {
|
|
215
|
+
Promise.allSettled(Object.values(server.cluster.workers).map((w) => this.call_worker(w, cmd))).then((r) => {
|
|
216
|
+
cb(r.filter((s) => s.status === 'fulfilled').map((s) => s.value))
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Merge per-worker responses into a single result matching non-cluster output shape.
|
|
221
|
+
exports.merge_worker_responses = (params, results) => {
|
|
222
|
+
const cmd = params.trim().split(/\s+/).slice(0, 2).join(' ').toUpperCase()
|
|
223
|
+
|
|
224
|
+
switch (cmd) {
|
|
225
|
+
case 'POOL LIST': {
|
|
226
|
+
return Object.assign({}, ...results)
|
|
227
|
+
}
|
|
228
|
+
case 'QUEUE INSPECT': {
|
|
229
|
+
const merged = { delivery_queue: [], temp_fail_queue: [] }
|
|
230
|
+
for (const r of results) {
|
|
231
|
+
if (!r) continue
|
|
232
|
+
if (Array.isArray(r.delivery_queue)) merged.delivery_queue.push(...r.delivery_queue)
|
|
233
|
+
if (Array.isArray(r.temp_fail_queue)) merged.temp_fail_queue.push(...r.temp_fail_queue)
|
|
234
|
+
}
|
|
235
|
+
return merged
|
|
236
|
+
}
|
|
237
|
+
case 'QUEUE STATS': {
|
|
238
|
+
const totals = [0, 0, 0]
|
|
239
|
+
for (const r of results) {
|
|
240
|
+
if (!r) continue
|
|
241
|
+
const parts = String(r).split('/')
|
|
242
|
+
for (let i = 0; i < 3; i++) totals[i] += parseInt(parts[i] ?? 0, 10)
|
|
243
|
+
}
|
|
244
|
+
return totals.join('/')
|
|
245
|
+
}
|
|
246
|
+
default:
|
|
247
|
+
return results
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// sends command to worker and then wait for response or timeout
|
|
252
|
+
exports.call_worker = (worker, cmd) => {
|
|
253
|
+
return new Promise((resolve) => {
|
|
254
|
+
let timeout
|
|
255
|
+
|
|
256
|
+
function message_handler(sender, msg) {
|
|
257
|
+
if (sender.id !== worker.id) return
|
|
258
|
+
if (msg.event !== 'status.response') return
|
|
259
|
+
|
|
260
|
+
clearTimeout(timeout)
|
|
261
|
+
server.cluster.removeListener('message', message_handler)
|
|
262
|
+
|
|
263
|
+
resolve(msg.result)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
timeout = setTimeout(() => {
|
|
267
|
+
server.cluster.removeListener('message', message_handler)
|
|
268
|
+
resolve()
|
|
269
|
+
}, 1000)
|
|
270
|
+
|
|
271
|
+
server.cluster.on('message', message_handler)
|
|
272
|
+
worker.send(cmd)
|
|
273
|
+
})
|
|
274
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// tarpit
|
|
2
|
+
|
|
3
|
+
let hooks_to_delay = [
|
|
4
|
+
'connect',
|
|
5
|
+
'helo',
|
|
6
|
+
'ehlo',
|
|
7
|
+
'mail',
|
|
8
|
+
'rcpt',
|
|
9
|
+
'rcpt_ok',
|
|
10
|
+
'data',
|
|
11
|
+
'data_post',
|
|
12
|
+
'queue',
|
|
13
|
+
'unrecognized_command',
|
|
14
|
+
'vrfy',
|
|
15
|
+
'noop',
|
|
16
|
+
'rset',
|
|
17
|
+
'quit',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
exports.register = function () {
|
|
21
|
+
// Register tarpit function last
|
|
22
|
+
|
|
23
|
+
const cfg = this.config.get('tarpit.ini')
|
|
24
|
+
if (cfg?.main.hooks_to_delay) {
|
|
25
|
+
hooks_to_delay = cfg.main.hooks_to_delay.split(/[\s,;]+/)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const hook of hooks_to_delay) {
|
|
29
|
+
this.register_hook(hook, 'tarpit')
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
exports.tarpit = function (next, connection) {
|
|
34
|
+
const { transaction } = connection
|
|
35
|
+
if (!transaction) return next()
|
|
36
|
+
|
|
37
|
+
let delay = connection.notes.tarpit
|
|
38
|
+
|
|
39
|
+
if (!delay) delay = transaction.notes.tarpit
|
|
40
|
+
|
|
41
|
+
if (!delay) return next()
|
|
42
|
+
|
|
43
|
+
connection.loginfo(this, `tarpitting response for ${delay}s`)
|
|
44
|
+
setTimeout(() => next(), delay * 1000)
|
|
45
|
+
}
|