haraka 0.0.33 → 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 +1894 -0
- package/CLAUDE.md +40 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/GEMINI.md +38 -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/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 +99 -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 +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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Auth against a flat file
|
|
2
|
+
|
|
3
|
+
exports.register = function () {
|
|
4
|
+
this.inherits('auth/auth_base')
|
|
5
|
+
this.load_flat_ini()
|
|
6
|
+
|
|
7
|
+
if (this.cfg.core.constrain_sender) {
|
|
8
|
+
this.register_hook('mail', 'constrain_sender')
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.load_flat_ini = function () {
|
|
13
|
+
this.cfg = this.config.get(
|
|
14
|
+
'auth_flat_file.ini',
|
|
15
|
+
{
|
|
16
|
+
booleans: ['+core.constrain_sender'],
|
|
17
|
+
},
|
|
18
|
+
() => {
|
|
19
|
+
this.load_flat_ini()
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if (this.cfg.users === undefined) this.cfg.users = {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
exports.hook_capabilities = function (next, connection) {
|
|
27
|
+
if (!connection.remote.is_private && !connection.tls.enabled) {
|
|
28
|
+
connection.logdebug(this, 'Auth disabled for insecure public connection')
|
|
29
|
+
return next()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const methods = this.cfg.core?.methods ? this.cfg.core.methods.split(',') : null
|
|
33
|
+
if (methods && methods.length > 0) {
|
|
34
|
+
connection.capabilities.push(`AUTH ${methods.join(' ')}`)
|
|
35
|
+
connection.notes.allowed_auth_methods = methods
|
|
36
|
+
}
|
|
37
|
+
next()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.get_plain_passwd = function (user, connection, cb) {
|
|
41
|
+
if (this.cfg.users[user]) return cb(this.cfg.users[user].toString())
|
|
42
|
+
|
|
43
|
+
cb()
|
|
44
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Plugin which registers mail received to a certain address
|
|
2
|
+
// and extracts a From: address from the mail and puts that address
|
|
3
|
+
// in the mail_from.blocklist file. You need to be running the
|
|
4
|
+
// mail_from.blocklist plugin for this to work fully.
|
|
5
|
+
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const path = require('node:path')
|
|
8
|
+
const utils = require('haraka-utils')
|
|
9
|
+
|
|
10
|
+
exports.hook_data = (next, connection) => {
|
|
11
|
+
// enable mail body parsing
|
|
12
|
+
connection.transaction.parse_body = true
|
|
13
|
+
next()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
exports.hook_data_post = function (next, connection) {
|
|
17
|
+
if (!connection?.relaying || !connection?.transaction) return next()
|
|
18
|
+
|
|
19
|
+
const recip = (this.config.get('block_me.recipient') || '').toLowerCase()
|
|
20
|
+
const senders = this.config.get('block_me.senders', 'list')
|
|
21
|
+
|
|
22
|
+
// Make sure only 1 recipient
|
|
23
|
+
if (connection.transaction.rcpt_to.length != 1) {
|
|
24
|
+
return next()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check recipient is the right one
|
|
28
|
+
if (connection.transaction.rcpt_to[0].address.toLowerCase() != recip) {
|
|
29
|
+
return next()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check sender is in list
|
|
33
|
+
const sender = connection.transaction.mail_from.address
|
|
34
|
+
if (!utils.in_array(sender, senders)) {
|
|
35
|
+
return next(DENY, `You are not allowed to block mail, ${sender}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Now extract the "From" from the body...
|
|
39
|
+
const to_block = extract_from_line(connection.transaction.body)
|
|
40
|
+
if (!to_block) {
|
|
41
|
+
connection.logerror(this, 'No sender found in email')
|
|
42
|
+
return next()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
connection.loginfo(this, `Blocking new sender: ${to_block}`)
|
|
46
|
+
|
|
47
|
+
connection.transaction.notes.block_me = 1
|
|
48
|
+
|
|
49
|
+
// add to mail_from.blocklist, in the same config dir the plugin reads from
|
|
50
|
+
const blocklist = path.join(this.config.root_path, 'mail_from.blocklist')
|
|
51
|
+
fs.open(blocklist, 'a', (err, fd) => {
|
|
52
|
+
if (err) {
|
|
53
|
+
connection.logerror(this, `Unable to append to mail_from.blocklist: ${err}`)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
fs.write(fd, `${to_block}\n`, null, 'UTF-8', () => {
|
|
57
|
+
fs.close(fd)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
next()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exports.hook_queue = (next, connection) => {
|
|
65
|
+
if (connection.transaction.notes.block_me) {
|
|
66
|
+
// pretend we queued this mail
|
|
67
|
+
return next(OK)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
next()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Example: From: Site Tucano Gold <contato@tucanogold.com.br>
|
|
74
|
+
function extract_from_line(body) {
|
|
75
|
+
const matches = body.bodytext.match(/\bFrom:[^<\n]*<([^>\n]*)>/)
|
|
76
|
+
if (matches) {
|
|
77
|
+
return matches[1]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (let i = 0, l = body.children.length; i < l; i++) {
|
|
81
|
+
const from = extract_from_line(body.children[i])
|
|
82
|
+
if (from) {
|
|
83
|
+
return from
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Simple string signatures
|
|
3
|
+
|
|
4
|
+
exports.hook_data = (next, connection) => {
|
|
5
|
+
// enable mail body parsing
|
|
6
|
+
if (connection?.transaction) connection.transaction.parse_body = true
|
|
7
|
+
next()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
exports.hook_data_post = function (next, connection) {
|
|
11
|
+
if (!connection?.transaction) return next()
|
|
12
|
+
|
|
13
|
+
const sigs = this.config.get('data.signatures', 'list')
|
|
14
|
+
|
|
15
|
+
if (check_sigs(sigs, connection.transaction.body)) {
|
|
16
|
+
return next(DENY, 'Mail matches a known spam signature')
|
|
17
|
+
}
|
|
18
|
+
next()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function check_sigs(sigs, body) {
|
|
22
|
+
for (let i = 0, l = sigs.length; i < l; i++) {
|
|
23
|
+
if (body.bodytext.includes(sigs[i])) return 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (let i = 0, l = body.children.length; i < l; i++) {
|
|
27
|
+
if (check_sigs(sigs, body.children[i])) return 1
|
|
28
|
+
}
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** delay_deny
|
|
3
|
+
**
|
|
4
|
+
** This plugin delays all pre-DATA 'deny' results until the recipients are sent
|
|
5
|
+
** and all post-DATA commands until all hook_data_post plugins have run.
|
|
6
|
+
** This allows relays and authenticated users to bypass pre-DATA rejections.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
exports.hook_deny = function (next, connection, params) {
|
|
10
|
+
/* params
|
|
11
|
+
** [0] = plugin return value (DENY or DENYSOFT)
|
|
12
|
+
** [1] = plugin return message
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const pi_name = params[2]
|
|
16
|
+
const pi_function = params[3]
|
|
17
|
+
// var pi_params = params[4];
|
|
18
|
+
const pi_hook = params[5]
|
|
19
|
+
|
|
20
|
+
const { transaction } = connection
|
|
21
|
+
|
|
22
|
+
// Don't delay ourselves...
|
|
23
|
+
if (pi_name == 'delay_deny') return next()
|
|
24
|
+
|
|
25
|
+
// Load config
|
|
26
|
+
const cfg = this.config.get('delay_deny.ini')
|
|
27
|
+
let skip
|
|
28
|
+
let included
|
|
29
|
+
if (cfg.main.included_plugins) {
|
|
30
|
+
included = cfg.main.included_plugins.split(/[;, ]+/)
|
|
31
|
+
} else if (cfg.main.excluded_plugins) {
|
|
32
|
+
skip = cfg.main.excluded_plugins.split(/[;, ]+/)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 'included' mode: only delay deny plugins in the included list
|
|
36
|
+
if (included?.length) {
|
|
37
|
+
if (
|
|
38
|
+
!included.includes(pi_name) &&
|
|
39
|
+
!included.includes(`${pi_name}:${pi_hook}`) &&
|
|
40
|
+
!included.includes(`${pi_name}:${pi_hook}:${pi_function}`)
|
|
41
|
+
) {
|
|
42
|
+
return next()
|
|
43
|
+
}
|
|
44
|
+
} else if (skip?.length) {
|
|
45
|
+
// 'excluded' mode: delay deny everything except in skip list
|
|
46
|
+
// Skip by <plugin name>
|
|
47
|
+
if (skip.includes(pi_name)) {
|
|
48
|
+
connection.logdebug(this, `not delaying excluded plugin: ${pi_name}`)
|
|
49
|
+
return next()
|
|
50
|
+
}
|
|
51
|
+
// Skip by <plugin name>:<hook>
|
|
52
|
+
if (skip.includes(`${pi_name}:${pi_hook}`)) {
|
|
53
|
+
connection.logdebug(this, `not delaying excluded hook: ${pi_hook} in plugin: ${pi_name}`)
|
|
54
|
+
return next()
|
|
55
|
+
}
|
|
56
|
+
// Skip by <plugin name>:<hook>:<function name>
|
|
57
|
+
if (skip.includes(`${pi_name}:${pi_hook}:${pi_function}`)) {
|
|
58
|
+
connection.logdebug(
|
|
59
|
+
this,
|
|
60
|
+
`not delaying excluded function: ${pi_function} on hook: ${pi_hook} in plugin: ${pi_name}`,
|
|
61
|
+
)
|
|
62
|
+
return next()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
switch (pi_hook) {
|
|
67
|
+
// Pre-DATA connection delays
|
|
68
|
+
case 'lookup_rdns':
|
|
69
|
+
case 'connect':
|
|
70
|
+
case 'ehlo':
|
|
71
|
+
case 'helo':
|
|
72
|
+
if (!connection.notes.delay_deny_pre) {
|
|
73
|
+
connection.notes.delay_deny_pre = []
|
|
74
|
+
}
|
|
75
|
+
connection.notes.delay_deny_pre.push(params)
|
|
76
|
+
if (!connection.notes.delay_deny_pre_fail) {
|
|
77
|
+
connection.notes.delay_deny_pre_fail = {}
|
|
78
|
+
}
|
|
79
|
+
connection.notes.delay_deny_pre_fail[pi_name] = 1
|
|
80
|
+
return next(OK)
|
|
81
|
+
// Pre-DATA transaction delays
|
|
82
|
+
case 'mail':
|
|
83
|
+
case 'rcpt':
|
|
84
|
+
case 'rcpt_ok':
|
|
85
|
+
if (!transaction.notes.delay_deny_pre) {
|
|
86
|
+
transaction.notes.delay_deny_pre = []
|
|
87
|
+
}
|
|
88
|
+
transaction.notes.delay_deny_pre.push(params)
|
|
89
|
+
if (!transaction.notes.delay_deny_pre_fail) {
|
|
90
|
+
transaction.notes.delay_deny_pre_fail = {}
|
|
91
|
+
}
|
|
92
|
+
transaction.notes.delay_deny_pre_fail[pi_name] = 1
|
|
93
|
+
return next(OK)
|
|
94
|
+
// Post-DATA delays
|
|
95
|
+
case 'data':
|
|
96
|
+
case 'data_post':
|
|
97
|
+
// fall through
|
|
98
|
+
default:
|
|
99
|
+
// No delays
|
|
100
|
+
next()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exports.hook_rcpt_ok = function (next, connection) {
|
|
105
|
+
const transaction = connection?.transaction
|
|
106
|
+
if (!transaction) return next()
|
|
107
|
+
|
|
108
|
+
// Bypass all pre-DATA deny for AUTH/RELAY
|
|
109
|
+
if (connection.relaying) {
|
|
110
|
+
connection.loginfo(this, 'bypassing all pre-DATA deny: AUTH/RELAY')
|
|
111
|
+
return next()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Apply any delayed rejections
|
|
115
|
+
// Check connection level pre-DATA rejections first
|
|
116
|
+
if (connection.notes?.delay_deny_pre) {
|
|
117
|
+
for (const params of connection.notes.delay_deny_pre) {
|
|
118
|
+
return next(params[0], params[1])
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Then check transaction level pre-DATA
|
|
123
|
+
if (transaction.notes?.delay_deny_pre) {
|
|
124
|
+
for (let i = 0; i < transaction.notes.delay_deny_pre.length; i++) {
|
|
125
|
+
const params = transaction.notes.delay_deny_pre[i]
|
|
126
|
+
|
|
127
|
+
// Remove rejection from the array if it was on the rcpt hooks
|
|
128
|
+
if (params[5] === 'rcpt' || params[5] === 'rcpt_ok') {
|
|
129
|
+
transaction.notes.delay_deny_pre.splice(i, 1)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return next(params[0], params[1])
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
next()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
exports.hook_data = (next, connection) => {
|
|
139
|
+
const transaction = connection?.transaction
|
|
140
|
+
if (!transaction) return next()
|
|
141
|
+
|
|
142
|
+
// Add a header showing all pre-DATA rejections
|
|
143
|
+
const fails = []
|
|
144
|
+
if (connection.notes?.delay_deny_pre_fail) {
|
|
145
|
+
fails.push.apply(Object.keys(connection.notes.delay_deny_pre_fail))
|
|
146
|
+
}
|
|
147
|
+
if (transaction.notes?.delay_deny_pre_fail) {
|
|
148
|
+
fails.push.apply(Object.keys(transaction.notes.delay_deny_pre_fail))
|
|
149
|
+
}
|
|
150
|
+
if (fails.length) transaction.add_header('X-Haraka-Fail-Pre', fails.join(' '))
|
|
151
|
+
|
|
152
|
+
next()
|
|
153
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Prevent a user from sending their AUTH credentials
|
|
2
|
+
// This is a simple, primitive form of anti-phishing.
|
|
3
|
+
|
|
4
|
+
function escapeRegExp(str) {
|
|
5
|
+
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
exports.hook_data = (next, connection) => {
|
|
9
|
+
const { notes, transaction } = connection ?? {}
|
|
10
|
+
|
|
11
|
+
if (transaction && notes?.auth_user && notes.auth_passwd) {
|
|
12
|
+
transaction.parse_body = true
|
|
13
|
+
}
|
|
14
|
+
next()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.hook_data_post = (next, connection) => {
|
|
18
|
+
if (!(connection.notes.auth_user && connection.notes.auth_passwd)) {
|
|
19
|
+
return next()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let user = connection.notes.auth_user
|
|
23
|
+
let domain
|
|
24
|
+
const idx = user.indexOf('@')
|
|
25
|
+
if (idx > 0) {
|
|
26
|
+
// If the username is qualified (e.g. user@domain.com)
|
|
27
|
+
// then we make the @domain.com part optional in the regexp.
|
|
28
|
+
// (idx === -1 is "no @"; idx === 0 is a leading @ — neither is
|
|
29
|
+
// a qualified user@domain, so don't split.)
|
|
30
|
+
domain = user.substring(idx)
|
|
31
|
+
user = user.substring(0, idx)
|
|
32
|
+
}
|
|
33
|
+
const passwd = connection.notes.auth_passwd
|
|
34
|
+
const bound_regexp = '(?:\\b|\\B)'
|
|
35
|
+
const passwd_regexp = new RegExp(bound_regexp + escapeRegExp(passwd) + bound_regexp, 'm')
|
|
36
|
+
const user_regexp = new RegExp(
|
|
37
|
+
bound_regexp + escapeRegExp(user) + (domain ? `(?:${escapeRegExp(domain)})?` : '') + bound_regexp,
|
|
38
|
+
'im',
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if (look_for_credentials(user_regexp, passwd_regexp, connection?.transaction?.body)) {
|
|
42
|
+
return next(DENY, 'Credential leak detected: never give out your username/password to anyone!')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
next()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function look_for_credentials(user_regexp, passwd_regexp, body) {
|
|
49
|
+
if (user_regexp.test(body.bodytext) && passwd_regexp.test(body.bodytext)) {
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check all child parts
|
|
54
|
+
for (let i = 0, l = body.children.length; i < l; i++) {
|
|
55
|
+
if (look_for_credentials(user_regexp, passwd_regexp, body.children[i])) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// process_title
|
|
2
|
+
|
|
3
|
+
const outbound = require('../outbound')
|
|
4
|
+
|
|
5
|
+
function setupInterval(title, server) {
|
|
6
|
+
// Set up a timer to update title
|
|
7
|
+
return setInterval(() => {
|
|
8
|
+
// Connections per second
|
|
9
|
+
const av_cps = Math.round((server.notes.pt_connections / process.uptime()) * 100) / 100
|
|
10
|
+
const cps = server.notes.pt_connections - server.notes.pt_cps_diff
|
|
11
|
+
if (cps > server.notes.pt_cps_max) server.notes.pt_cps_max = cps
|
|
12
|
+
server.notes.pt_cps_diff = server.notes.pt_connections
|
|
13
|
+
// Recipients per second
|
|
14
|
+
const av_rps = Math.round((server.notes.pt_recipients / process.uptime()) * 100) / 100
|
|
15
|
+
const rps = server.notes.pt_recipients - server.notes.pt_rps_diff
|
|
16
|
+
if (rps > server.notes.pt_rps_max) server.notes.pt_rps_max = rps
|
|
17
|
+
server.notes.pt_rps_diff = server.notes.pt_recipients
|
|
18
|
+
// Recipients per message
|
|
19
|
+
const rpm = Math.round((server.notes.pt_recipients / server.notes.pt_messages) * 100) / 100 || 0
|
|
20
|
+
// Messages per second
|
|
21
|
+
const av_mps = Math.round((server.notes.pt_messages / process.uptime()) * 100) / 100
|
|
22
|
+
const mps = server.notes.pt_messages - server.notes.pt_mps_diff
|
|
23
|
+
if (mps > server.notes.pt_mps_max) server.notes.pt_mps_max = mps
|
|
24
|
+
server.notes.pt_mps_diff = server.notes.pt_messages
|
|
25
|
+
// Messages per connection
|
|
26
|
+
const mpc = Math.round((server.notes.pt_messages / server.notes.pt_connections) * 100) / 100 || 0
|
|
27
|
+
|
|
28
|
+
const out = server.notes.pt_out_stats || outbound.get_stats()
|
|
29
|
+
if (/\(worker\)/.test(title)) {
|
|
30
|
+
process.send({ event: 'process_title.outbound_stats', data: out })
|
|
31
|
+
}
|
|
32
|
+
// Update title
|
|
33
|
+
let new_title = `${title} cn=${server.notes.pt_connections} cc=${server.notes.pt_concurrent} cps=${cps}/${av_cps}/${server.notes.pt_cps_max} rcpts=${server.notes.pt_recipients}/${rpm} rps=${rps}/${av_rps}/${server.notes.pt_rps_max} msgs=${server.notes.pt_messages}/${mpc} mps=${mps}/${av_mps}/${server.notes.pt_mps_max} out=${out} `
|
|
34
|
+
if (/\(master\)/.test(title)) {
|
|
35
|
+
new_title += `respawn=${server.notes.pt_child_exits} `
|
|
36
|
+
}
|
|
37
|
+
process.title = new_title
|
|
38
|
+
}, 1000)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
exports.hook_init_master = function (next, server) {
|
|
42
|
+
server.notes.pt_connections = 0
|
|
43
|
+
server.notes.pt_concurrent = 0
|
|
44
|
+
server.notes.pt_cps_diff = 0
|
|
45
|
+
server.notes.pt_cps_max = 0
|
|
46
|
+
server.notes.pt_recipients = 0
|
|
47
|
+
server.notes.pt_rps_diff = 0
|
|
48
|
+
server.notes.pt_rps_max = 0
|
|
49
|
+
server.notes.pt_messages = 0
|
|
50
|
+
server.notes.pt_mps_diff = 0
|
|
51
|
+
server.notes.pt_mps_max = 0
|
|
52
|
+
server.notes.pt_child_exits = 0
|
|
53
|
+
let title = 'Haraka'
|
|
54
|
+
if (server.cluster) {
|
|
55
|
+
title = 'Haraka (master)'
|
|
56
|
+
process.title = title
|
|
57
|
+
server.notes.pt_concurrent_cluster = {}
|
|
58
|
+
server.notes.pt_new_out_stats = [0, 0, 0, 0]
|
|
59
|
+
const { cluster } = server
|
|
60
|
+
const recvMsg = (msg) => {
|
|
61
|
+
let count
|
|
62
|
+
switch (msg.event) {
|
|
63
|
+
case 'process_title.connect':
|
|
64
|
+
server.notes.pt_connections++
|
|
65
|
+
server.notes.pt_concurrent_cluster[msg.wid]++
|
|
66
|
+
count = 0
|
|
67
|
+
for (const id of Object.keys(server.notes.pt_concurrent_cluster)) {
|
|
68
|
+
count += server.notes.pt_concurrent_cluster[id]
|
|
69
|
+
}
|
|
70
|
+
server.notes.pt_concurrent = count
|
|
71
|
+
break
|
|
72
|
+
case 'process_title.disconnect':
|
|
73
|
+
server.notes.pt_concurrent_cluster[msg.wid]--
|
|
74
|
+
count = 0
|
|
75
|
+
for (const id of Object.keys(server.notes.pt_concurrent_cluster)) {
|
|
76
|
+
count += server.notes.pt_concurrent_cluster[id]
|
|
77
|
+
}
|
|
78
|
+
server.notes.pt_concurrent = count
|
|
79
|
+
break
|
|
80
|
+
case 'process_title.recipient':
|
|
81
|
+
server.notes.pt_recipients++
|
|
82
|
+
break
|
|
83
|
+
case 'process_title.message':
|
|
84
|
+
server.notes.pt_messages++
|
|
85
|
+
break
|
|
86
|
+
case 'process_title.outbound_stats': {
|
|
87
|
+
const out_stats = msg.data.split('/')
|
|
88
|
+
for (let i = 0; i < out_stats.length; i++) {
|
|
89
|
+
server.notes.pt_new_out_stats[i] += parseInt(out_stats[i], 10)
|
|
90
|
+
}
|
|
91
|
+
server.notes.pt_new_out_stats[3]++
|
|
92
|
+
// Check if we got all results back yet
|
|
93
|
+
if (server.notes.pt_new_out_stats[3] === Object.keys(cluster.workers).length) {
|
|
94
|
+
server.notes.pt_out_stats = server.notes.pt_new_out_stats.slice(0, 3).join('/')
|
|
95
|
+
server.notes.pt_new_out_stats = [0, 0, 0, 0]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// fall through
|
|
99
|
+
default:
|
|
100
|
+
// Unknown message
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Register any new workers
|
|
104
|
+
cluster.on('fork', (worker) => {
|
|
105
|
+
server.notes.pt_concurrent_cluster[worker.id] = 0
|
|
106
|
+
cluster.workers[worker.id].on('message', recvMsg)
|
|
107
|
+
})
|
|
108
|
+
cluster.on('exit', (worker) => {
|
|
109
|
+
delete server.notes.pt_concurrent_cluster[worker.id]
|
|
110
|
+
// Update concurrency
|
|
111
|
+
let count = 0
|
|
112
|
+
for (const id of Object.keys(server.notes.pt_concurrent_cluster)) {
|
|
113
|
+
count += server.notes.pt_concurrent_cluster[id]
|
|
114
|
+
}
|
|
115
|
+
server.notes.pt_concurrent = count
|
|
116
|
+
server.notes.pt_child_exits++
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
this._interval = setupInterval(title, server)
|
|
120
|
+
next()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
exports.hook_init_child = function (next, server) {
|
|
124
|
+
server.notes.pt_connections = 0
|
|
125
|
+
server.notes.pt_concurrent = 0
|
|
126
|
+
server.notes.pt_cps_diff = 0
|
|
127
|
+
server.notes.pt_cps_max = 0
|
|
128
|
+
server.notes.pt_recipients = 0
|
|
129
|
+
server.notes.pt_rps_diff = 0
|
|
130
|
+
server.notes.pt_rps_max = 0
|
|
131
|
+
server.notes.pt_messages = 0
|
|
132
|
+
server.notes.pt_mps_diff = 0
|
|
133
|
+
server.notes.pt_mps_max = 0
|
|
134
|
+
process.title = 'Haraka (worker)'
|
|
135
|
+
this._interval = setupInterval(process.title, server)
|
|
136
|
+
next()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
exports.shutdown = function () {
|
|
140
|
+
this.logdebug(`Shutting down interval: ${this._interval}`)
|
|
141
|
+
clearInterval(this._interval)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
exports.hook_connect_init = (next, connection) => {
|
|
145
|
+
const { server } = connection
|
|
146
|
+
connection.notes.pt_connect_run = true
|
|
147
|
+
if (server.cluster) {
|
|
148
|
+
const { worker } = server.cluster
|
|
149
|
+
worker.send({ event: 'process_title.connect', wid: worker.id })
|
|
150
|
+
}
|
|
151
|
+
server.notes.pt_connections++
|
|
152
|
+
server.notes.pt_concurrent++
|
|
153
|
+
next()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
exports.hook_disconnect = (next, connection) => {
|
|
157
|
+
const { server } = connection
|
|
158
|
+
// Check that the hook above ran
|
|
159
|
+
// It might not if the disconnection is immediate
|
|
160
|
+
// echo "QUIT" | nc localhost 25
|
|
161
|
+
// will exhibit this behaviour.
|
|
162
|
+
let worker
|
|
163
|
+
if (!connection.notes.pt_connect_run) {
|
|
164
|
+
if (server.cluster) {
|
|
165
|
+
worker = server.cluster.worker
|
|
166
|
+
worker.send({ event: 'process_title.connect', wid: worker.id })
|
|
167
|
+
}
|
|
168
|
+
server.notes.pt_connections++
|
|
169
|
+
server.notes.pt_concurrent++
|
|
170
|
+
}
|
|
171
|
+
if (server.cluster) {
|
|
172
|
+
worker = server.cluster.worker
|
|
173
|
+
worker.send({ event: 'process_title.disconnect', wid: worker.id })
|
|
174
|
+
}
|
|
175
|
+
server.notes.pt_concurrent--
|
|
176
|
+
next()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
exports.hook_rcpt = (next, connection) => {
|
|
180
|
+
const { server } = connection
|
|
181
|
+
if (server.cluster) {
|
|
182
|
+
const { worker } = server.cluster
|
|
183
|
+
worker.send({ event: 'process_title.recipient' })
|
|
184
|
+
}
|
|
185
|
+
server.notes.pt_recipients++
|
|
186
|
+
next()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
exports.hook_data = (next, connection) => {
|
|
190
|
+
const { server } = connection
|
|
191
|
+
if (server.cluster) {
|
|
192
|
+
const { worker } = server.cluster
|
|
193
|
+
worker.send({ event: 'process_title.message' })
|
|
194
|
+
}
|
|
195
|
+
server.notes.pt_messages++
|
|
196
|
+
next()
|
|
197
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const prof = require('v8-profiler')
|
|
2
|
+
|
|
3
|
+
exports.hook_connect_init = (next, conn) => {
|
|
4
|
+
prof.startProfiling(`Connection from: ${conn.remote.ip}`)
|
|
5
|
+
next()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
exports.hook_disconnect = (next, conn) => {
|
|
9
|
+
prof.stopProfiling(`Connection from: ${conn.remote.ip}`)
|
|
10
|
+
next()
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This plugin is entirely redundant. The core will queue outbound mails
|
|
2
|
+
// automatically just like this. It is kept here for backwards compatibility
|
|
3
|
+
// purposes only.
|
|
4
|
+
|
|
5
|
+
const outbound = require('./outbound')
|
|
6
|
+
|
|
7
|
+
exports.hook_queue_outbound = (next, connection) => {
|
|
8
|
+
// if not relaying, don't deliver outbound
|
|
9
|
+
if (!connection?.relaying) return next()
|
|
10
|
+
|
|
11
|
+
outbound.send_trans_email(connection?.transaction, next)
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// discard
|
|
2
|
+
|
|
3
|
+
exports.register = function () {
|
|
4
|
+
this.register_hook('queue', 'discard')
|
|
5
|
+
this.register_hook('queue_outbound', 'discard')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
exports.discard = (next, connection) => {
|
|
9
|
+
const txn = connection.transaction
|
|
10
|
+
|
|
11
|
+
const q_wants = txn.notes.get('queue.wants')
|
|
12
|
+
if (q_wants && q_wants !== 'discard') return next()
|
|
13
|
+
|
|
14
|
+
function discard() {
|
|
15
|
+
connection.loginfo('discarding message')
|
|
16
|
+
// Pretend we delivered the message
|
|
17
|
+
return next(OK)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (connection.notes.discard) return discard()
|
|
21
|
+
if (txn.notes.discard) return discard()
|
|
22
|
+
if (q_wants === 'discard') return discard()
|
|
23
|
+
if (process.env.YES_REALLY_DO_DISCARD) return discard()
|
|
24
|
+
|
|
25
|
+
// Allow other queue plugins to deliver
|
|
26
|
+
next()
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//queue/lmtp
|
|
2
|
+
|
|
3
|
+
'use strict'
|
|
4
|
+
|
|
5
|
+
let outbound
|
|
6
|
+
|
|
7
|
+
exports.register = function () {
|
|
8
|
+
this.load_lmtp_ini()
|
|
9
|
+
outbound = this.haraka_require('outbound')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.load_lmtp_ini = function () {
|
|
13
|
+
this.cfg = this.config.get('lmtp.ini', () => {
|
|
14
|
+
this.load_lmtp_ini()
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.hook_get_mx = function (next, hmail, domain) {
|
|
19
|
+
if (!hmail.todo.notes.using_lmtp) return next()
|
|
20
|
+
|
|
21
|
+
const section = this.cfg[domain] || this.cfg.main
|
|
22
|
+
|
|
23
|
+
const mx = {
|
|
24
|
+
using_lmtp: true,
|
|
25
|
+
priority: 0,
|
|
26
|
+
exchange: section.host ?? '127.0.0.1',
|
|
27
|
+
port: section.port ?? 24,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (section.path) mx.path = section.path
|
|
31
|
+
|
|
32
|
+
next(OK, mx)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
exports.hook_queue = (next, connection) => {
|
|
36
|
+
const txn = connection?.transaction
|
|
37
|
+
if (!txn) return next()
|
|
38
|
+
|
|
39
|
+
const q_wants = txn.notes.get('queue.wants')
|
|
40
|
+
|
|
41
|
+
if (q_wants && q_wants !== 'lmtp') return next()
|
|
42
|
+
|
|
43
|
+
txn.notes.using_lmtp = true
|
|
44
|
+
outbound.send_trans_email(txn, next)
|
|
45
|
+
}
|