Haraka 3.1.6 → 3.1.7
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/CHANGELOG.md +32 -1
- package/CONTRIBUTORS.md +8 -8
- package/Plugins.md +99 -99
- package/config/smtp_forward.ini +10 -0
- package/config/smtp_proxy.ini +10 -0
- package/connection.js +25 -8
- package/docs/plugins/queue/smtp_forward.md +19 -3
- package/docs/plugins/queue/smtp_proxy.md +10 -2
- package/haraka.js +1 -1
- package/outbound/hmail.js +39 -39
- package/outbound/index.js +4 -4
- package/outbound/tls.js +2 -43
- package/package.json +49 -48
- package/plugins/auth/auth_base.js +9 -3
- package/plugins/auth/auth_proxy.js +14 -11
- package/plugins/block_me.js +4 -2
- package/plugins/prevent_credential_leaks.js +3 -1
- package/plugins/process_title.js +6 -6
- package/plugins/queue/qmail-queue.js +15 -19
- package/plugins/queue/smtp_forward.js +12 -4
- package/plugins/queue/smtp_proxy.js +14 -3
- package/plugins/tls.js +13 -5
- package/plugins/xclient.js +3 -1
- package/server.js +5 -3
- package/smtp_client.js +20 -11
- package/test/config/block_me.recipient +1 -0
- package/test/config/block_me.senders +1 -0
- package/test/connection.js +24 -0
- package/test/outbound/bounce_net_errors.js +3 -2
- package/test/plugins/auth/auth_bridge.js +80 -0
- package/test/plugins/auth/flat_file.js +128 -0
- package/test/plugins/block_me.js +157 -0
- package/test/plugins/data.signatures.js +114 -0
- package/test/plugins/delay_deny.js +263 -0
- package/test/plugins/prevent_credential_leaks.js +178 -0
- package/test/plugins/process_title.js +135 -0
- package/test/plugins/queue/deliver.js +99 -0
- package/test/plugins/queue/discard.js +79 -0
- package/test/plugins/queue/lmtp.js +138 -0
- package/test/plugins/queue/qmail-queue.js +99 -0
- package/test/plugins/queue/quarantine.js +81 -0
- package/test/plugins/queue/smtp_bridge.js +154 -0
- package/test/plugins/queue/smtp_forward.js +42 -6
- package/test/plugins/queue/smtp_proxy.js +139 -0
- package/test/plugins/reseed_rng.js +34 -0
- package/test/plugins/tarpit.js +91 -0
- package/test/plugins/tls.js +25 -0
- package/test/plugins/toobusy.js +21 -0
- package/test/plugins/xclient.js +14 -0
- package/test/server.js +59 -0
- package/test/smtp_client.js +45 -12
- package/test/tls_socket.js +82 -0
- package/tls_socket.js +50 -0
package/outbound/hmail.js
CHANGED
|
@@ -266,12 +266,12 @@ class HMailItem extends events.EventEmitter {
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
async found_mx(mxs) {
|
|
269
|
-
// support
|
|
269
|
+
// support RFC 7505 null MX
|
|
270
270
|
if (mxs.length === 1 && mxs[0].priority === 0 && mxs[0].exchange === '') {
|
|
271
271
|
for (const rcpt of this.todo.rcpt_to) {
|
|
272
272
|
this.extend_rcpt_with_dsn(
|
|
273
273
|
rcpt,
|
|
274
|
-
DSN.
|
|
274
|
+
DSN.addr_null_mx(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`),
|
|
275
275
|
)
|
|
276
276
|
}
|
|
277
277
|
return this.bounce(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`)
|
|
@@ -459,7 +459,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
459
459
|
} else if (r.toUpperCase() === 'ENHANCEDSTATUSCODES') {
|
|
460
460
|
smtp_properties.enh_status_codes = true
|
|
461
461
|
} else if (r.toUpperCase() === 'SMTPUTF8') {
|
|
462
|
-
smtp_properties.
|
|
462
|
+
smtp_properties.smtputf8 = true
|
|
463
463
|
} else {
|
|
464
464
|
// Check for SIZE parameter and limit
|
|
465
465
|
let matches = r.match(/^SIZE\s+(\d+)$/)
|
|
@@ -476,9 +476,9 @@ class HMailItem extends events.EventEmitter {
|
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
function get_reverse_path_with_params() {
|
|
479
|
-
const rp = self.todo.mail_from.format(!smtp_properties.
|
|
479
|
+
const rp = self.todo.mail_from.format(!smtp_properties.smtputf8)
|
|
480
480
|
let rp_params = ''
|
|
481
|
-
if (smtp_properties.
|
|
481
|
+
if (smtp_properties.smtputf8 && has_non_ascii(rp)) rp_params += ' SMTPUTF8'
|
|
482
482
|
return `FROM:${rp}${rp_params}`
|
|
483
483
|
}
|
|
484
484
|
|
|
@@ -678,12 +678,12 @@ class HMailItem extends events.EventEmitter {
|
|
|
678
678
|
processing_mail = false
|
|
679
679
|
// Release back to the pool and instruct it to terminate this connection
|
|
680
680
|
client_pool.release_client(socket, mx)
|
|
681
|
-
self.todo.rcpt_to
|
|
681
|
+
for (const rcpt of self.todo.rcpt_to) {
|
|
682
682
|
self.extend_rcpt_with_dsn(
|
|
683
683
|
rcpt,
|
|
684
684
|
DSN.proto_invalid_command(`Unrecognized response from upstream server: ${line}`),
|
|
685
685
|
)
|
|
686
|
-
}
|
|
686
|
+
}
|
|
687
687
|
self.bounce(`Unrecognized response from upstream server: ${line}`, { mx })
|
|
688
688
|
return
|
|
689
689
|
}
|
|
@@ -720,14 +720,14 @@ class HMailItem extends events.EventEmitter {
|
|
|
720
720
|
}
|
|
721
721
|
// Error
|
|
722
722
|
reason = response.join(' ')
|
|
723
|
-
|
|
723
|
+
for (const rcpt of recipients) {
|
|
724
724
|
rcpt.dsn_action = 'delayed'
|
|
725
725
|
rcpt.dsn_smtp_code = code
|
|
726
726
|
rcpt.dsn_smtp_extc = extc
|
|
727
727
|
rcpt.dsn_status = extc
|
|
728
728
|
rcpt.dsn_smtp_response = response.join(' ')
|
|
729
729
|
rcpt.dsn_remote_mta = mx.exchange
|
|
730
|
-
}
|
|
730
|
+
}
|
|
731
731
|
send_command('QUIT')
|
|
732
732
|
processing_mail = false
|
|
733
733
|
return self.temp_fail(`Upstream error: ${code} ${extc ? `${extc} ` : ''}${reason}`)
|
|
@@ -756,14 +756,14 @@ class HMailItem extends events.EventEmitter {
|
|
|
756
756
|
}
|
|
757
757
|
} else if (processing_mail) {
|
|
758
758
|
reason = response.join(' ')
|
|
759
|
-
|
|
759
|
+
for (const rcpt of recipients) {
|
|
760
760
|
rcpt.dsn_action = 'delayed'
|
|
761
761
|
rcpt.dsn_smtp_code = code
|
|
762
762
|
rcpt.dsn_smtp_extc = extc
|
|
763
763
|
rcpt.dsn_status = extc
|
|
764
764
|
rcpt.dsn_smtp_response = response.join(' ')
|
|
765
765
|
rcpt.dsn_remote_mta = mx.exchange
|
|
766
|
-
}
|
|
766
|
+
}
|
|
767
767
|
send_command('QUIT')
|
|
768
768
|
processing_mail = false
|
|
769
769
|
return self.temp_fail(`Upstream error: ${code} ${extc ? `${extc} ` : ''}${reason}`)
|
|
@@ -803,14 +803,14 @@ class HMailItem extends events.EventEmitter {
|
|
|
803
803
|
}
|
|
804
804
|
}
|
|
805
805
|
} else {
|
|
806
|
-
|
|
806
|
+
for (const rcpt of recipients) {
|
|
807
807
|
rcpt.dsn_action = 'failed'
|
|
808
808
|
rcpt.dsn_smtp_code = code
|
|
809
809
|
rcpt.dsn_smtp_extc = extc
|
|
810
810
|
rcpt.dsn_status = extc
|
|
811
811
|
rcpt.dsn_smtp_response = response.join(' ')
|
|
812
812
|
rcpt.dsn_remote_mta = mx.exchange
|
|
813
|
-
}
|
|
813
|
+
}
|
|
814
814
|
send_command('QUIT')
|
|
815
815
|
processing_mail = false
|
|
816
816
|
return self.bounce(reason, { mx })
|
|
@@ -871,7 +871,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
871
871
|
case 'mail':
|
|
872
872
|
last_recip = recipients[recip_index]
|
|
873
873
|
recip_index++
|
|
874
|
-
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.
|
|
874
|
+
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.smtputf8)}`)
|
|
875
875
|
break
|
|
876
876
|
case 'rcpt':
|
|
877
877
|
if (last_recip && code.match(/^250/)) {
|
|
@@ -887,7 +887,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
887
887
|
} else {
|
|
888
888
|
last_recip = recipients[recip_index]
|
|
889
889
|
recip_index++
|
|
890
|
-
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.
|
|
890
|
+
send_command('RCPT', `TO:${last_recip.format(!smtp_properties.smtputf8)}`)
|
|
891
891
|
}
|
|
892
892
|
break
|
|
893
893
|
case 'data': {
|
|
@@ -1036,7 +1036,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
1036
1036
|
msgid: `<${utils.uuid()}@${net_utils.get_primary_host_name()}>`,
|
|
1037
1037
|
}
|
|
1038
1038
|
|
|
1039
|
-
|
|
1039
|
+
for (let line of bounce_msg_) {
|
|
1040
1040
|
line = line.replace(/\{(\w+)\}/g, (i, word) => values[word] || '?')
|
|
1041
1041
|
|
|
1042
1042
|
if (bounce_headers_done == false && line == '') {
|
|
@@ -1046,7 +1046,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
1046
1046
|
} else if (bounce_headers_done == true) {
|
|
1047
1047
|
bounce_body_lines.push(line)
|
|
1048
1048
|
}
|
|
1049
|
-
}
|
|
1049
|
+
}
|
|
1050
1050
|
|
|
1051
1051
|
const escaped_chars = {
|
|
1052
1052
|
'&': 'amp',
|
|
@@ -1059,7 +1059,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
1059
1059
|
}
|
|
1060
1060
|
const escape_pattern = new RegExp(`[${Object.keys(escaped_chars).join('')}]`, 'g')
|
|
1061
1061
|
|
|
1062
|
-
|
|
1062
|
+
for (let line of bounce_msg_html_) {
|
|
1063
1063
|
line = line.replace(/\{(\w+)\}/g, (i, word) => {
|
|
1064
1064
|
if (word in values) {
|
|
1065
1065
|
return String(values[word]).replace(escape_pattern, (m) => `&${escaped_chars[m]};`)
|
|
@@ -1069,18 +1069,18 @@ class HMailItem extends events.EventEmitter {
|
|
|
1069
1069
|
})
|
|
1070
1070
|
|
|
1071
1071
|
bounce_html_lines.push(line)
|
|
1072
|
-
}
|
|
1072
|
+
}
|
|
1073
1073
|
|
|
1074
|
-
|
|
1074
|
+
for (const line of bounce_msg_image_) {
|
|
1075
1075
|
bounce_image_lines.push(line)
|
|
1076
|
-
}
|
|
1076
|
+
}
|
|
1077
1077
|
|
|
1078
1078
|
const boundary = `boundary_${utils.uuid()}`
|
|
1079
1079
|
const bounce_body = []
|
|
1080
1080
|
|
|
1081
|
-
|
|
1081
|
+
for (const line of bounce_header_lines) {
|
|
1082
1082
|
bounce_body.push(`${line}${CRLF}`)
|
|
1083
|
-
}
|
|
1083
|
+
}
|
|
1084
1084
|
bounce_body.push(
|
|
1085
1085
|
`Content-Type: multipart/report; report-type=delivery-status;${CRLF} boundary="${boundary}"${CRLF}`,
|
|
1086
1086
|
)
|
|
@@ -1108,18 +1108,18 @@ class HMailItem extends events.EventEmitter {
|
|
|
1108
1108
|
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1109
1109
|
bounce_body.push(`Content-Type: text/plain; charset=us-ascii${CRLF}`)
|
|
1110
1110
|
bounce_body.push(CRLF)
|
|
1111
|
-
|
|
1111
|
+
for (const line of bounce_body_lines) {
|
|
1112
1112
|
bounce_body.push(`${line}${CRLF}`)
|
|
1113
|
-
}
|
|
1113
|
+
}
|
|
1114
1114
|
bounce_body.push(CRLF)
|
|
1115
1115
|
|
|
1116
1116
|
if (bounce_html_lines.length > 1) {
|
|
1117
1117
|
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1118
1118
|
bounce_body.push(`Content-Type: text/html; charset=us-ascii${CRLF}`)
|
|
1119
1119
|
bounce_body.push(CRLF)
|
|
1120
|
-
|
|
1120
|
+
for (const line of bounce_html_lines) {
|
|
1121
1121
|
bounce_body.push(`${line}${CRLF}`)
|
|
1122
|
-
}
|
|
1122
|
+
}
|
|
1123
1123
|
bounce_body.push(CRLF)
|
|
1124
1124
|
bounce_body.push(`--${boundary}${boundary_incr}--${CRLF}`)
|
|
1125
1125
|
|
|
@@ -1128,9 +1128,9 @@ class HMailItem extends events.EventEmitter {
|
|
|
1128
1128
|
bounce_body.push(`--${boundary}${boundary_incr}${CRLF}`)
|
|
1129
1129
|
//bounce_body.push(`Content-Type: text/html; charset=us-ascii${CRLF}`);
|
|
1130
1130
|
//bounce_body.push(CRLF);
|
|
1131
|
-
|
|
1131
|
+
for (const line of bounce_image_lines) {
|
|
1132
1132
|
bounce_body.push(`${line}${CRLF}`)
|
|
1133
|
-
}
|
|
1133
|
+
}
|
|
1134
1134
|
bounce_body.push(CRLF)
|
|
1135
1135
|
bounce_body.push(`--${boundary}${boundary_incr}--${CRLF}`)
|
|
1136
1136
|
}
|
|
@@ -1146,7 +1146,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
1146
1146
|
if (this.todo.queue_time) {
|
|
1147
1147
|
bounce_body.push(`Arrival-Date: ${utils.date_to_str(new Date(this.todo.queue_time))}${CRLF}`)
|
|
1148
1148
|
}
|
|
1149
|
-
this.todo.rcpt_to
|
|
1149
|
+
for (const rcpt_to of this.todo.rcpt_to) {
|
|
1150
1150
|
bounce_body.push(CRLF)
|
|
1151
1151
|
bounce_body.push(`Final-Recipient: rfc822;${rcpt_to.address()}${CRLF}`)
|
|
1152
1152
|
let dsn_action = null
|
|
@@ -1208,16 +1208,16 @@ class HMailItem extends events.EventEmitter {
|
|
|
1208
1208
|
if (diag_code != null) {
|
|
1209
1209
|
bounce_body.push(`Diagnostic-Code: ${diag_code}${CRLF}`)
|
|
1210
1210
|
}
|
|
1211
|
-
}
|
|
1211
|
+
}
|
|
1212
1212
|
bounce_body.push(CRLF)
|
|
1213
1213
|
|
|
1214
1214
|
bounce_body.push(`--${boundary}${CRLF}`)
|
|
1215
1215
|
bounce_body.push(`Content-Description: Undelivered Message Headers${CRLF}`)
|
|
1216
1216
|
bounce_body.push(`Content-Type: text/rfc822-headers${CRLF}`)
|
|
1217
1217
|
bounce_body.push(CRLF)
|
|
1218
|
-
header.header_list
|
|
1218
|
+
for (const line of header.header_list) {
|
|
1219
1219
|
bounce_body.push(line)
|
|
1220
|
-
}
|
|
1220
|
+
}
|
|
1221
1221
|
bounce_body.push(CRLF)
|
|
1222
1222
|
|
|
1223
1223
|
bounce_body.push(`--${boundary}--${CRLF}`)
|
|
@@ -1322,12 +1322,12 @@ class HMailItem extends events.EventEmitter {
|
|
|
1322
1322
|
}
|
|
1323
1323
|
|
|
1324
1324
|
convert_temp_failed_to_bounce(err, extra) {
|
|
1325
|
-
this.todo.rcpt_to
|
|
1325
|
+
for (const rcpt_to of this.todo.rcpt_to) {
|
|
1326
1326
|
rcpt_to.dsn_action = 'failed'
|
|
1327
1327
|
if (rcpt_to.dsn_status) {
|
|
1328
1328
|
rcpt_to.dsn_status = `${rcpt_to.dsn_status}`.replace(/^4/, '5')
|
|
1329
1329
|
}
|
|
1330
|
-
}
|
|
1330
|
+
}
|
|
1331
1331
|
return this.bounce(err, extra)
|
|
1332
1332
|
}
|
|
1333
1333
|
|
|
@@ -1446,9 +1446,9 @@ class HMailItem extends events.EventEmitter {
|
|
|
1446
1446
|
})
|
|
1447
1447
|
function err_handler(err, location) {
|
|
1448
1448
|
logger.error(this, `Error while splitting to new recipients (${location}): ${err}`)
|
|
1449
|
-
hmail.todo.rcpt_to
|
|
1449
|
+
for (const rcpt of hmail.todo.rcpt_to) {
|
|
1450
1450
|
hmail.extend_rcpt_with_dsn(rcpt, DSN.sys_unspecified(`Error splitting to new recipients: ${err}`))
|
|
1451
|
-
}
|
|
1451
|
+
}
|
|
1452
1452
|
hmail.bounce(`Error splitting to new recipients: ${err}`)
|
|
1453
1453
|
}
|
|
1454
1454
|
|
|
@@ -1486,9 +1486,9 @@ class HMailItem extends events.EventEmitter {
|
|
|
1486
1486
|
ws.on('error', (err) => {
|
|
1487
1487
|
logger.error(this, `Unable to write queue file (${fname}): ${err}`)
|
|
1488
1488
|
ws.destroy()
|
|
1489
|
-
hmail.todo.rcpt_to
|
|
1489
|
+
for (const rcpt of hmail.todo.rcpt_to) {
|
|
1490
1490
|
hmail.extend_rcpt_with_dsn(rcpt, DSN.sys_unspecified(`Error re-queueing some recipients: ${err}`))
|
|
1491
|
-
}
|
|
1491
|
+
}
|
|
1492
1492
|
hmail.bounce(`Error re-queueing some recipients: ${err}`)
|
|
1493
1493
|
})
|
|
1494
1494
|
|
package/outbound/index.js
CHANGED
|
@@ -200,16 +200,16 @@ function get_deliveries(transaction) {
|
|
|
200
200
|
|
|
201
201
|
// First get each domain
|
|
202
202
|
const recips = {}
|
|
203
|
-
transaction.rcpt_to
|
|
203
|
+
for (const rcpt of transaction.rcpt_to) {
|
|
204
204
|
const domain = rcpt.host
|
|
205
205
|
if (!recips[domain]) {
|
|
206
206
|
recips[domain] = []
|
|
207
207
|
}
|
|
208
208
|
recips[domain].push(rcpt)
|
|
209
|
-
}
|
|
210
|
-
Object.keys(recips)
|
|
209
|
+
}
|
|
210
|
+
for (const domain of Object.keys(recips)) {
|
|
211
211
|
deliveries.push({ domain, rcpts: recips[domain] })
|
|
212
|
-
}
|
|
212
|
+
}
|
|
213
213
|
return deliveries
|
|
214
214
|
}
|
|
215
215
|
|
package/outbound/tls.js
CHANGED
|
@@ -8,18 +8,6 @@ const hkredis = require('haraka-plugin-redis')
|
|
|
8
8
|
const logger = require('../logger')
|
|
9
9
|
const tls_socket = require('../tls_socket')
|
|
10
10
|
|
|
11
|
-
const inheritable_opts = [
|
|
12
|
-
'key',
|
|
13
|
-
'cert',
|
|
14
|
-
'ciphers',
|
|
15
|
-
'minVersion',
|
|
16
|
-
'dhparam',
|
|
17
|
-
'requestCert',
|
|
18
|
-
'honorCipherOrder',
|
|
19
|
-
'rejectUnauthorized',
|
|
20
|
-
'force_tls_hosts',
|
|
21
|
-
]
|
|
22
|
-
|
|
23
11
|
class OutboundTLS {
|
|
24
12
|
constructor() {
|
|
25
13
|
this.config = config
|
|
@@ -34,37 +22,8 @@ class OutboundTLS {
|
|
|
34
22
|
|
|
35
23
|
load_config() {
|
|
36
24
|
const tls_cfg = tls_socket.load_tls_ini({ role: 'client' })
|
|
37
|
-
|
|
38
|
-
cfg.redis = tls_cfg.redis //
|
|
39
|
-
|
|
40
|
-
for (const opt of inheritable_opts) {
|
|
41
|
-
if (cfg[opt] !== undefined) continue // option set in [outbound]
|
|
42
|
-
if (tls_cfg.main[opt] === undefined) continue // opt unset in tls.ini[main]
|
|
43
|
-
cfg[opt] = tls_cfg.main[opt] // use value from [main] section
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (cfg.key) {
|
|
47
|
-
if (Array.isArray(cfg.key)) {
|
|
48
|
-
cfg.key = cfg.key[0]
|
|
49
|
-
}
|
|
50
|
-
cfg.key = this.config.get(cfg.key, 'binary')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (cfg.dhparam) {
|
|
54
|
-
cfg.dhparam = this.config.get(cfg.dhparam, 'binary')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (cfg.cert) {
|
|
58
|
-
if (Array.isArray(cfg.cert)) {
|
|
59
|
-
cfg.cert = cfg.cert[0]
|
|
60
|
-
}
|
|
61
|
-
cfg.cert = this.config.get(cfg.cert, 'binary')
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!cfg.no_tls_hosts) cfg.no_tls_hosts = []
|
|
65
|
-
if (!cfg.force_tls_hosts) cfg.force_tls_hosts = []
|
|
66
|
-
|
|
67
|
-
this.cfg = cfg
|
|
25
|
+
this.cfg = tls_socket.load_plugin_tls_options(tls_cfg.outbound || {})
|
|
26
|
+
this.cfg.redis = tls_cfg.redis // outbound-only: TLS NO-GO db (don't clone — has methods)
|
|
68
27
|
}
|
|
69
28
|
|
|
70
29
|
init(cb) {
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"server",
|
|
10
10
|
"email"
|
|
11
11
|
],
|
|
12
|
-
"version": "3.1.
|
|
12
|
+
"version": "3.1.7",
|
|
13
13
|
"homepage": "http://haraka.github.io",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -20,58 +20,58 @@
|
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"address-rfc2821": "
|
|
24
|
-
"address-rfc2822": "
|
|
25
|
-
"haraka-config": "
|
|
26
|
-
"haraka-constants": "
|
|
27
|
-
"haraka-dsn": "
|
|
28
|
-
"haraka-email-message": "
|
|
29
|
-
"haraka-net-utils": "
|
|
30
|
-
"haraka-notes": "
|
|
31
|
-
"haraka-plugin-redis": "
|
|
32
|
-
"haraka-results": "
|
|
33
|
-
"haraka-tld": "
|
|
34
|
-
"haraka-utils": "
|
|
23
|
+
"address-rfc2821": "~2.2.0",
|
|
24
|
+
"address-rfc2822": "~2.2.3",
|
|
25
|
+
"haraka-config": "~1.6.0",
|
|
26
|
+
"haraka-constants": "~1.0.7",
|
|
27
|
+
"haraka-dsn": "~1.2.0",
|
|
28
|
+
"haraka-email-message": "~1.3.3",
|
|
29
|
+
"haraka-net-utils": "~1.8.2",
|
|
30
|
+
"haraka-notes": "~1.1.3",
|
|
31
|
+
"haraka-plugin-redis": "~2.0.11",
|
|
32
|
+
"haraka-results": "~2.3.0",
|
|
33
|
+
"haraka-tld": "~1.3.4",
|
|
34
|
+
"haraka-utils": "~1.1.4",
|
|
35
35
|
"ipaddr.js": "~2.4.0",
|
|
36
|
-
"node-gyp": "
|
|
37
|
-
"nopt": "
|
|
36
|
+
"node-gyp": "~12.3.0",
|
|
37
|
+
"nopt": "~10.0.0",
|
|
38
38
|
"redis": "~5.12.1",
|
|
39
|
-
"semver": "
|
|
39
|
+
"semver": "~7.8.0"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
|
-
"@haraka/ocsp": "
|
|
43
|
-
"haraka-plugin-access": "
|
|
44
|
-
"haraka-plugin-aliases": "
|
|
45
|
-
"haraka-plugin-asn": "
|
|
46
|
-
"haraka-plugin-attachment": "
|
|
47
|
-
"haraka-plugin-bounce": "
|
|
48
|
-
"haraka-plugin-clamd": "
|
|
49
|
-
"haraka-plugin-dcc": "
|
|
50
|
-
"haraka-plugin-dkim": "
|
|
51
|
-
"haraka-plugin-dns-list": "
|
|
52
|
-
"haraka-plugin-early_talker": "
|
|
53
|
-
"haraka-plugin-fcrdns": "
|
|
54
|
-
"haraka-plugin-geoip": "
|
|
55
|
-
"haraka-plugin-greylist": "
|
|
56
|
-
"haraka-plugin-headers": "
|
|
57
|
-
"haraka-plugin-helo.checks": "
|
|
58
|
-
"haraka-plugin-karma": "
|
|
59
|
-
"haraka-plugin-known-senders": "
|
|
60
|
-
"haraka-plugin-limit": "
|
|
61
|
-
"haraka-plugin-mail_from.is_resolvable": "
|
|
62
|
-
"haraka-plugin-messagesniffer": "
|
|
63
|
-
"haraka-plugin-qmail-deliverable": "
|
|
64
|
-
"haraka-plugin-relay": "
|
|
65
|
-
"haraka-plugin-rspamd": "
|
|
66
|
-
"haraka-plugin-spamassassin": "
|
|
67
|
-
"haraka-plugin-spf": "
|
|
68
|
-
"haraka-plugin-syslog": "
|
|
69
|
-
"haraka-plugin-uribl": "
|
|
42
|
+
"@haraka/ocsp": "~1.2.0",
|
|
43
|
+
"haraka-plugin-access": "~1.2.0",
|
|
44
|
+
"haraka-plugin-aliases": "~1.0.3",
|
|
45
|
+
"haraka-plugin-asn": "~2.1.0",
|
|
46
|
+
"haraka-plugin-attachment": "~1.2.0",
|
|
47
|
+
"haraka-plugin-bounce": "~2.1.2",
|
|
48
|
+
"haraka-plugin-clamd": "~1.0.2",
|
|
49
|
+
"haraka-plugin-dcc": "~1.0.3",
|
|
50
|
+
"haraka-plugin-dkim": "~1.1.2",
|
|
51
|
+
"haraka-plugin-dns-list": "~1.2.4",
|
|
52
|
+
"haraka-plugin-early_talker": "~1.0.2",
|
|
53
|
+
"haraka-plugin-fcrdns": "~1.1.2",
|
|
54
|
+
"haraka-plugin-geoip": "~1.1.2",
|
|
55
|
+
"haraka-plugin-greylist": "~1.1.1",
|
|
56
|
+
"haraka-plugin-headers": "~1.1.2",
|
|
57
|
+
"haraka-plugin-helo.checks": "~1.1.1",
|
|
58
|
+
"haraka-plugin-karma": "~2.4.1",
|
|
59
|
+
"haraka-plugin-known-senders": "~1.1.4",
|
|
60
|
+
"haraka-plugin-limit": "~1.2.7",
|
|
61
|
+
"haraka-plugin-mail_from.is_resolvable": "~1.2.0",
|
|
62
|
+
"haraka-plugin-messagesniffer": "~1.0.1",
|
|
63
|
+
"haraka-plugin-qmail-deliverable": "~1.3.5",
|
|
64
|
+
"haraka-plugin-relay": "~1.0.2",
|
|
65
|
+
"haraka-plugin-rspamd": "~1.5.0",
|
|
66
|
+
"haraka-plugin-spamassassin": "~1.0.4",
|
|
67
|
+
"haraka-plugin-spf": "~1.2.11",
|
|
68
|
+
"haraka-plugin-syslog": "~1.1.0",
|
|
69
|
+
"haraka-plugin-uribl": "~1.0.10"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@haraka/eslint-config": "
|
|
73
|
-
"haraka-test-fixtures": "
|
|
74
|
-
"mock-require": "
|
|
72
|
+
"@haraka/eslint-config": "~2.0.4",
|
|
73
|
+
"haraka-test-fixtures": "~1.6.0",
|
|
74
|
+
"mock-require": "~3.0.3"
|
|
75
75
|
},
|
|
76
76
|
"bugs": {
|
|
77
77
|
"mail": "haraka.mail@gmail.com",
|
|
@@ -90,7 +90,8 @@
|
|
|
90
90
|
"test": "sh ./run_tests",
|
|
91
91
|
"versions": "npx npm-dep-mgr check",
|
|
92
92
|
"versions:fix": "npx npm-dep-mgr update",
|
|
93
|
-
"test:coverage": "
|
|
93
|
+
"test:coverage": "node --test --test-concurrency=1 --experimental-test-coverage",
|
|
94
|
+
"test:coverage:lcov": "mkdir -p coverage && node --test --test-concurrency=1 --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info"
|
|
94
95
|
},
|
|
95
96
|
"prettier": {
|
|
96
97
|
"singleQuote": true,
|
|
@@ -99,6 +99,12 @@ exports.check_user = function (next, connection, credentials, method) {
|
|
|
99
99
|
(typeof opts === 'object' ? opts.message : opts) ||
|
|
100
100
|
(valid ? '2.7.0 Authentication successful' : '5.7.8 Authentication failed')
|
|
101
101
|
|
|
102
|
+
// The AUTH username is attacker-controlled (base64-decoded). Strip
|
|
103
|
+
// control chars before it is stored in notes or emitted into the
|
|
104
|
+
// Authentication-Results header (header injection).
|
|
105
|
+
// eslint-disable-next-line no-control-regex
|
|
106
|
+
const safe_user = String(credentials[0] ?? '').replace(/[\x00-\x1f\x7f]/g, '')
|
|
107
|
+
|
|
102
108
|
if (valid) {
|
|
103
109
|
connection.relaying = true
|
|
104
110
|
connection.results.add({ name: 'relay' }, { pass: plugin.name })
|
|
@@ -108,14 +114,14 @@ exports.check_user = function (next, connection, credentials, method) {
|
|
|
108
114
|
{
|
|
109
115
|
pass: plugin.name,
|
|
110
116
|
method,
|
|
111
|
-
user:
|
|
117
|
+
user: safe_user,
|
|
112
118
|
},
|
|
113
119
|
)
|
|
114
120
|
|
|
115
121
|
connection.respond(status_code, status_message, () => {
|
|
116
122
|
connection.authheader = '(authenticated bits=0)\n'
|
|
117
123
|
connection.auth_results(`auth=pass (${method.toLowerCase()})`)
|
|
118
|
-
connection.notes.auth_user =
|
|
124
|
+
connection.notes.auth_user = safe_user
|
|
119
125
|
if (!plugin.blankout_password) connection.notes.auth_passwd = credentials[1]
|
|
120
126
|
next(OK)
|
|
121
127
|
})
|
|
@@ -133,7 +139,7 @@ exports.check_user = function (next, connection, credentials, method) {
|
|
|
133
139
|
}
|
|
134
140
|
connection.lognotice(plugin, `delaying for ${delay} seconds`)
|
|
135
141
|
// here we include the username, as shown in RFC 5451 example
|
|
136
|
-
connection.auth_results(`auth=fail (${method.toLowerCase()}) smtp.auth=${
|
|
142
|
+
connection.auth_results(`auth=fail (${method.toLowerCase()}) smtp.auth=${safe_user}`)
|
|
137
143
|
setTimeout(() => {
|
|
138
144
|
connection.respond(status_code, status_message, () => {
|
|
139
145
|
connection.reset_transaction(() => next(OK))
|
|
@@ -93,7 +93,9 @@ exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
|
93
93
|
if (cmd === 'dot') {
|
|
94
94
|
line = '.'
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
// Don't leak proxied SASL credentials (AUTH PLAIN <base64>) to logs
|
|
97
|
+
const safe = line.replace(/^(AUTH\s+\S+\s+).+$/i, '$1[redacted]')
|
|
98
|
+
connection.logprotocol(self, `C: ${safe}`)
|
|
97
99
|
command = cmd.toLowerCase()
|
|
98
100
|
this.write(`${line}\r\n`)
|
|
99
101
|
// Clear response buffer from previous command
|
|
@@ -128,18 +130,19 @@ exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
|
128
130
|
for (const i in response) {
|
|
129
131
|
if (/^STARTTLS/.test(response[i])) {
|
|
130
132
|
if (secure) continue // silly remote, we've already upgraded
|
|
131
|
-
//
|
|
133
|
+
// Opportunistic TLS: a client does not need its own
|
|
134
|
+
// certificate to negotiate TLS, so always STARTTLS when
|
|
135
|
+
// the backend offers it. The local key/cert are only
|
|
136
|
+
// attached if configured (mutual TLS), not required.
|
|
132
137
|
key = self.config.get(self.tls_cfg.main.key || 'tls_key.pem', 'binary')
|
|
133
138
|
cert = self.config.get(self.tls_cfg.main.cert || 'tls_cert.pem', 'binary')
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return
|
|
142
|
-
}
|
|
139
|
+
this.on('secure', () => {
|
|
140
|
+
if (secure) return
|
|
141
|
+
secure = true
|
|
142
|
+
socket.send_command('EHLO', connection.local.host)
|
|
143
|
+
})
|
|
144
|
+
socket.send_command('STARTTLS')
|
|
145
|
+
return
|
|
143
146
|
} else if (/^AUTH /.test(response[i])) {
|
|
144
147
|
// Parse supported AUTH methods
|
|
145
148
|
const parse = /^AUTH (.+)$/.exec(response[i])
|
package/plugins/block_me.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// mail_from.blocklist plugin for this to work fully.
|
|
5
5
|
|
|
6
6
|
const fs = require('node:fs')
|
|
7
|
+
const path = require('node:path')
|
|
7
8
|
const utils = require('haraka-utils')
|
|
8
9
|
|
|
9
10
|
exports.hook_data = (next, connection) => {
|
|
@@ -45,8 +46,9 @@ exports.hook_data_post = function (next, connection) {
|
|
|
45
46
|
|
|
46
47
|
connection.transaction.notes.block_me = 1
|
|
47
48
|
|
|
48
|
-
// add to mail_from.blocklist
|
|
49
|
-
|
|
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) => {
|
|
50
52
|
if (err) {
|
|
51
53
|
connection.logerror(this, `Unable to append to mail_from.blocklist: ${err}`)
|
|
52
54
|
return
|
|
@@ -22,9 +22,11 @@ exports.hook_data_post = (next, connection) => {
|
|
|
22
22
|
let user = connection.notes.auth_user
|
|
23
23
|
let domain
|
|
24
24
|
const idx = user.indexOf('@')
|
|
25
|
-
if (idx) {
|
|
25
|
+
if (idx > 0) {
|
|
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
|
+
// (idx === -1 is "no @"; idx === 0 is a leading @ — neither is
|
|
29
|
+
// a qualified user@domain, so don't split.)
|
|
28
30
|
domain = user.substring(idx)
|
|
29
31
|
user = user.substring(0, idx)
|
|
30
32
|
}
|
package/plugins/process_title.js
CHANGED
|
@@ -64,17 +64,17 @@ exports.hook_init_master = function (next, server) {
|
|
|
64
64
|
server.notes.pt_connections++
|
|
65
65
|
server.notes.pt_concurrent_cluster[msg.wid]++
|
|
66
66
|
count = 0
|
|
67
|
-
Object.keys(server.notes.pt_concurrent_cluster)
|
|
67
|
+
for (const id of Object.keys(server.notes.pt_concurrent_cluster)) {
|
|
68
68
|
count += server.notes.pt_concurrent_cluster[id]
|
|
69
|
-
}
|
|
69
|
+
}
|
|
70
70
|
server.notes.pt_concurrent = count
|
|
71
71
|
break
|
|
72
72
|
case 'process_title.disconnect':
|
|
73
73
|
server.notes.pt_concurrent_cluster[msg.wid]--
|
|
74
74
|
count = 0
|
|
75
|
-
Object.keys(server.notes.pt_concurrent_cluster)
|
|
75
|
+
for (const id of Object.keys(server.notes.pt_concurrent_cluster)) {
|
|
76
76
|
count += server.notes.pt_concurrent_cluster[id]
|
|
77
|
-
}
|
|
77
|
+
}
|
|
78
78
|
server.notes.pt_concurrent = count
|
|
79
79
|
break
|
|
80
80
|
case 'process_title.recipient':
|
|
@@ -109,9 +109,9 @@ exports.hook_init_master = function (next, server) {
|
|
|
109
109
|
delete server.notes.pt_concurrent_cluster[worker.id]
|
|
110
110
|
// Update concurrency
|
|
111
111
|
let count = 0
|
|
112
|
-
Object.keys(server.notes.pt_concurrent_cluster)
|
|
112
|
+
for (const id of Object.keys(server.notes.pt_concurrent_cluster)) {
|
|
113
113
|
count += server.notes.pt_concurrent_cluster[id]
|
|
114
|
-
}
|
|
114
|
+
}
|
|
115
115
|
server.notes.pt_concurrent = count
|
|
116
116
|
server.notes.pt_child_exits++
|
|
117
117
|
})
|