Haraka 3.1.5 → 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/.prettierignore +1 -1
- package/{Changes.md → CHANGELOG.md} +54 -3
- package/CONTRIBUTORS.md +26 -26
- package/Plugins.md +99 -99
- package/README.md +68 -93
- package/SECURITY.md +178 -0
- package/bin/haraka +7 -14
- package/config/plugins +0 -3
- package/config/smtp_forward.ini +10 -0
- package/config/smtp_proxy.ini +10 -0
- package/connection.js +25 -8
- package/docs/Connection.md +126 -39
- package/docs/CoreConfig.md +92 -74
- package/docs/HAProxy.md +41 -25
- package/docs/Logging.md +68 -38
- package/docs/Outbound.md +124 -179
- package/docs/Plugins.md +38 -59
- package/docs/Transaction.md +78 -83
- package/docs/Tutorial.md +122 -209
- package/docs/plugins/aliases.md +1 -141
- package/docs/plugins/auth/auth_ldap.md +2 -39
- package/docs/plugins/max_unrecognized_commands.md +4 -18
- package/docs/plugins/process_title.md +3 -3
- package/docs/plugins/queue/smtp_forward.md +19 -3
- package/docs/plugins/queue/smtp_proxy.md +10 -2
- package/docs/plugins/reseed_rng.md +11 -13
- package/docs/plugins/tls.md +7 -7
- package/docs/plugins/toobusy.md +10 -4
- package/docs/tutorials/SettingUpOutbound.md +40 -48
- package/endpoint.js +32 -2
- package/haraka.js +1 -1
- package/outbound/hmail.js +42 -41
- package/outbound/index.js +7 -4
- package/outbound/tls.js +2 -43
- package/package.json +51 -61
- 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 +22 -10
- 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 +258 -0
- package/test/endpoint.js +27 -0
- package/test/outbound/bounce_net_errors.js +3 -2
- package/test/outbound/hmail.js +19 -0
- package/test/outbound/index.js +189 -0
- package/test/outbound/queue.js +92 -0
- 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 +231 -0
- package/test/smtp_client.js +45 -12
- package/test/tls_socket.js +220 -0
- package/tls_socket.js +52 -2
package/bin/haraka
CHANGED
|
@@ -11,7 +11,6 @@ const os = require('node:os')
|
|
|
11
11
|
|
|
12
12
|
const nopt = require('nopt')
|
|
13
13
|
const utils = require('haraka-utils')
|
|
14
|
-
const sprintf = require('sprintf-js').sprintf
|
|
15
14
|
const base = path.join(__dirname, '..')
|
|
16
15
|
const ver = utils.getVersion(base)
|
|
17
16
|
const knownOpts = {
|
|
@@ -313,15 +312,7 @@ if (parsed.version) {
|
|
|
313
312
|
;(async () => {
|
|
314
313
|
const qlist = await outbound.list_queue()
|
|
315
314
|
for (const todo of qlist) {
|
|
316
|
-
console.log(
|
|
317
|
-
sprintf(
|
|
318
|
-
'Q: %s rcpt:%d from:%s domain:%s',
|
|
319
|
-
todo.file,
|
|
320
|
-
todo.rcpt_to.length,
|
|
321
|
-
todo.mail_from.toString(),
|
|
322
|
-
todo.domain,
|
|
323
|
-
),
|
|
324
|
-
)
|
|
315
|
+
console.log(`Q: ${todo.file} rcpt:${todo.rcpt_to.length} from:${todo.mail_from} domain:${todo.domain}`)
|
|
325
316
|
}
|
|
326
317
|
process.exit()
|
|
327
318
|
})()
|
|
@@ -379,12 +370,14 @@ if (parsed.version) {
|
|
|
379
370
|
console.log('')
|
|
380
371
|
for (const hook of getHooks()) {
|
|
381
372
|
if (!plugins.registered_hooks[hook]) continue
|
|
382
|
-
console.log(
|
|
383
|
-
console.log(
|
|
384
|
-
console.log(
|
|
373
|
+
console.log(`Hook: ${hook} `.padEnd(80, '-'))
|
|
374
|
+
console.log(`${'Plugin'.padEnd(35)} ${'Method'.padEnd(35)} ${'Prio'.padEnd(4)} T/O`)
|
|
375
|
+
console.log('-'.repeat(80))
|
|
385
376
|
for (let p = 0; p < plugins.registered_hooks[hook].length; p++) {
|
|
386
377
|
const item = plugins.registered_hooks[hook][p]
|
|
387
|
-
console.log(
|
|
378
|
+
console.log(
|
|
379
|
+
`${item.plugin.padEnd(35)} ${item.method.padEnd(35)} ${String(item.priority).padStart(4)} ${String(item.timeout).padStart(3)}`,
|
|
380
|
+
)
|
|
388
381
|
}
|
|
389
382
|
console.log('')
|
|
390
383
|
}
|
package/config/plugins
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
# status
|
|
13
13
|
# process_title
|
|
14
14
|
# syslog
|
|
15
|
-
# watch
|
|
16
15
|
|
|
17
16
|
# CONNECT
|
|
18
17
|
# ----------
|
|
@@ -20,7 +19,6 @@
|
|
|
20
19
|
# karma
|
|
21
20
|
# relay
|
|
22
21
|
# access
|
|
23
|
-
# p0f
|
|
24
22
|
# geoip
|
|
25
23
|
# asn
|
|
26
24
|
# fcrdns
|
|
@@ -49,7 +47,6 @@ mail_from.is_resolvable
|
|
|
49
47
|
# At least one rcpt_to plugin is REQUIRED for inbound email.
|
|
50
48
|
rcpt_to.in_host_list
|
|
51
49
|
# qmail-deliverable
|
|
52
|
-
# rcpt_to.routes
|
|
53
50
|
|
|
54
51
|
# DATA
|
|
55
52
|
# ----------
|
package/config/smtp_forward.ini
CHANGED
|
@@ -19,3 +19,13 @@ port=2555
|
|
|
19
19
|
; should outbound messages be delivered by smtp_forward?
|
|
20
20
|
; see #1472 and #2795
|
|
21
21
|
; enable_outbound=false
|
|
22
|
+
|
|
23
|
+
; Options here override the same option in tls.ini [main]
|
|
24
|
+
[tls]
|
|
25
|
+
; rejectUnauthorized=true
|
|
26
|
+
; minVersion=TLSv1.2
|
|
27
|
+
; ciphers=ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
|
|
28
|
+
; key=outbound_tls_key.pem
|
|
29
|
+
; cert=outbound_tls_cert.pem
|
|
30
|
+
; no_tls_hosts[]=10.0.0.5
|
|
31
|
+
; force_tls_hosts[]=mx.example.com
|
package/config/smtp_proxy.ini
CHANGED
|
@@ -15,3 +15,13 @@ port=2555
|
|
|
15
15
|
; should outbound messages be delivered by smtp_proxy?
|
|
16
16
|
; see https://github.com/haraka/Haraka/issues/1472
|
|
17
17
|
; enable_outbound=true
|
|
18
|
+
|
|
19
|
+
; Options here override the same option in tls.ini [main]
|
|
20
|
+
[tls]
|
|
21
|
+
; rejectUnauthorized=true
|
|
22
|
+
; minVersion=TLSv1.2
|
|
23
|
+
; ciphers=ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
|
|
24
|
+
; key=outbound_tls_key.pem
|
|
25
|
+
; cert=outbound_tls_cert.pem
|
|
26
|
+
; no_tls_hosts[]=10.0.0.5
|
|
27
|
+
; force_tls_hosts[]=mx.example.com
|
package/connection.js
CHANGED
|
@@ -329,7 +329,7 @@ class Connection {
|
|
|
329
329
|
} catch (err) {
|
|
330
330
|
if (err.stack) {
|
|
331
331
|
this.logerror(`${method} failed: ${err}`)
|
|
332
|
-
err.stack.split('\n')
|
|
332
|
+
for (const line of err.stack.split('\n')) this.logerror(line)
|
|
333
333
|
} else {
|
|
334
334
|
this.logerror(`${method} failed: ${err}`)
|
|
335
335
|
}
|
|
@@ -1234,6 +1234,13 @@ class Connection {
|
|
|
1234
1234
|
if (!host) {
|
|
1235
1235
|
return this.respond(501, 'HELO requires domain/address - see RFC-2821 4.1.1.1')
|
|
1236
1236
|
}
|
|
1237
|
+
// RFC 5321 §4.1.1.1: the domain/address-literal cannot contain
|
|
1238
|
+
// control characters. process_line() only strips the first \r?\n,
|
|
1239
|
+
// so a bare \r could otherwise survive into hello.host and the
|
|
1240
|
+
// generated Received: header / logs (header injection).
|
|
1241
|
+
if (/[\x00-\x1f\x7f]/.test(host)) {
|
|
1242
|
+
return this.respond(501, 'HELO syntax error - see RFC-2821 4.1.1.1')
|
|
1243
|
+
}
|
|
1237
1244
|
|
|
1238
1245
|
this.reset_transaction(() => {
|
|
1239
1246
|
this.set('hello', 'verb', 'HELO')
|
|
@@ -1248,6 +1255,10 @@ class Connection {
|
|
|
1248
1255
|
if (!host) {
|
|
1249
1256
|
return this.respond(501, 'EHLO requires domain/address - see RFC-2821 4.1.1.1')
|
|
1250
1257
|
}
|
|
1258
|
+
// RFC 5321 §4.1.1.1: reject control chars (see cmd_helo).
|
|
1259
|
+
if (/[\x00-\x1f\x7f]/.test(host)) {
|
|
1260
|
+
return this.respond(501, 'EHLO syntax error - see RFC-2821 4.1.1.1')
|
|
1261
|
+
}
|
|
1251
1262
|
|
|
1252
1263
|
this.reset_transaction(() => {
|
|
1253
1264
|
this.set('hello', 'verb', 'EHLO')
|
|
@@ -1320,10 +1331,10 @@ class Connection {
|
|
|
1320
1331
|
|
|
1321
1332
|
// Get rest of key=value pairs
|
|
1322
1333
|
const params = {}
|
|
1323
|
-
|
|
1334
|
+
for (const param of results) {
|
|
1324
1335
|
const kv = param.match(/^([^=]+)(?:=(.+))?$/)
|
|
1325
1336
|
if (kv) params[kv[1].toUpperCase()] = kv[2] || null
|
|
1326
|
-
}
|
|
1337
|
+
}
|
|
1327
1338
|
|
|
1328
1339
|
// Parameters are only valid if EHLO was sent
|
|
1329
1340
|
if (!this.esmtp && Object.keys(params).length > 0) {
|
|
@@ -1379,10 +1390,10 @@ class Connection {
|
|
|
1379
1390
|
|
|
1380
1391
|
// Get rest of key=value pairs
|
|
1381
1392
|
const params = {}
|
|
1382
|
-
|
|
1393
|
+
for (const param of results) {
|
|
1383
1394
|
const kv = param.match(/^([^=]+)(?:=(.+))?$/)
|
|
1384
1395
|
if (kv) params[kv[1].toUpperCase()] = kv[2] || null
|
|
1385
|
-
}
|
|
1396
|
+
}
|
|
1386
1397
|
|
|
1387
1398
|
// Parameters are only valid if EHLO was sent
|
|
1388
1399
|
if (!this.esmtp && Object.keys(params).length > 0) {
|
|
@@ -1433,17 +1444,23 @@ class Connection {
|
|
|
1433
1444
|
this.transaction.notes.authentication_results = []
|
|
1434
1445
|
}
|
|
1435
1446
|
|
|
1447
|
+
// Strip CR/LF and other control chars: an attacker-influenced
|
|
1448
|
+
// value (e.g. a failed AUTH username, see auth_base) must not be
|
|
1449
|
+
// able to inject extra header lines into Authentication-Results.
|
|
1450
|
+
// The legitimate folding (;\r\n\t) is added by the join below.
|
|
1451
|
+
const ar_clean = (s) => String(s).replace(/[\x00-\x1f\x7f]/g, '')
|
|
1452
|
+
|
|
1436
1453
|
// if message, store it in the appropriate note
|
|
1437
1454
|
if (message) {
|
|
1438
1455
|
if (has_tran === true) {
|
|
1439
|
-
this.transaction.notes.authentication_results.push(message)
|
|
1456
|
+
this.transaction.notes.authentication_results.push(ar_clean(message))
|
|
1440
1457
|
} else {
|
|
1441
|
-
this.notes.authentication_results.push(message)
|
|
1458
|
+
this.notes.authentication_results.push(ar_clean(message))
|
|
1442
1459
|
}
|
|
1443
1460
|
}
|
|
1444
1461
|
|
|
1445
1462
|
// assemble the new header
|
|
1446
|
-
let header = [this.local.host, ...this.notes.authentication_results]
|
|
1463
|
+
let header = [ar_clean(this.local.host), ...this.notes.authentication_results]
|
|
1447
1464
|
if (has_tran === true) {
|
|
1448
1465
|
header = [...header, ...this.transaction.notes.authentication_results]
|
|
1449
1466
|
}
|
package/docs/Connection.md
CHANGED
|
@@ -1,66 +1,153 @@
|
|
|
1
1
|
# Connection Object
|
|
2
2
|
|
|
3
|
-
For each connection to Haraka there is one connection object.
|
|
3
|
+
For each connection to Haraka there is one connection object. It is the first argument passed to almost every plugin hook and is the primary context object plugins use to inspect and act on the SMTP session.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Properties
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### connection.uuid
|
|
8
8
|
|
|
9
|
-
A unique UUID for this connection.
|
|
9
|
+
A unique UUID for this connection. Used as the connection identifier in logs and inherited by `transaction.uuid`.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### connection.remote
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- host - reverse DNS of the remote hosts IP
|
|
15
|
-
- is_private - true if the remote IP is from a private (loopback, RFC 1918, link local, etc.) IP address.
|
|
16
|
-
- is_local - true if the remote IP is localhost (loopback, link local)
|
|
13
|
+
Information about the host connecting to Haraka.
|
|
17
14
|
|
|
18
|
-
-
|
|
15
|
+
- `ip` — remote IP address
|
|
16
|
+
- `port` — remote TCP port
|
|
17
|
+
- `host` — reverse DNS of the remote IP (populated by the `connect.rdns_access` / `connect` hooks)
|
|
18
|
+
- `info` — free-form descriptor (e.g. populated by FCrDNS)
|
|
19
|
+
- `closed` — `true` once the remote end has dropped the connection
|
|
20
|
+
- `is_private` — `true` if the remote IP is in a private range (RFC 1918, loopback, link-local, etc.)
|
|
21
|
+
- `is_local` — `true` if the remote IP is localhost / loopback
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
- port - the port number handling the connection.
|
|
22
|
-
- host - the rDNS host name of the local IP
|
|
23
|
+
### connection.local
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Information about the Haraka server endpoint handling this connection.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
- `ip` — the IP of the Haraka server, as reported by the OS
|
|
28
|
+
- `port` — the port number handling the connection
|
|
29
|
+
- `host` — the primary host name of the Haraka server
|
|
30
|
+
- `info` — `Haraka` (with `/<version>` appended when `headers.show_version` is enabled in `connection.ini`)
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
### connection.hello
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
- host - The hostname given with HELO or EHLO
|
|
34
|
+
The greeting given by the client.
|
|
34
35
|
|
|
35
|
-
-
|
|
36
|
+
- `verb` — `EHLO` or `HELO`, whichever the client used
|
|
37
|
+
- `host` — the hostname argument
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
### connection.tls
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
State of the TLS layer on this connection.
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
- `enabled` — `true` once STARTTLS has been negotiated (or the listener is `smtps`)
|
|
44
|
+
- `advertised` — `true` if Haraka advertised STARTTLS in the EHLO response
|
|
45
|
+
- `verified` — `true` if the peer certificate validated against the configured CAs
|
|
46
|
+
- `cipher` — the negotiated cipher object (`name`, `version`, …)
|
|
47
|
+
- `verifyError` — the verification error, if any
|
|
48
|
+
- `peerCertificate` — the parsed peer certificate (when client certs are used)
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
### connection.proxy
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
deliver mails outbound). This is normally set by SMTP AUTH, or sometimes via
|
|
49
|
-
an IP address check.
|
|
52
|
+
Proxy-protocol state, set when the connection arrived via HAProxy (see [HAProxy.md](HAProxy.md)).
|
|
50
53
|
|
|
51
|
-
-
|
|
54
|
+
- `allowed` — `true` if the remote IP is in the `haproxy.hosts` allow-list
|
|
55
|
+
- `ip` — the proxy server's IP (the real client IP appears in `connection.remote.ip` once PROXY is parsed)
|
|
56
|
+
- `type` — currently `null` or `'haproxy'`
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
verbatim as it was sent. Can be useful in certain botnet detection techniques.
|
|
58
|
+
### connection.notes
|
|
55
59
|
|
|
56
|
-
- connection.
|
|
60
|
+
A plain object that persists for the lifetime of the connection. Use it to share state between plugin hooks. For structured per-test results prefer `connection.results`. See also [haraka-notes](https://github.com/haraka/haraka-notes).
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
### connection.results
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
Structured store for plugin results. See [haraka-results](https://github.com/haraka/haraka-results).
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
### connection.transaction
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
The current `Transaction` object. Valid between `MAIL FROM` and the end of `queue` / `RSET` (or until `MAIL FROM` is rejected). See [Transaction.md](Transaction.md).
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
### connection.relaying
|
|
71
|
+
|
|
72
|
+
Boolean. `true` if this connection is allowed to relay (i.e. deliver mail outbound). Normally set by an auth plugin or an IP allow-list. Reading or writing this property transparently routes through the current transaction when one exists, so the flag survives across multiple messages in a single connection only when set on the connection.
|
|
73
|
+
|
|
74
|
+
### connection.capabilities
|
|
75
|
+
|
|
76
|
+
Array of ESMTP capabilities advertised in the EHLO response (e.g. `['PIPELINING', '8BITMIME', 'SIZE 0', 'STARTTLS', 'AUTH PLAIN LOGIN']`). Plugins may push additional capability strings during the `capabilities` hook.
|
|
77
|
+
|
|
78
|
+
### connection.esmtp
|
|
79
|
+
|
|
80
|
+
`true` if the client used `EHLO` (as opposed to `HELO`).
|
|
81
|
+
|
|
82
|
+
### connection.pipelining
|
|
83
|
+
|
|
84
|
+
`true` once Haraka has advertised, and the client has used, SMTP pipelining.
|
|
85
|
+
|
|
86
|
+
### connection.early_talker
|
|
87
|
+
|
|
88
|
+
`true` if the client sent data before Haraka issued its banner — a
|
|
89
|
+
common spam-bot signal.
|
|
90
|
+
|
|
91
|
+
### connection.tran_count
|
|
92
|
+
|
|
93
|
+
Number of transactions completed on this connection.
|
|
94
|
+
|
|
95
|
+
### connection.rcpt_count / connection.msg_count
|
|
96
|
+
|
|
97
|
+
Per-disposition counters (`accept`, `tempfail`, `reject`) tracking
|
|
98
|
+
recipients and full messages on this connection.
|
|
99
|
+
|
|
100
|
+
### connection.start_time
|
|
101
|
+
|
|
102
|
+
Connection start time, in epoch milliseconds (`Date.now()`).
|
|
103
|
+
|
|
104
|
+
### connection.last_response
|
|
105
|
+
|
|
106
|
+
The last SMTP response line Haraka sent to the client.
|
|
107
|
+
|
|
108
|
+
### connection.last_reject
|
|
109
|
+
|
|
110
|
+
The text of the last rejection issued to this client (used by
|
|
111
|
+
`max_unrecognized_commands` and similar throttling plugins).
|
|
112
|
+
|
|
113
|
+
### connection.errors
|
|
114
|
+
|
|
115
|
+
Count of protocol errors on this connection.
|
|
116
|
+
|
|
117
|
+
### connection.current_line
|
|
118
|
+
|
|
119
|
+
Low-level. The current line as sent by the remote end, verbatim. Useful
|
|
120
|
+
for botnet fingerprinting.
|
|
121
|
+
|
|
122
|
+
### connection.state
|
|
123
|
+
|
|
124
|
+
The connection's protocol state — one of the values in `haraka-constants`'s `connection.state` table (`PAUSE`, `CMD`, `LOOP`, `DATA`, `DISCONNECTING`, `DISCONNECTED`).
|
|
125
|
+
|
|
126
|
+
## Methods
|
|
127
|
+
|
|
128
|
+
### connection.respond(code, msg, cb)
|
|
129
|
+
|
|
130
|
+
Send an SMTP response to the client. `code` is the numeric SMTP code, `msg` is the human-readable text (a string or an array of strings for a multi-line response). The callback fires when the response has been written.
|
|
131
|
+
|
|
132
|
+
### connection.disconnect()
|
|
133
|
+
|
|
134
|
+
Close the connection after running the `disconnect` hook.
|
|
135
|
+
|
|
136
|
+
### connection.reset_transaction(cb)
|
|
137
|
+
|
|
138
|
+
Tear down the current transaction (equivalent to `RSET`) and invoke `cb` when complete.
|
|
139
|
+
|
|
140
|
+
### connection.set(path, value)
|
|
141
|
+
|
|
142
|
+
Assign a nested property safely, e.g. `connection.set('remote.host', 'mx.example.com')`. Setting `remote.ip`
|
|
143
|
+
automatically recomputes `remote.is_private` / `remote.is_local`.
|
|
144
|
+
|
|
145
|
+
### connection.get(path)
|
|
146
|
+
|
|
147
|
+
Read a nested property, returning `undefined` if any segment is missing.
|
|
148
|
+
|
|
149
|
+
### connection.loginfo / lognotice / logwarn / logerror / logdebug / logcrit / logalert / logemerg / logprotocol / logdata
|
|
150
|
+
|
|
151
|
+
Log at the named level. Each takes either `(msg)` or `(plugin, msg, data)`.
|
|
152
|
+
|
|
153
|
+
See [Logging.md](Logging.md).
|
package/docs/CoreConfig.md
CHANGED
|
@@ -1,76 +1,94 @@
|
|
|
1
1
|
# Core Configuration Files
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
If either of these files
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- plugins
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
3
|
+
Haraka reads the configuration files in this document directly. Plugins typically own their own files (named after the plugin); see the individual plugin docs.
|
|
4
|
+
|
|
5
|
+
See also [Logging](Logging.md), [HAProxy](HAProxy.md), and [Outbound](Outbound.md).
|
|
6
|
+
|
|
7
|
+
## smtp.yaml / smtp.json
|
|
8
|
+
|
|
9
|
+
If either of these files exists it is loaded first. It uses the YAML/JSON override mechanism from [haraka-config](https://github.com/haraka/haraka-config) and can provide the entire configuration as a single file.
|
|
10
|
+
|
|
11
|
+
## plugins
|
|
12
|
+
|
|
13
|
+
A newline-delimited list of plugins to load. Lines starting with `#` are ignored.
|
|
14
|
+
|
|
15
|
+
## smtp.ini
|
|
16
|
+
|
|
17
|
+
Controls the SMTP listener and the master process.
|
|
18
|
+
|
|
19
|
+
| Key | Default | Description |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `listen` | `[::0]:25` | Comma-separated `IP:port` pairs to listen on (e.g. `127.0.0.1:25,127.0.0.1:587`). |
|
|
22
|
+
| `listen_host` / `port` | — | Legacy. If set, a `<listen_host>:<port>` entry is prepended to `listen`. Prefer `listen`. |
|
|
23
|
+
| `smtps_port` | `465` | Port used by the optional implicit-TLS listener. |
|
|
24
|
+
| `public_ip` | none | The server's public IP. Helps NAT-aware plugins (SPF, GeoIP) when Haraka is behind NAT. If `stun` is on `$PATH` Haraka will try to discover it automatically. |
|
|
25
|
+
| `inactivity_timeout` | `300` | Idle seconds before a client socket is dropped. |
|
|
26
|
+
| `nodes` | `1` | Number of worker processes to fork. The string `cpus` forks one per CPU. |
|
|
27
|
+
| `user` / `group` | — | User and group to drop privileges to (name or numeric ID). |
|
|
28
|
+
| `ignore_bad_plugins` | `0` | If `1`, Haraka starts even if some plugins fail to compile. |
|
|
29
|
+
| `daemonize` | `false` | If `true`, fork into the background at start-up. |
|
|
30
|
+
| `daemon_log_file` | `/var/log/haraka.log` | Where to redirect stdout/stderr when daemonized. |
|
|
31
|
+
| `daemon_pid_file` | `/var/run/haraka.pid` | Where to write the PID file. |
|
|
32
|
+
| `graceful_shutdown` | `false` | If `true`, wait for in-flight sockets on shutdown. |
|
|
33
|
+
| `force_shutdown_timeout` | `30` | Seconds to wait before forcing shutdown. |
|
|
34
|
+
|
|
35
|
+
## me
|
|
36
|
+
|
|
37
|
+
A single-line file containing the server name used in `Received:` headers
|
|
38
|
+
and elsewhere. Defaults to `hostname(1)`.
|
|
39
|
+
|
|
40
|
+
## connection.ini
|
|
41
|
+
|
|
42
|
+
Per-connection limits and behaviours. See inline comments in the shipped
|
|
43
|
+
`config/connection.ini` for full details.
|
|
44
|
+
|
|
45
|
+
| Section / Key | Default | Description |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `main.spool_dir` | `/tmp` | Directory for temporary spool files. |
|
|
48
|
+
| `main.spool_after` | `-1` | Size (bytes) at which to spool the message to disk. `-1` never spools; `0` always spools. |
|
|
49
|
+
| `main.strict_rfc1869` | `false` | Reject `MAIL FROM` / `RCPT TO` that violates RFC 1869/821 (spurious spaces, missing brackets). |
|
|
50
|
+
| `main.smtputf8` | `true` | Advertise `SMTPUTF8` (RFC 6531). |
|
|
51
|
+
| `haproxy.hosts` | empty | Array of IPs/CIDRs allowed to send the PROXY protocol. See [HAProxy.md](HAProxy.md). |
|
|
52
|
+
| `headers.add_received` | `true` | Add a `Received:` header to incoming mail. |
|
|
53
|
+
| `headers.clean_auth_results` | `true` | Strip inbound `Authentication-Results:` headers before plugins run. |
|
|
54
|
+
| `headers.show_version` | `true` | Include the Haraka version in the SMTP banner and the `Received:` header. |
|
|
55
|
+
| `headers.max_lines` | `1000` | Maximum number of header lines accepted. |
|
|
56
|
+
| `headers.max_received` | `100` | Maximum number of `Received:` headers before mail is rejected as looping. |
|
|
57
|
+
| `max.bytes` | `26214400` | Maximum message size (advertised as the `SIZE` ESMTP extension). |
|
|
58
|
+
| `max.line_length` | `512` | SMTP command line length cap. Clients exceeding this are dropped with `521`. |
|
|
59
|
+
| `max.data_line_length` | `992` | Maximum line length in `DATA`. Longer lines are wrapped with `CRLF SPACE` (Sendmail behaviour) and `transaction.notes.data_line_length_exceeded` is set. |
|
|
60
|
+
| `max.mime_parts` | `1000` | Maximum MIME parts per message. |
|
|
61
|
+
| `message.greeting` | — | Array. Lines used as the SMTP greeting banner. |
|
|
62
|
+
| `message.helo` | `Haraka is at your service.` | Reply text for `HELO` / `EHLO`. |
|
|
63
|
+
| `message.close` | `closing connection. …` | Reply text on `QUIT`. |
|
|
64
|
+
| `uuid.banner_chars` | `6` | Number of UUID chars included in the SMTP banner (`0` to disable, `40` for the full UUID). |
|
|
65
|
+
| `uuid.deny_chars` | `0` | Number of UUID chars prepended (in brackets) to deny messages. |
|
|
66
|
+
|
|
67
|
+
## plugin_timeout
|
|
68
|
+
|
|
69
|
+
Single-integer file. Seconds to allow a plugin hook to run before Haraka
|
|
70
|
+
automatically advances to the next hook. Default: `30`.
|
|
71
|
+
|
|
72
|
+
A per-plugin override may be placed at `config/<plugin>.timeout`. A value
|
|
73
|
+
of `0` disables the timeout for that plugin — use with care. Plugins in
|
|
74
|
+
subdirectories need a matching path: `queue/smtp_forward` looks for
|
|
75
|
+
`config/queue/smtp_forward.timeout`.
|
|
76
|
+
|
|
77
|
+
## outbound.ini
|
|
78
|
+
|
|
79
|
+
Configures the outbound delivery engine. The most common keys are listed
|
|
80
|
+
below; see [Outbound.md](Outbound.md) for the full set.
|
|
81
|
+
|
|
82
|
+
| Key | Default | Description |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `disabled` | `false` | Disable outbound delivery entirely. |
|
|
85
|
+
| `concurrency_max` | `100` | Maximum simultaneous outbound deliveries. |
|
|
86
|
+
| `enable_tls` | `true` | Use STARTTLS opportunistically on outbound deliveries. |
|
|
87
|
+
| `maxTempFailures` | `13` | Number of temporary failures before a message bounces. |
|
|
88
|
+
| `always_split` | `false` | Create one queue file per recipient instead of one per destination domain. |
|
|
89
|
+
| `received_header` | `Haraka outbound` | Value used in the outbound `Received:` header. |
|
|
90
|
+
| `inet_prefer` | `default` | IP family preference for MX lookups: `v4`, `v6`, or `default` (OS preference). |
|
|
91
|
+
|
|
92
|
+
## outbound.bounce_message
|
|
93
|
+
|
|
94
|
+
Template used when an outbound message bounces. The default is usually fine. Available template variables are documented in the source of `outbound/hmail.js`.
|
package/docs/HAProxy.md
CHANGED
|
@@ -1,43 +1,59 @@
|
|
|
1
|
-
# HAProxy PROXY
|
|
1
|
+
# HAProxy PROXY Protocol Support
|
|
2
2
|
|
|
3
|
-
Haraka supports PROXY protocol
|
|
3
|
+
Haraka supports version 1 (text format) of the HAProxy [PROXY protocol][proxy-spec], which allows an upstream proxy to tell Haraka the real client IP and port. DNSBLs, allow-lists, and other IP-based plugins then see the original client rather than the proxy.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The PROXY v2 binary header is not currently supported.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Configuration
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
PROXY support is disabled by default. To enable it, list the IPs (or CIDRs) of trusted proxies in `connection.ini`:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```ini
|
|
12
|
+
[haproxy]
|
|
13
|
+
hosts[] = 192.0.2.4
|
|
14
|
+
hosts[] = 192.0.2.5/30
|
|
15
|
+
hosts[] = 2001:db8::1
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Connections from any other IP get a `DENYSOFTDISCONNECT` if they send a `PROXY` command. `DENYSOFT` is deliberate — it avoids permanently rejecting valid mail when a misconfiguration causes a legitimate proxy to fall outside the allow-list.
|
|
19
|
+
|
|
20
|
+
When a listed proxy connects, Haraka **does not** send the SMTP banner; it waits for the `PROXY` command. If none arrives within 30 seconds the connection is closed with `421 PROXY timed out`.
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
## What plugins see after PROXY is parsed
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
| Property | Value |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `connection.remote.ip` / `.port` | the **real** client address from the PROXY header |
|
|
27
|
+
| `connection.local.ip` / `.port` | the destination IP/port from the PROXY header |
|
|
28
|
+
| `connection.proxy.allowed` | `true` |
|
|
29
|
+
| `connection.proxy.ip` | the proxy's address (i.e. the original socket peer) |
|
|
30
|
+
| `connection.proxy.type` | `'haproxy'` |
|
|
31
|
+
| `connection.notes.proxy` | full parsed record: `{ type, proto, src_ip, src_port, dst_ip, dst_port, proxy_ip }` |
|
|
16
32
|
|
|
17
|
-
HAProxy
|
|
33
|
+
## HAProxy configuration
|
|
18
34
|
|
|
19
|
-
|
|
35
|
+
You need HAProxy ≥ 1.5. The `send-proxy` option on each backend server tells HAProxy to emit the v1 header on every connection.
|
|
20
36
|
|
|
21
37
|
```
|
|
22
38
|
listen smtp :25
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
server smtp2 ip.of.haraka.server2:25 check-send-proxy check inter 10s send-proxy
|
|
29
|
-
server smtp3 ip.of.haraka.server3:25 check-send-proxy check inter 10s send-proxy
|
|
30
|
-
server smtp4 ip.of.haraka.server4:25 check-send-proxy check inter 10s send-proxy
|
|
31
|
-
server smtp5 ip.of.haraka.server5:25 check-send-proxy check inter 10s send-proxy
|
|
39
|
+
mode tcp
|
|
40
|
+
option tcplog
|
|
41
|
+
balance roundrobin
|
|
42
|
+
server smtp1 ip.of.haraka1:25 check-send-proxy check inter 10s send-proxy
|
|
43
|
+
server smtp2 ip.of.haraka2:25 check-send-proxy check inter 10s send-proxy
|
|
32
44
|
```
|
|
33
45
|
|
|
34
|
-
The
|
|
46
|
+
The `check-send-proxy` flag is required for HAProxy's health checks because Haraka does not respond with a banner before the PROXY header arrives.
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
### Health checks
|
|
49
|
+
|
|
50
|
+
`option smtpchk` drops the connection mid-handshake and shows up as `CONNRESET` in Haraka's logs. A cleaner check is to use `tcp-check` and politely close with `QUIT`:
|
|
37
51
|
|
|
38
52
|
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
option tcp-check
|
|
54
|
+
tcp-check expect rstring ^220
|
|
55
|
+
tcp-check send QUIT\r\n
|
|
56
|
+
tcp-check expect rstring ^221
|
|
43
57
|
```
|
|
58
|
+
|
|
59
|
+
[proxy-spec]: https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt
|