Haraka 3.1.0 → 3.1.2
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/.prettierignore +4 -0
- package/CONTRIBUTORS.md +5 -5
- package/Changes.md +69 -50
- package/Plugins.md +3 -1
- package/README.md +1 -1
- package/bin/haraka +475 -478
- package/config/outbound.ini +3 -0
- package/connection.js +1072 -1108
- package/docs/Connection.md +29 -30
- package/docs/CoreConfig.md +38 -39
- package/docs/CustomReturnCodes.md +0 -1
- package/docs/HAProxy.md +2 -2
- package/docs/Header.md +1 -1
- package/docs/Logging.md +29 -5
- package/docs/Outbound.md +93 -78
- package/docs/Plugins.md +103 -108
- package/docs/Transaction.md +49 -51
- package/docs/Tutorial.md +127 -143
- package/docs/deprecated/access.md +0 -1
- package/docs/deprecated/backscatterer.md +2 -3
- package/docs/deprecated/connect.rdns_access.md +18 -27
- package/docs/deprecated/data.headers.md +0 -1
- package/docs/deprecated/data.nomsgid.md +1 -2
- package/docs/deprecated/data.noreceived.md +1 -2
- package/docs/deprecated/data.rfc5322_header_checks.md +1 -2
- package/docs/deprecated/dkim_sign.md +13 -17
- package/docs/deprecated/dkim_verify.md +9 -17
- package/docs/deprecated/dnsbl.md +36 -38
- package/docs/deprecated/dnswl.md +41 -43
- package/docs/deprecated/lookup_rdns.strict.md +21 -34
- package/docs/deprecated/mail_from.access.md +17 -25
- package/docs/deprecated/mail_from.blocklist.md +9 -12
- package/docs/deprecated/mail_from.nobounces.md +1 -2
- package/docs/deprecated/rcpt_to.access.md +20 -27
- package/docs/deprecated/rcpt_to.blocklist.md +10 -13
- package/docs/deprecated/rcpt_to.routes.md +0 -1
- package/docs/deprecated/rdns.regexp.md +13 -15
- package/docs/plugins/aliases.md +89 -89
- package/docs/plugins/auth/auth_bridge.md +5 -7
- package/docs/plugins/auth/auth_ldap.md +11 -14
- package/docs/plugins/auth/auth_proxy.md +10 -12
- package/docs/plugins/auth/auth_vpopmaild.md +5 -6
- package/docs/plugins/auth/flat_file.md +4 -4
- package/docs/plugins/block_me.md +3 -3
- package/docs/plugins/data.signatures.md +1 -2
- package/docs/plugins/delay_deny.md +3 -4
- package/docs/plugins/max_unrecognized_commands.md +4 -4
- package/docs/plugins/prevent_credential_leaks.md +6 -6
- package/docs/plugins/process_title.md +18 -18
- package/docs/plugins/queue/deliver.md +2 -3
- package/docs/plugins/queue/discard.md +4 -4
- package/docs/plugins/queue/lmtp.md +1 -3
- package/docs/plugins/queue/qmail-queue.md +7 -9
- package/docs/plugins/queue/quarantine.md +16 -21
- package/docs/plugins/queue/rabbitmq.md +8 -11
- package/docs/plugins/queue/rabbitmq_amqplib.md +43 -39
- package/docs/plugins/queue/smtp_bridge.md +7 -10
- package/docs/plugins/queue/smtp_forward.md +42 -34
- package/docs/plugins/queue/smtp_proxy.md +30 -29
- package/docs/plugins/queue/test.md +1 -3
- package/docs/plugins/rcpt_to.in_host_list.md +6 -6
- package/docs/plugins/rcpt_to.max_count.md +1 -1
- package/docs/plugins/record_envelope_addresses.md +3 -3
- package/docs/plugins/reseed_rng.md +6 -6
- package/docs/plugins/status.md +9 -8
- package/docs/plugins/tarpit.md +7 -11
- package/docs/plugins/tls.md +12 -17
- package/docs/plugins/toobusy.md +4 -4
- package/docs/plugins/xclient.md +3 -3
- package/docs/tutorials/Migrating_from_v1_to_v2.md +19 -41
- package/docs/tutorials/SettingUpOutbound.md +6 -9
- package/endpoint.js +35 -38
- package/eslint.config.mjs +22 -19
- package/haraka.js +42 -47
- package/host_pool.js +75 -79
- package/http/html/404.html +45 -49
- package/http/html/index.html +39 -28
- package/http/package.json +2 -4
- package/line_socket.js +27 -28
- package/logger.js +182 -201
- package/outbound/client_pool.js +34 -27
- package/outbound/config.js +64 -59
- package/outbound/fsync_writestream.js +24 -25
- package/outbound/hmail.js +888 -835
- package/outbound/index.js +194 -187
- package/outbound/qfile.js +49 -52
- package/outbound/queue.js +197 -190
- package/outbound/timer_queue.js +41 -43
- package/outbound/tls.js +68 -61
- package/outbound/todo.js +11 -11
- package/package.json +38 -33
- package/plugins/.eslintrc.yaml +0 -1
- package/plugins/auth/auth_base.js +123 -127
- package/plugins/auth/auth_bridge.js +7 -7
- package/plugins/auth/auth_proxy.js +121 -126
- package/plugins/auth/auth_vpopmaild.js +84 -85
- package/plugins/auth/flat_file.js +18 -17
- package/plugins/block_me.js +31 -31
- package/plugins/data.signatures.js +13 -13
- package/plugins/delay_deny.js +65 -61
- package/plugins/prevent_credential_leaks.js +23 -23
- package/plugins/process_title.js +125 -128
- package/plugins/profile.js +5 -5
- package/plugins/queue/deliver.js +3 -3
- package/plugins/queue/discard.js +13 -14
- package/plugins/queue/lmtp.js +16 -17
- package/plugins/queue/qmail-queue.js +54 -55
- package/plugins/queue/quarantine.js +68 -70
- package/plugins/queue/rabbitmq.js +80 -87
- package/plugins/queue/rabbitmq_amqplib.js +75 -54
- package/plugins/queue/smtp_bridge.js +16 -16
- package/plugins/queue/smtp_forward.js +175 -179
- package/plugins/queue/smtp_proxy.js +69 -71
- package/plugins/queue/test.js +9 -9
- package/plugins/rcpt_to.host_list_base.js +30 -34
- package/plugins/rcpt_to.in_host_list.js +19 -19
- package/plugins/record_envelope_addresses.js +4 -4
- package/plugins/reseed_rng.js +4 -4
- package/plugins/status.js +90 -97
- package/plugins/tarpit.js +25 -14
- package/plugins/tls.js +68 -68
- package/plugins/toobusy.js +21 -23
- package/plugins/xclient.js +51 -53
- package/plugins.js +276 -293
- package/rfc1869.js +30 -35
- package/server.js +308 -299
- package/smtp_client.js +244 -228
- package/test/.eslintrc.yaml +0 -1
- package/test/connection.js +127 -134
- package/test/endpoint.js +53 -47
- package/test/fixtures/line_socket.js +12 -12
- package/test/fixtures/util_hmailitem.js +89 -85
- package/test/host_pool.js +90 -92
- package/test/installation/plugins/base_plugin.js +2 -2
- package/test/installation/plugins/folder_plugin/index.js +2 -3
- package/test/installation/plugins/inherits.js +3 -3
- package/test/installation/plugins/load_first.js +2 -3
- package/test/installation/plugins/plugin.js +1 -3
- package/test/installation/plugins/tls.js +2 -4
- package/test/logger.js +135 -116
- package/test/outbound/hmail.js +49 -35
- package/test/outbound/index.js +118 -101
- package/test/outbound/qfile.js +51 -53
- package/test/outbound_bounce_net_errors.js +84 -69
- package/test/outbound_bounce_rfc3464.js +235 -165
- package/test/plugins/auth/auth_base.js +420 -279
- package/test/plugins/auth/auth_vpopmaild.js +38 -39
- package/test/plugins/queue/smtp_forward.js +126 -104
- package/test/plugins/rcpt_to.host_list_base.js +85 -67
- package/test/plugins/rcpt_to.in_host_list.js +159 -112
- package/test/plugins/status.js +71 -64
- package/test/plugins/tls.js +37 -34
- package/test/plugins.js +97 -92
- package/test/rfc1869.js +19 -26
- package/test/server.js +293 -272
- package/test/smtp_client.js +180 -176
- package/test/tls_socket.js +62 -66
- package/test/transaction.js +159 -160
- package/tls_socket.js +331 -333
- package/transaction.js +129 -137
package/plugins/block_me.js
CHANGED
|
@@ -3,84 +3,84 @@
|
|
|
3
3
|
// in the mail_from.blocklist file. You need to be running the
|
|
4
4
|
// mail_from.blocklist plugin for this to work fully.
|
|
5
5
|
|
|
6
|
-
const fs
|
|
7
|
-
const utils = require('haraka-utils')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const utils = require('haraka-utils')
|
|
8
8
|
|
|
9
9
|
exports.hook_data = (next, connection) => {
|
|
10
10
|
// enable mail body parsing
|
|
11
|
-
connection.transaction.parse_body = true
|
|
12
|
-
next()
|
|
11
|
+
connection.transaction.parse_body = true
|
|
12
|
+
next()
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
exports.hook_data_post = function (next, connection) {
|
|
16
|
-
if (!connection?.relaying || !connection?.transaction) return next()
|
|
16
|
+
if (!connection?.relaying || !connection?.transaction) return next()
|
|
17
17
|
|
|
18
|
-
const recip = (this.config.get('block_me.recipient') || '').toLowerCase()
|
|
19
|
-
const senders = this.config.get('block_me.senders', 'list')
|
|
18
|
+
const recip = (this.config.get('block_me.recipient') || '').toLowerCase()
|
|
19
|
+
const senders = this.config.get('block_me.senders', 'list')
|
|
20
20
|
|
|
21
21
|
// Make sure only 1 recipient
|
|
22
22
|
if (connection.transaction.rcpt_to.length != 1) {
|
|
23
|
-
return next()
|
|
23
|
+
return next()
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Check recipient is the right one
|
|
27
27
|
if (connection.transaction.rcpt_to[0].address().toLowerCase() != recip) {
|
|
28
|
-
return next()
|
|
28
|
+
return next()
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Check sender is in list
|
|
32
|
-
const sender = connection.transaction.mail_from.address()
|
|
32
|
+
const sender = connection.transaction.mail_from.address()
|
|
33
33
|
if (!utils.in_array(sender, senders)) {
|
|
34
|
-
return next(DENY, `You are not allowed to block mail, ${sender}`)
|
|
34
|
+
return next(DENY, `You are not allowed to block mail, ${sender}`)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Now extract the "From" from the body...
|
|
38
|
-
const to_block = extract_from_line(connection.transaction.body)
|
|
38
|
+
const to_block = extract_from_line(connection.transaction.body)
|
|
39
39
|
if (!to_block) {
|
|
40
|
-
connection.logerror(this,
|
|
41
|
-
return next()
|
|
40
|
+
connection.logerror(this, 'No sender found in email')
|
|
41
|
+
return next()
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
connection.loginfo(this, `Blocking new sender: ${to_block}`)
|
|
44
|
+
connection.loginfo(this, `Blocking new sender: ${to_block}`)
|
|
45
45
|
|
|
46
|
-
connection.transaction.notes.block_me = 1
|
|
46
|
+
connection.transaction.notes.block_me = 1
|
|
47
47
|
|
|
48
48
|
// add to mail_from.blocklist
|
|
49
49
|
fs.open('./config/mail_from.blocklist', 'a', (err, fd) => {
|
|
50
50
|
if (err) {
|
|
51
|
-
connection.logerror(this, `Unable to append to mail_from.blocklist: ${err}`)
|
|
52
|
-
return
|
|
51
|
+
connection.logerror(this, `Unable to append to mail_from.blocklist: ${err}`)
|
|
52
|
+
return
|
|
53
53
|
}
|
|
54
54
|
fs.write(fd, `${to_block}\n`, null, 'UTF-8', (err2, written) => {
|
|
55
|
-
fs.close(fd)
|
|
56
|
-
})
|
|
57
|
-
})
|
|
55
|
+
fs.close(fd)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
58
|
|
|
59
|
-
next()
|
|
59
|
+
next()
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
exports.hook_queue = (next, connection) => {
|
|
63
63
|
if (connection.transaction.notes.block_me) {
|
|
64
64
|
// pretend we queued this mail
|
|
65
|
-
return next(OK)
|
|
65
|
+
return next(OK)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
next()
|
|
68
|
+
next()
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// Example: From: Site Tucano Gold <contato@tucanogold.com.br>
|
|
72
|
-
function extract_from_line
|
|
73
|
-
const matches = body.bodytext.match(/\bFrom:[^<\n]*<([^>\n]*)>/)
|
|
72
|
+
function extract_from_line(body) {
|
|
73
|
+
const matches = body.bodytext.match(/\bFrom:[^<\n]*<([^>\n]*)>/)
|
|
74
74
|
if (matches) {
|
|
75
|
-
return matches[1]
|
|
75
|
+
return matches[1]
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
for (let i=0,l=body.children.length; i < l; i++) {
|
|
79
|
-
const from = extract_from_line(body.children[i])
|
|
78
|
+
for (let i = 0, l = body.children.length; i < l; i++) {
|
|
79
|
+
const from = extract_from_line(body.children[i])
|
|
80
80
|
if (from) {
|
|
81
|
-
return from
|
|
81
|
+
return from
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
return null
|
|
85
|
+
return null
|
|
86
86
|
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
'use strict'
|
|
1
|
+
'use strict'
|
|
2
2
|
// Simple string signatures
|
|
3
3
|
|
|
4
4
|
exports.hook_data = (next, connection) => {
|
|
5
5
|
// enable mail body parsing
|
|
6
|
-
if (connection?.transaction) connection.transaction.parse_body = true
|
|
7
|
-
next()
|
|
6
|
+
if (connection?.transaction) connection.transaction.parse_body = true
|
|
7
|
+
next()
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
exports.hook_data_post = function (next, connection) {
|
|
11
|
-
if (!connection?.transaction) return next()
|
|
11
|
+
if (!connection?.transaction) return next()
|
|
12
12
|
|
|
13
|
-
const sigs = this.config.get('data.signatures', 'list')
|
|
13
|
+
const sigs = this.config.get('data.signatures', 'list')
|
|
14
14
|
|
|
15
15
|
if (check_sigs(sigs, connection.transaction.body)) {
|
|
16
|
-
return next(DENY,
|
|
16
|
+
return next(DENY, 'Mail matches a known spam signature')
|
|
17
17
|
}
|
|
18
|
-
next()
|
|
18
|
+
next()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function check_sigs
|
|
22
|
-
for (let i=0,l=sigs.length; i < l; i++) {
|
|
23
|
-
if (body.bodytext.includes(sigs[i])) return 1
|
|
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
24
|
}
|
|
25
25
|
|
|
26
|
-
for (let i=0,l=body.children.length; i < l; i++) {
|
|
27
|
-
if (check_sigs(sigs, body.children[i])) return 1
|
|
26
|
+
for (let i = 0, l = body.children.length; i < l; i++) {
|
|
27
|
+
if (check_sigs(sigs, body.children[i])) return 1
|
|
28
28
|
}
|
|
29
|
-
return 0
|
|
29
|
+
return 0
|
|
30
30
|
}
|
package/plugins/delay_deny.js
CHANGED
|
@@ -1,61 +1,65 @@
|
|
|
1
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
|
-
*/
|
|
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
8
|
|
|
9
9
|
exports.hook_deny = function (next, connection, params) {
|
|
10
10
|
/* params
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
** [0] = plugin return value (DENY or DENYSOFT)
|
|
12
|
+
** [1] = plugin return message
|
|
13
|
+
*/
|
|
14
14
|
|
|
15
|
-
const pi_name
|
|
16
|
-
const pi_function = params[3]
|
|
15
|
+
const pi_name = params[2]
|
|
16
|
+
const pi_function = params[3]
|
|
17
17
|
// var pi_params = params[4];
|
|
18
|
-
const pi_hook
|
|
18
|
+
const pi_hook = params[5]
|
|
19
19
|
|
|
20
|
-
const { transaction } = connection
|
|
20
|
+
const { transaction } = connection
|
|
21
21
|
|
|
22
22
|
// Don't delay ourselves...
|
|
23
|
-
if (pi_name == 'delay_deny') return next()
|
|
23
|
+
if (pi_name == 'delay_deny') return next()
|
|
24
24
|
|
|
25
25
|
// Load config
|
|
26
|
-
const cfg = this.config.get('delay_deny.ini')
|
|
27
|
-
let skip
|
|
28
|
-
let included
|
|
26
|
+
const cfg = this.config.get('delay_deny.ini')
|
|
27
|
+
let skip
|
|
28
|
+
let included
|
|
29
29
|
if (cfg.main.included_plugins) {
|
|
30
|
-
included = cfg.main.included_plugins.split(/[;, ]+/)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
skip = cfg.main.excluded_plugins.split(/[;, ]+/);
|
|
30
|
+
included = cfg.main.included_plugins.split(/[;, ]+/)
|
|
31
|
+
} else if (cfg.main.excluded_plugins) {
|
|
32
|
+
skip = cfg.main.excluded_plugins.split(/[;, ]+/)
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
// 'included' mode: only delay deny plugins in the included list
|
|
37
36
|
if (included?.length) {
|
|
38
|
-
if (
|
|
37
|
+
if (
|
|
38
|
+
!included.includes(pi_name) &&
|
|
39
39
|
!included.includes(`${pi_name}:${pi_hook}`) &&
|
|
40
|
-
!included.includes(`${pi_name}:${pi_hook}:${pi_function}`)
|
|
41
|
-
|
|
40
|
+
!included.includes(`${pi_name}:${pi_hook}:${pi_function}`)
|
|
41
|
+
) {
|
|
42
|
+
return next()
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
44
|
+
} else if (skip?.length) {
|
|
45
|
+
// 'excluded' mode: delay deny everything except in skip list
|
|
45
46
|
// Skip by <plugin name>
|
|
46
47
|
if (skip.includes(pi_name)) {
|
|
47
|
-
connection.logdebug(this, `not delaying excluded plugin: ${pi_name}`)
|
|
48
|
-
return next()
|
|
48
|
+
connection.logdebug(this, `not delaying excluded plugin: ${pi_name}`)
|
|
49
|
+
return next()
|
|
49
50
|
}
|
|
50
51
|
// Skip by <plugin name>:<hook>
|
|
51
52
|
if (skip.includes(`${pi_name}:${pi_hook}`)) {
|
|
52
|
-
connection.logdebug(this, `not delaying excluded hook: ${pi_hook} in plugin: ${pi_name}`)
|
|
53
|
-
return next()
|
|
53
|
+
connection.logdebug(this, `not delaying excluded hook: ${pi_hook} in plugin: ${pi_name}`)
|
|
54
|
+
return next()
|
|
54
55
|
}
|
|
55
56
|
// Skip by <plugin name>:<hook>:<function name>
|
|
56
57
|
if (skip.includes(`${pi_name}:${pi_hook}:${pi_function}`)) {
|
|
57
|
-
connection.logdebug(
|
|
58
|
-
|
|
58
|
+
connection.logdebug(
|
|
59
|
+
this,
|
|
60
|
+
`not delaying excluded function: ${pi_function} on hook: ${pi_hook} in plugin: ${pi_name}`,
|
|
61
|
+
)
|
|
62
|
+
return next()
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -66,84 +70,84 @@ exports.hook_deny = function (next, connection, params) {
|
|
|
66
70
|
case 'ehlo':
|
|
67
71
|
case 'helo':
|
|
68
72
|
if (!connection.notes.delay_deny_pre) {
|
|
69
|
-
connection.notes.delay_deny_pre = []
|
|
73
|
+
connection.notes.delay_deny_pre = []
|
|
70
74
|
}
|
|
71
|
-
connection.notes.delay_deny_pre.push(params)
|
|
75
|
+
connection.notes.delay_deny_pre.push(params)
|
|
72
76
|
if (!connection.notes.delay_deny_pre_fail) {
|
|
73
|
-
connection.notes.delay_deny_pre_fail = {}
|
|
77
|
+
connection.notes.delay_deny_pre_fail = {}
|
|
74
78
|
}
|
|
75
|
-
connection.notes.delay_deny_pre_fail[pi_name] = 1
|
|
76
|
-
return next(OK)
|
|
79
|
+
connection.notes.delay_deny_pre_fail[pi_name] = 1
|
|
80
|
+
return next(OK)
|
|
77
81
|
// Pre-DATA transaction delays
|
|
78
82
|
case 'mail':
|
|
79
83
|
case 'rcpt':
|
|
80
84
|
case 'rcpt_ok':
|
|
81
85
|
if (!transaction.notes.delay_deny_pre) {
|
|
82
|
-
transaction.notes.delay_deny_pre = []
|
|
86
|
+
transaction.notes.delay_deny_pre = []
|
|
83
87
|
}
|
|
84
|
-
transaction.notes.delay_deny_pre.push(params)
|
|
88
|
+
transaction.notes.delay_deny_pre.push(params)
|
|
85
89
|
if (!transaction.notes.delay_deny_pre_fail) {
|
|
86
|
-
transaction.notes.delay_deny_pre_fail = {}
|
|
90
|
+
transaction.notes.delay_deny_pre_fail = {}
|
|
87
91
|
}
|
|
88
|
-
transaction.notes.delay_deny_pre_fail[pi_name] = 1
|
|
89
|
-
return next(OK)
|
|
92
|
+
transaction.notes.delay_deny_pre_fail[pi_name] = 1
|
|
93
|
+
return next(OK)
|
|
90
94
|
// Post-DATA delays
|
|
91
95
|
case 'data':
|
|
92
96
|
case 'data_post':
|
|
93
|
-
|
|
97
|
+
// fall through
|
|
94
98
|
default:
|
|
95
99
|
// No delays
|
|
96
|
-
next()
|
|
100
|
+
next()
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
exports.hook_rcpt_ok = function (next, connection, rcpt) {
|
|
101
|
-
const transaction = connection?.transaction
|
|
102
|
-
if
|
|
105
|
+
const transaction = connection?.transaction
|
|
106
|
+
if (!transaction) return next()
|
|
103
107
|
|
|
104
108
|
// Bypass all pre-DATA deny for AUTH/RELAY
|
|
105
109
|
if (connection.relaying) {
|
|
106
|
-
connection.loginfo(this, 'bypassing all pre-DATA deny: AUTH/RELAY')
|
|
107
|
-
return next()
|
|
110
|
+
connection.loginfo(this, 'bypassing all pre-DATA deny: AUTH/RELAY')
|
|
111
|
+
return next()
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
// Apply any delayed rejections
|
|
111
115
|
// Check connection level pre-DATA rejections first
|
|
112
116
|
if (connection.notes?.delay_deny_pre) {
|
|
113
117
|
for (const params of connection.notes.delay_deny_pre) {
|
|
114
|
-
return next(params[0], params[1])
|
|
118
|
+
return next(params[0], params[1])
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
// Then check transaction level pre-DATA
|
|
119
123
|
if (transaction.notes?.delay_deny_pre) {
|
|
120
|
-
for (let i=0; i<transaction.notes.delay_deny_pre.length; i++) {
|
|
121
|
-
const params = transaction.notes.delay_deny_pre[i]
|
|
124
|
+
for (let i = 0; i < transaction.notes.delay_deny_pre.length; i++) {
|
|
125
|
+
const params = transaction.notes.delay_deny_pre[i]
|
|
122
126
|
|
|
123
127
|
// Remove rejection from the array if it was on the rcpt hooks
|
|
124
128
|
if (params[5] === 'rcpt' || params[5] === 'rcpt_ok') {
|
|
125
|
-
transaction.notes.delay_deny_pre.splice(i, 1)
|
|
129
|
+
transaction.notes.delay_deny_pre.splice(i, 1)
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
return next(params[0], params[1])
|
|
132
|
+
return next(params[0], params[1])
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
|
-
next()
|
|
135
|
+
next()
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
exports.hook_data = (next, connection) => {
|
|
135
|
-
const transaction = connection?.transaction
|
|
136
|
-
if (!transaction) return next()
|
|
139
|
+
const transaction = connection?.transaction
|
|
140
|
+
if (!transaction) return next()
|
|
137
141
|
|
|
138
142
|
// Add a header showing all pre-DATA rejections
|
|
139
|
-
const fails = []
|
|
143
|
+
const fails = []
|
|
140
144
|
if (connection.notes?.delay_deny_pre_fail) {
|
|
141
|
-
fails.push.apply(Object.keys(connection.notes.delay_deny_pre_fail))
|
|
145
|
+
fails.push.apply(Object.keys(connection.notes.delay_deny_pre_fail))
|
|
142
146
|
}
|
|
143
147
|
if (transaction.notes?.delay_deny_pre_fail) {
|
|
144
|
-
fails.push.apply(Object.keys(transaction.notes.delay_deny_pre_fail))
|
|
148
|
+
fails.push.apply(Object.keys(transaction.notes.delay_deny_pre_fail))
|
|
145
149
|
}
|
|
146
|
-
if (fails.length) transaction.add_header('X-Haraka-Fail-Pre', fails.join(' '))
|
|
150
|
+
if (fails.length) transaction.add_header('X-Haraka-Fail-Pre', fails.join(' '))
|
|
147
151
|
|
|
148
|
-
next()
|
|
152
|
+
next()
|
|
149
153
|
}
|
|
@@ -1,59 +1,59 @@
|
|
|
1
1
|
// Prevent a user from sending their AUTH credentials
|
|
2
2
|
// This is a simple, primitive form of anti-phishing.
|
|
3
3
|
|
|
4
|
-
function escapeRegExp
|
|
5
|
-
return str.replace(/[-[\]/{}()*+?.\\^$|]/g,
|
|
4
|
+
function escapeRegExp(str) {
|
|
5
|
+
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
exports.hook_data = (next, connection) => {
|
|
9
9
|
const { notes, transaction } = connection ?? {}
|
|
10
10
|
|
|
11
11
|
if (transaction && notes?.auth_user && notes.auth_passwd) {
|
|
12
|
-
transaction.parse_body = true
|
|
12
|
+
transaction.parse_body = true
|
|
13
13
|
}
|
|
14
|
-
next()
|
|
14
|
+
next()
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
exports.hook_data_post = (next, connection) => {
|
|
18
18
|
if (!(connection.notes.auth_user && connection.notes.auth_passwd)) {
|
|
19
|
-
return next()
|
|
19
|
+
return next()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let user = connection.notes.auth_user
|
|
23
|
-
let domain
|
|
22
|
+
let user = connection.notes.auth_user
|
|
23
|
+
let domain
|
|
24
24
|
const idx = user.indexOf('@')
|
|
25
25
|
if (idx) {
|
|
26
26
|
// If the username is qualified (e.g. user@domain.com)
|
|
27
27
|
// then we make the @domain.com part optional in the regexp.
|
|
28
|
-
domain = user.substr(idx)
|
|
29
|
-
user = user.substr(0, idx)
|
|
28
|
+
domain = user.substr(idx)
|
|
29
|
+
user = user.substr(0, idx)
|
|
30
30
|
}
|
|
31
|
-
const passwd
|
|
32
|
-
const bound_regexp
|
|
33
|
-
const passwd_regexp = new RegExp(bound_regexp + escapeRegExp(passwd) + bound_regexp, 'm')
|
|
34
|
-
const user_regexp
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
const passwd = connection.notes.auth_passwd
|
|
32
|
+
const bound_regexp = '(?:\\b|\\B)'
|
|
33
|
+
const passwd_regexp = new RegExp(bound_regexp + escapeRegExp(passwd) + bound_regexp, 'm')
|
|
34
|
+
const user_regexp = new RegExp(
|
|
35
|
+
bound_regexp + escapeRegExp(user) + (domain ? `(?:${escapeRegExp(domain)})?` : '') + bound_regexp,
|
|
36
|
+
'im',
|
|
37
|
+
)
|
|
38
38
|
|
|
39
39
|
if (look_for_credentials(user_regexp, passwd_regexp, connection?.transaction?.body)) {
|
|
40
|
-
return next(DENY,
|
|
40
|
+
return next(DENY, 'Credential leak detected: never give out your username/password to anyone!')
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
next()
|
|
43
|
+
next()
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function look_for_credentials
|
|
46
|
+
function look_for_credentials(user_regexp, passwd_regexp, body) {
|
|
47
47
|
if (user_regexp.test(body.bodytext) && passwd_regexp.test(body.bodytext)) {
|
|
48
|
-
return true
|
|
48
|
+
return true
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// Check all child parts
|
|
52
|
-
for (let i=0,l=body.children.length; i < l; i++) {
|
|
52
|
+
for (let i = 0, l = body.children.length; i < l; i++) {
|
|
53
53
|
if (look_for_credentials(user_regexp, passwd_regexp, body.children[i])) {
|
|
54
|
-
return true
|
|
54
|
+
return true
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
return false
|
|
58
|
+
return false
|
|
59
59
|
}
|