Haraka 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.githooks/pre-commit +41 -0
  2. package/.prettierignore +1 -0
  3. package/.qlty/.gitignore +7 -0
  4. package/.qlty/configs/.shellcheckrc +1 -0
  5. package/.qlty/qlty.toml +15 -0
  6. package/CHANGELOG.md +29 -2
  7. package/CONTRIBUTORS.md +5 -5
  8. package/README.md +6 -3
  9. package/bin/haraka +12 -4
  10. package/config/connection.ini +6 -0
  11. package/connection.js +67 -68
  12. package/contrib/bsd-rc.d/haraka +2 -0
  13. package/docs/CoreConfig.md +2 -0
  14. package/docs/HAProxy.md +4 -1
  15. package/eslint.config.mjs +2 -30
  16. package/haraka.js +2 -2
  17. package/line_socket.js +6 -33
  18. package/outbound/hmail.js +18 -29
  19. package/outbound/index.js +3 -3
  20. package/outbound/queue.js +8 -5
  21. package/package.json +51 -48
  22. package/plugins/auth/auth_proxy.js +7 -4
  23. package/plugins/block_me.js +1 -1
  24. package/plugins/delay_deny.js +1 -1
  25. package/plugins/queue/qmail-queue.js +1 -1
  26. package/plugins/queue/quarantine.js +5 -5
  27. package/plugins/queue/smtp_bridge.js +1 -1
  28. package/plugins/queue/smtp_proxy.js +2 -2
  29. package/plugins/status.js +2 -2
  30. package/plugins/toobusy.js +1 -1
  31. package/plugins.js +4 -3
  32. package/server.js +172 -28
  33. package/smtp_client.js +2 -1
  34. package/test/connection.js +119 -2
  35. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  36. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  37. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  38. package/test/fixtures/line_socket.js +1 -1
  39. package/test/fixtures/util_hmailitem.js +2 -3
  40. package/test/outbound/index.js +6 -7
  41. package/test/outbound/qfile.js +1 -1
  42. package/test/outbound/queue.js +2 -2
  43. package/test/plugins/auth/auth_base.js +17 -17
  44. package/test/plugins/auth/auth_bridge.js +3 -3
  45. package/test/plugins/auth/auth_vpopmaild.js +3 -3
  46. package/test/plugins/auth/flat_file.js +16 -21
  47. package/test/plugins/block_me.js +7 -23
  48. package/test/plugins/data.signatures.js +17 -20
  49. package/test/plugins/delay_deny.js +3 -4
  50. package/test/plugins/prevent_credential_leaks.js +17 -21
  51. package/test/plugins/process_title.js +12 -6
  52. package/test/plugins/queue/deliver.js +7 -8
  53. package/test/plugins/queue/discard.js +3 -4
  54. package/test/plugins/queue/lmtp.js +5 -6
  55. package/test/plugins/queue/qmail-queue.js +7 -8
  56. package/test/plugins/queue/quarantine.js +3 -4
  57. package/test/plugins/queue/smtp_bridge.js +5 -7
  58. package/test/plugins/queue/smtp_forward.js +49 -60
  59. package/test/plugins/queue/smtp_proxy.js +6 -7
  60. package/test/plugins/rcpt_to.host_list_base.js +6 -9
  61. package/test/plugins/rcpt_to.in_host_list.js +6 -11
  62. package/test/plugins/record_envelope_addresses.js +33 -60
  63. package/test/plugins/reseed_rng.js +3 -3
  64. package/test/plugins/status.js +4 -5
  65. package/test/plugins/tarpit.js +3 -4
  66. package/test/plugins/tls.js +3 -5
  67. package/test/plugins/toobusy.js +186 -9
  68. package/test/plugins/xclient.js +7 -4
  69. package/test/server.js +425 -1
  70. package/test/smtp_client.js +11 -18
  71. package/test/tls_socket.js +3 -6
  72. package/tls_socket.js +3 -3
  73. package/transaction.js +3 -3
  74. package/address.js +0 -53
  75. package/endpoint.js +0 -96
  76. package/host_pool.js +0 -169
  77. package/outbound/fsync_writestream.js +0 -44
  78. package/outbound/timer_queue.js +0 -86
  79. package/rfc1869.js +0 -93
  80. package/test/endpoint.js +0 -128
  81. package/test/host_pool.js +0 -188
  82. package/test/rfc1869.js +0 -89
@@ -0,0 +1,41 @@
1
+ #!/bin/sh
2
+ # pre-commit: if package.json has a `prettier` script AND prettier is
3
+ # resolvable without triggering npx auto-install, run it and block the
4
+ # commit on formatting violations.
5
+
6
+ [ -f package.json ] || exit 0
7
+ command -v npm >/dev/null 2>&1 || exit 0
8
+
9
+ has_script() {
10
+ # Detect a "scriptname": "..." entry. Prefers jq; falls back to grep so
11
+ # the hook works on minimal environments. The grep pattern requires a
12
+ # string value (opening quote after the colon), which avoids matching
13
+ # the top-level "prettier": { ... } config block.
14
+ if command -v jq >/dev/null 2>&1; then
15
+ jq -e ".scripts[\"$1\"]" package.json >/dev/null 2>&1
16
+ else
17
+ grep -qE "\"$1\"[[:space:]]*:[[:space:]]*\"" package.json
18
+ fi
19
+ }
20
+
21
+ has_script prettier || exit 0
22
+
23
+ # Bail if prettier isn't locally or globally installed — `npx prettier`
24
+ # would otherwise prompt to install it, which a hook can't answer.
25
+ if [ ! -x node_modules/.bin/prettier ] && ! command -v prettier >/dev/null 2>&1; then
26
+ exit 0
27
+ fi
28
+
29
+ npm run --silent prettier && exit 0
30
+
31
+ echo
32
+ echo "Prettier reported formatting violations. To fix:"
33
+ if has_script format; then
34
+ echo " npm run format # prettier:fix + lint:fix"
35
+ elif has_script prettier:fix; then
36
+ echo " npm run prettier:fix"
37
+ else
38
+ echo " npx prettier . --write"
39
+ fi
40
+ echo "Or bypass with: git commit --no-verify"
41
+ exit 1
package/.prettierignore CHANGED
@@ -1,4 +1,5 @@
1
1
  docs/*.md
2
+ AGENTS.md
2
3
  CHANGELOG.md
3
4
  CONTRIBUTORS.md
4
5
  Plugins.md
@@ -0,0 +1,7 @@
1
+ *
2
+ !configs
3
+ !configs/**
4
+ !hooks
5
+ !hooks/**
6
+ !qlty.toml
7
+ !.gitignore
@@ -0,0 +1 @@
1
+ source-path=SCRIPTDIR
@@ -0,0 +1,15 @@
1
+ exclude_patterns = [
2
+ ".release/**",
3
+ "test/**",
4
+ "docs/**",
5
+ "bin/**",
6
+ "eslint.config.mjs",
7
+ "coverage/**",
8
+ "node_modules/**",
9
+ ]
10
+
11
+ [smells.file_complexity]
12
+ threshold = 300
13
+
14
+ [smells.return_statements]
15
+ enabled = false
package/CHANGELOG.md CHANGED
@@ -4,11 +4,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
4
4
 
5
5
  ### Unreleased
6
6
 
7
- ### [3.2.0] - 2026-05-NN
7
+ ### [3.3.1] - 2026-06-12
8
+
9
+ - fix(conn): flag soft queue denials in results
10
+ - feat: expose fetch to plugins
11
+ - change: remove @haraka/email-address wrapper #3598
12
+ - change: log when MFROM or RCPT fail to parse #3581
13
+ - fix: change package name from Haraka to haraka #3596
14
+ - fix(haraka): wrap util.createFile in a try #3595
15
+ - fix(conn): update local and remote results after proxy #3593
16
+ - feat(conn): add main.postel option #3592
17
+ - feat: proxy support for smtps (465) #3577
18
+ - refactor(auth_proxy): use net_utils.endpoint #3584
19
+ - refactor: move endpoint, HostPool, LineSocket to net-utils #3583
20
+ - refactor: move rfc1869, FsyncWriteStream, TimerQueue to haraka-utils #3585
21
+ - refactor: move cram_md5_response to haraka-utils #3585
22
+ - dep(many): bump to latest #3587
23
+ - dep(eslint): update @haraka/eslint-config to v3, #3586
24
+ - dep(haraka-utils): ~2.1.1 for position-aware MAIL/RCPT address errors
25
+ - build: add qlty config and README badge #3588
26
+ - test: update to test-fixtures 1.7.0 helpers #3589
27
+
28
+ ### [3.2.1] - 2026-05-24
29
+
30
+ - fix(deps): update rspamd, dkim plugins to latest #3576
31
+
32
+ ### [3.2.0] - 2026-05-24
8
33
 
9
34
  - fix(status): merge worker status into summary #3574
10
35
  - dep: replace address-rfc282{1,2} with @haraka/email-address #3566
11
- - change(BREAKING for some plugins), see https://github.com/haraka/Haraka/issues/3564
36
+ - change(BREAKING for some plugins), see https://github.com/haraka/Haraka/issues/3564
12
37
 
13
38
  ### [3.1.7] - 2026-05-19
14
39
 
@@ -1869,3 +1894,5 @@ config files.
1869
1894
  [3.1.6]: https://github.com/haraka/Haraka/releases/tag/v3.1.6
1870
1895
  [3.1.7]: https://github.com/haraka/Haraka/releases/tag/v3.1.7
1871
1896
  [3.2.0]: https://github.com/haraka/Haraka/releases/tag/v3.2.0
1897
+ [3.2.1]: https://github.com/haraka/Haraka/releases/tag/v3.2.1
1898
+ [3.3.1]: https://github.com/haraka/Haraka/releases/tag/v3.3.1
package/CONTRIBUTORS.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  This handcrafted artisanal software is brought to you by:
4
4
 
5
- | <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/haraka/Haraka/commits?author=msimerson">1682</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/662371?v=4"><br><a href="https://github.com/baudehlo">baudehlo</a> (<a href="https://github.com/haraka/Haraka/commits?author=baudehlo">969</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/550490?v=4"><br><a href="https://github.com/smfreegard">smfreegard</a> (<a href="https://github.com/haraka/Haraka/commits?author=smfreegard">794</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/959600?v=4"><br><a href="https://github.com/godsflaw">godsflaw</a> (<a href="https://github.com/haraka/Haraka/commits?author=godsflaw">171</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/934254?v=4"><br><a href="https://github.com/analogic">analogic</a> (<a href="https://github.com/haraka/Haraka/commits?author=analogic">42</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1674289?v=4"><br><a href="https://github.com/Dexus">Dexus</a> (<a href="https://github.com/haraka/Haraka/commits?author=Dexus">42</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/82041?v=4"><br><a href="https://github.com/gramakri">gramakri</a> (<a href="https://github.com/haraka/Haraka/commits?author=gramakri">40</a>) |
5
+ | <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/haraka/Haraka/commits?author=msimerson">1685</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/662371?v=4"><br><a href="https://github.com/baudehlo">baudehlo</a> (<a href="https://github.com/haraka/Haraka/commits?author=baudehlo">969</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/550490?v=4"><br><a href="https://github.com/smfreegard">smfreegard</a> (<a href="https://github.com/haraka/Haraka/commits?author=smfreegard">794</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/959600?v=4"><br><a href="https://github.com/godsflaw">godsflaw</a> (<a href="https://github.com/haraka/Haraka/commits?author=godsflaw">171</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/934254?v=4"><br><a href="https://github.com/analogic">analogic</a> (<a href="https://github.com/haraka/Haraka/commits?author=analogic">42</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1674289?v=4"><br><a href="https://github.com/Dexus">Dexus</a> (<a href="https://github.com/haraka/Haraka/commits?author=Dexus">42</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/82041?v=4"><br><a href="https://github.com/gramakri">gramakri</a> (<a href="https://github.com/haraka/Haraka/commits?author=gramakri">40</a>) |
6
6
  | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
7
7
  | <img height="80" src="https://avatars.githubusercontent.com/u/203240?v=4"><br><a href="https://github.com/lnedry">lnedry</a> (<a href="https://github.com/haraka/Haraka/commits?author=lnedry">27</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/748075?v=4"><br><a href="https://github.com/celesteking">celesteking</a> (<a href="https://github.com/haraka/Haraka/commits?author=celesteking">21</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/791972?v=4"><br><a href="https://github.com/lpatters">lpatters</a> (<a href="https://github.com/haraka/Haraka/commits?author=lpatters">20</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/366268?v=4"><br><a href="https://github.com/chazomaticus">chazomaticus</a> (<a href="https://github.com/haraka/Haraka/commits?author=chazomaticus">19</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/123708?v=4"><br><a href="https://github.com/arlolra">arlolra</a> (<a href="https://github.com/haraka/Haraka/commits?author=arlolra">16</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/271024?v=4"><br><a href="https://github.com/hayesgm">hayesgm</a> (<a href="https://github.com/haraka/Haraka/commits?author=hayesgm">16</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1573133?v=4"><br><a href="https://github.com/gauravaror">gauravaror</a> (<a href="https://github.com/haraka/Haraka/commits?author=gauravaror">14</a>) |
8
8
  | <img height="80" src="https://avatars.githubusercontent.com/u/260607?v=4"><br><a href="https://github.com/typingArtist">typingArtist</a> (<a href="https://github.com/haraka/Haraka/commits?author=typingArtist">14</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/11343494?v=4"><br><a href="https://github.com/superman20">superman20</a> (<a href="https://github.com/haraka/Haraka/commits?author=superman20">13</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/158380?v=4"><br><a href="https://github.com/darkpixel">darkpixel</a> (<a href="https://github.com/haraka/Haraka/commits?author=darkpixel">12</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/9887966?v=4"><br><a href="https://github.com/KingNoosh">KingNoosh</a> (<a href="https://github.com/haraka/Haraka/commits?author=KingNoosh">11</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/5229495?v=4"><br><a href="https://github.com/tstonis">tstonis</a> (<a href="https://github.com/haraka/Haraka/commits?author=tstonis">10</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1746394?v=4"><br><a href="https://github.com/wltsmrz">wltsmrz</a> (<a href="https://github.com/haraka/Haraka/commits?author=wltsmrz">9</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/218741413?v=4"><br><a href="https://github.com/SamuelGrave">SamuelGrave</a> (<a href="https://github.com/haraka/Haraka/commits?author=SamuelGrave">9</a>) |
9
- | <img height="80" src="https://avatars.githubusercontent.com/u/2176651?v=4"><br><a href="https://github.com/fatalbanana">fatalbanana</a> (<a href="https://github.com/haraka/Haraka/commits?author=fatalbanana">9</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/231081?v=4"><br><a href="https://github.com/EyePulp">EyePulp</a> (<a href="https://github.com/haraka/Haraka/commits?author=EyePulp">8</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/81561?v=4"><br><a href="https://github.com/Synchro">Synchro</a> (<a href="https://github.com/haraka/Haraka/commits?author=Synchro">8</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/3957811?v=4"><br><a href="https://github.com/gene-hightower">gene-hightower</a> (<a href="https://github.com/haraka/Haraka/commits?author=gene-hightower">7</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/8224508?v=4"><br><a href="https://github.com/DarkSorrow">DarkSorrow</a> (<a href="https://github.com/haraka/Haraka/commits?author=DarkSorrow">6</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/103802?v=4"><br><a href="https://github.com/joshuathayer">joshuathayer</a> (<a href="https://github.com/haraka/Haraka/commits?author=joshuathayer">6</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/298453?v=4"><br><a href="https://github.com/zllovesuki">zllovesuki</a> (<a href="https://github.com/haraka/Haraka/commits?author=zllovesuki">5</a>) |
10
- | <img height="80" src="https://avatars.githubusercontent.com/u/132251?v=4"><br><a href="https://github.com/schamane">schamane</a> (<a href="https://github.com/haraka/Haraka/commits?author=schamane">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/2158203?v=4"><br><a href="https://github.com/luto">luto</a> (<a href="https://github.com/haraka/Haraka/commits?author=luto">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/21576638?v=4"><br><a href="https://github.com/steflw">steflw</a> (<a href="https://github.com/haraka/Haraka/commits?author=steflw">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1263856?v=4"><br><a href="https://github.com/ricardopolo">ricardopolo</a> (<a href="https://github.com/haraka/Haraka/commits?author=ricardopolo">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1521113?v=4"><br><a href="https://github.com/hontas">hontas</a> (<a href="https://github.com/haraka/Haraka/commits?author=hontas">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/918201?v=4"><br><a href="https://github.com/DoobleD">DoobleD</a> (<a href="https://github.com/haraka/Haraka/commits?author=DoobleD">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/11270?v=4"><br><a href="https://github.com/swerter">swerter</a> (<a href="https://github.com/haraka/Haraka/commits?author=swerter">4</a>) |
11
- | <img height="80" src="https://avatars.githubusercontent.com/u/791835?v=4"><br><a href="https://github.com/Juerd">Juerd</a> (<a href="https://github.com/haraka/Haraka/commits?author=Juerd">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/5351045?v=4"><br><a href="https://github.com/rhedshi">rhedshi</a> (<a href="https://github.com/haraka/Haraka/commits?author=rhedshi">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/7155684?v=4"><br><a href="https://github.com/rw251">rw251</a> (<a href="https://github.com/haraka/Haraka/commits?author=rw251">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/255701?v=4"><br><a href="https://github.com/abhas">abhas</a> (<a href="https://github.com/haraka/Haraka/commits?author=abhas">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/147893?v=4"><br><a href="https://github.com/Andrew565">Andrew565</a> (<a href="https://github.com/haraka/Haraka/commits?author=Andrew565">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1451395?v=4"><br><a href="https://github.com/bmonty">bmonty</a> (<a href="https://github.com/haraka/Haraka/commits?author=bmonty">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/308887?v=4"><br><a href="https://github.com/benjisg">benjisg</a> (<a href="https://github.com/haraka/Haraka/commits?author=benjisg">4</a>) |
12
- | <img height="80" src="https://avatars.githubusercontent.com/u/5624708?v=4"><br><a href="https://github.com/Bramzor">Bramzor</a> (<a href="https://github.com/haraka/Haraka/commits?author=Bramzor">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/8285462?v=4"><br><a href="https://github.com/CalgaryMichael">CalgaryMichael</a> (<a href="https://github.com/haraka/Haraka/commits?author=CalgaryMichael">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/34087?v=4"><br><a href="https://github.com/fredjean">fredjean</a> (<a href="https://github.com/haraka/Haraka/commits?author=fredjean">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/13922268?v=4"><br><a href="https://github.com/jdai8">jdai8</a> (<a href="https://github.com/haraka/Haraka/commits?author=jdai8">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/6220422?v=4"><br><a href="https://github.com/dcharbonnier">dcharbonnier</a> (<a href="https://github.com/haraka/Haraka/commits?author=dcharbonnier">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/232225?v=4"><br><a href="https://github.com/soncodi">soncodi</a> (<a href="https://github.com/haraka/Haraka/commits?author=soncodi">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/2115696?v=4"><br><a href="https://github.com/AuspeXeu">AuspeXeu</a> (<a href="https://github.com/haraka/Haraka/commits?author=AuspeXeu">3</a>) |
9
+ | <img height="80" src="https://avatars.githubusercontent.com/u/2176651?v=4"><br><a href="https://github.com/fatalbanana">fatalbanana</a> (<a href="https://github.com/haraka/Haraka/commits?author=fatalbanana">9</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/231081?v=4"><br><a href="https://github.com/EyePulp">EyePulp</a> (<a href="https://github.com/haraka/Haraka/commits?author=EyePulp">8</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/81561?v=4"><br><a href="https://github.com/Synchro">Synchro</a> (<a href="https://github.com/haraka/Haraka/commits?author=Synchro">8</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/3957811?v=4"><br><a href="https://github.com/gene-hightower">gene-hightower</a> (<a href="https://github.com/haraka/Haraka/commits?author=gene-hightower">7</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/8224508?v=4"><br><a href="https://github.com/DarkSorrow">DarkSorrow</a> (<a href="https://github.com/haraka/Haraka/commits?author=DarkSorrow">6</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/103802?v=4"><br><a href="https://github.com/joshuathayer">joshuathayer</a> (<a href="https://github.com/haraka/Haraka/commits?author=joshuathayer">6</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/918201?v=4"><br><a href="https://github.com/DoobleD">DoobleD</a> (<a href="https://github.com/haraka/Haraka/commits?author=DoobleD">5</a>) |
10
+ | <img height="80" src="https://avatars.githubusercontent.com/u/298453?v=4"><br><a href="https://github.com/zllovesuki">zllovesuki</a> (<a href="https://github.com/haraka/Haraka/commits?author=zllovesuki">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/132251?v=4"><br><a href="https://github.com/schamane">schamane</a> (<a href="https://github.com/haraka/Haraka/commits?author=schamane">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/2158203?v=4"><br><a href="https://github.com/luto">luto</a> (<a href="https://github.com/haraka/Haraka/commits?author=luto">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/21576638?v=4"><br><a href="https://github.com/steflw">steflw</a> (<a href="https://github.com/haraka/Haraka/commits?author=steflw">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1263856?v=4"><br><a href="https://github.com/ricardopolo">ricardopolo</a> (<a href="https://github.com/haraka/Haraka/commits?author=ricardopolo">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1521113?v=4"><br><a href="https://github.com/hontas">hontas</a> (<a href="https://github.com/haraka/Haraka/commits?author=hontas">5</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/11270?v=4"><br><a href="https://github.com/swerter">swerter</a> (<a href="https://github.com/haraka/Haraka/commits?author=swerter">4</a>) |
11
+ | <img height="80" src="https://avatars.githubusercontent.com/u/791835?v=4"><br><a href="https://github.com/Juerd">Juerd</a> (<a href="https://github.com/haraka/Haraka/commits?author=Juerd">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/5351045?v=4"><br><a href="https://github.com/rhedshi">rhedshi</a> (<a href="https://github.com/haraka/Haraka/commits?author=rhedshi">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/7155684?v=4"><br><a href="https://github.com/rw251">rw251</a> (<a href="https://github.com/haraka/Haraka/commits?author=rw251">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/13922268?v=4"><br><a href="https://github.com/jdai8">jdai8</a> (<a href="https://github.com/haraka/Haraka/commits?author=jdai8">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/34087?v=4"><br><a href="https://github.com/fredjean">fredjean</a> (<a href="https://github.com/haraka/Haraka/commits?author=fredjean">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/8285462?v=4"><br><a href="https://github.com/CalgaryMichael">CalgaryMichael</a> (<a href="https://github.com/haraka/Haraka/commits?author=CalgaryMichael">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/5624708?v=4"><br><a href="https://github.com/Bramzor">Bramzor</a> (<a href="https://github.com/haraka/Haraka/commits?author=Bramzor">4</a>) |
12
+ | <img height="80" src="https://avatars.githubusercontent.com/u/308887?v=4"><br><a href="https://github.com/benjisg">benjisg</a> (<a href="https://github.com/haraka/Haraka/commits?author=benjisg">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1451395?v=4"><br><a href="https://github.com/bmonty">bmonty</a> (<a href="https://github.com/haraka/Haraka/commits?author=bmonty">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/147893?v=4"><br><a href="https://github.com/Andrew565">Andrew565</a> (<a href="https://github.com/haraka/Haraka/commits?author=Andrew565">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/255701?v=4"><br><a href="https://github.com/abhas">abhas</a> (<a href="https://github.com/haraka/Haraka/commits?author=abhas">4</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/6220422?v=4"><br><a href="https://github.com/dcharbonnier">dcharbonnier</a> (<a href="https://github.com/haraka/Haraka/commits?author=dcharbonnier">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/232225?v=4"><br><a href="https://github.com/soncodi">soncodi</a> (<a href="https://github.com/haraka/Haraka/commits?author=soncodi">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/2115696?v=4"><br><a href="https://github.com/AuspeXeu">AuspeXeu</a> (<a href="https://github.com/haraka/Haraka/commits?author=AuspeXeu">3</a>) |
13
13
  | <img height="80" src="https://avatars.githubusercontent.com/u/6684599?v=4"><br><a href="https://github.com/acharkizakaria">acharkizakaria</a> (<a href="https://github.com/haraka/Haraka/commits?author=acharkizakaria">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1810178?v=4"><br><a href="https://github.com/pvsousalima">pvsousalima</a> (<a href="https://github.com/haraka/Haraka/commits?author=pvsousalima">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/759912?v=4"><br><a href="https://github.com/davebeyer">davebeyer</a> (<a href="https://github.com/haraka/Haraka/commits?author=davebeyer">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/706078?v=4"><br><a href="https://github.com/brunolm">brunolm</a> (<a href="https://github.com/haraka/Haraka/commits?author=brunolm">3</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/7008027?v=4"><br><a href="https://github.com/mkp7">mkp7</a> (<a href="https://github.com/haraka/Haraka/commits?author=mkp7">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1007551?v=4"><br><a href="https://github.com/unquietwiki">unquietwiki</a> (<a href="https://github.com/haraka/Haraka/commits?author=unquietwiki">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/10368105?v=4"><br><a href="https://github.com/mtrivera">mtrivera</a> (<a href="https://github.com/haraka/Haraka/commits?author=mtrivera">2</a>) |
14
14
  | <img height="80" src="https://avatars.githubusercontent.com/u/1829326?v=4"><br><a href="https://github.com/slattery">slattery</a> (<a href="https://github.com/haraka/Haraka/commits?author=slattery">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/8569238?v=4"><br><a href="https://github.com/MuraraAllan">MuraraAllan</a> (<a href="https://github.com/haraka/Haraka/commits?author=MuraraAllan">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/994004?v=4"><br><a href="https://github.com/stefanocoding">stefanocoding</a> (<a href="https://github.com/haraka/Haraka/commits?author=stefanocoding">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/5483299?v=4"><br><a href="https://github.com/thihara">thihara</a> (<a href="https://github.com/haraka/Haraka/commits?author=thihara">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/43457281?v=4"><br><a href="https://github.com/benjamonnguyen">benjamonnguyen</a> (<a href="https://github.com/haraka/Haraka/commits?author=benjamonnguyen">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/15035337?v=4"><br><a href="https://github.com/gtech99">gtech99</a> (<a href="https://github.com/haraka/Haraka/commits?author=gtech99">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/3073292?v=4"><br><a href="https://github.com/joelchornik">joelchornik</a> (<a href="https://github.com/haraka/Haraka/commits?author=joelchornik">2</a>) |
15
15
  | <img height="80" src="https://avatars.githubusercontent.com/u/934178?v=4"><br><a href="https://github.com/vrevo">vrevo</a> (<a href="https://github.com/haraka/Haraka/commits?author=vrevo">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/1249760?v=4"><br><a href="https://github.com/andreis">andreis</a> (<a href="https://github.com/haraka/Haraka/commits?author=andreis">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/15694566?v=4"><br><a href="https://github.com/apoorv-mishra">apoorv-mishra</a> (<a href="https://github.com/haraka/Haraka/commits?author=apoorv-mishra">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/563469?v=4"><br><a href="https://github.com/niieani">niieani</a> (<a href="https://github.com/haraka/Haraka/commits?author=niieani">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/196198?v=4"><br><a href="https://github.com/hathaway">hathaway</a> (<a href="https://github.com/haraka/Haraka/commits?author=hathaway">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/14928995?v=4"><br><a href="https://github.com/carolinaknoll">carolinaknoll</a> (<a href="https://github.com/haraka/Haraka/commits?author=carolinaknoll">2</a>) | <img height="80" src="https://avatars.githubusercontent.com/u/654682?v=4"><br><a href="https://github.com/martin1yness">martin1yness</a> (<a href="https://github.com/haraka/Haraka/commits?author=martin1yness">2</a>) |
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Haraka — a Node.js Mail Server
2
2
 
3
- ![Tests](https://github.com/haraka/Haraka/actions/workflows/ci.yml/badge.svg)
4
- [![Coverage Status][cov-img]][cov-url]
3
+ [![Build][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]
5
4
 
6
5
  Haraka is a highly scalable [Node.js][1] SMTP server with a modular plugin architecture. It handles thousands of concurrent connections and delivers thousands of messages per second. Haraka and its plugins are written in asynchronous JavaScript, optimised for throughput and low latency.
7
6
 
@@ -115,5 +114,9 @@ Haraka is released under the MIT License. See [LICENSE][license] for details.
115
114
  [msimerson]: https://github.com/msimerson
116
115
  [spamassassin]: https://spamassassin.apache.org/
117
116
  [qpsmtpd]: https://github.com/smtpd/qpsmtpd/
117
+ [ci-img]: https://github.com/haraka/Haraka/actions/workflows/ci.yml/badge.svg
118
+ [ci-url]: https://github.com/haraka/Haraka/actions/workflows/ci.yml
118
119
  [cov-img]: https://codecov.io/github/haraka/Haraka/coverage.svg
119
- [cov-url]: https://codecov.io/github/haraka/Haraka?branch=master
120
+ [cov-url]: https://codecov.io/github/haraka/Haraka
121
+ [qlty-img]: https://qlty.sh/gh/haraka/projects/Haraka/maintainability.svg
122
+ [qlty-url]: https://qlty.sh/gh/haraka/projects/Haraka
package/bin/haraka CHANGED
@@ -165,6 +165,14 @@ function setupRequire() {
165
165
 
166
166
  function noop() {}
167
167
 
168
+ function tryCreateFile(filePath, data, info = {}, force = false) {
169
+ try {
170
+ utils.createFile(...arguments)
171
+ } catch (e) {
172
+ warning(`EEXIST, File exists '${filePath}'`)
173
+ }
174
+ }
175
+
168
176
  const readme = `Haraka
169
177
 
170
178
  Congratulations on creating a new installation of Haraka.
@@ -406,7 +414,7 @@ if (parsed.version) {
406
414
  plugins.load_plugins(parsed.test && parsed.test[0] !== 'all' ? parsed.test : null)
407
415
  const Connection = require(path.join(base, 'connection'))
408
416
  // var Transaction = require(path.join(base, "transaction"));
409
- const Address = require('../address').Address
417
+ const Address = require('@haraka/email-address').Address
410
418
  const Notes = require('haraka-notes')
411
419
  const constants = require('haraka-constants')
412
420
  const client = {
@@ -549,10 +557,10 @@ if (parsed.version) {
549
557
  utils.mkDir(path.join(pa, d))
550
558
  }
551
559
  utils.copyFile(path.join(base, 'docs', 'Plugins.md'), path.join(pa, 'docs', 'Plugins.md'))
552
- utils.createFile(path.join(pa, 'README'), readme, {}, parsed.force)
553
- utils.createFile(path.join(pa, 'package.json'), packageJson, {}, parsed.force)
560
+ tryCreateFile(path.join(pa, 'README'), readme, {}, parsed.force)
561
+ tryCreateFile(path.join(pa, 'package.json'), packageJson, {}, parsed.force)
554
562
  const bytes = require('crypto').randomBytes(32)
555
- utils.createFile(path.join(pa, 'config', 'internalcmd_key'), bytes.toString('hex'), {}, parsed.force)
563
+ tryCreateFile(path.join(pa, 'config', 'internalcmd_key'), bytes.toString('hex'), {}, parsed.force)
556
564
  setupHostname(path.join(pa, 'config'))
557
565
  setupBaseConfig(path.join(pa, 'config'))
558
566
  } else {
@@ -14,11 +14,17 @@
14
14
  ; Require senders to conform to RFC 1869 and RFC 821 when sending the MAIL FROM and RCPT TO commands. In particular, the inclusion of spurious spaces or missing angle brackets will be rejected.
15
15
  ; strict_rfc1869 = false
16
16
 
17
+ ; Liberal envelope parsing. See @haraka/email-address for what postel mode relaxes.
18
+ ; postel = false
19
+
17
20
  ; Advertise support for SMTPUTF8 (RFC-6531)
18
21
  ; smtputf8=true
19
22
 
20
23
 
21
24
  [haproxy]
25
+ ; Bool: enable HAProxy PROXY protocol support and SMTPS pre-parsing.
26
+ ; enabled=true
27
+
22
28
  ; Array: hosts or CIDRs that Haraka should enable the PROXY protocol from. See docs/HAProxy for format
23
29
  hosts[] =
24
30
  ; hosts[] = 192.0.2.4
package/connection.js CHANGED
@@ -2,7 +2,6 @@
2
2
  // a single connection
3
3
 
4
4
  const dns = require('node:dns')
5
- const net = require('node:net')
6
5
  const os = require('node:os')
7
6
 
8
7
  // npm libs
@@ -12,14 +11,14 @@ const constants = require('haraka-constants')
12
11
  const net_utils = require('haraka-net-utils')
13
12
  const Notes = require('haraka-notes')
14
13
  const utils = require('haraka-utils')
15
- const { Address } = require('./address')
14
+ const { Address } = require('@haraka/email-address')
16
15
  const ResultStore = require('haraka-results')
17
16
 
18
17
  // Haraka libs
19
18
  const logger = require('./logger')
20
19
  const trans = require('./transaction')
21
20
  const plugins = require('./plugins')
22
- const rfc1869 = require('./rfc1869')
21
+ const rfc1869 = utils.rfc1869
23
22
  const outbound = require('./outbound')
24
23
 
25
24
  const states = constants.connection.state
@@ -27,6 +26,7 @@ const states = constants.connection.state
27
26
  const cfg = config.get('connection.ini', {
28
27
  booleans: [
29
28
  '-main.strict_rfc1869',
29
+ '-main.postel',
30
30
  '+main.smtputf8',
31
31
  '+headers.add_received',
32
32
  '+headers.show_version',
@@ -34,20 +34,8 @@ const cfg = config.get('connection.ini', {
34
34
  ],
35
35
  })
36
36
 
37
- const haproxy_hosts_ipv4 = []
38
- const haproxy_hosts_ipv6 = []
39
-
40
- for (const ip of cfg.haproxy.hosts) {
41
- if (!ip) continue
42
- if (net.isIPv6(ip.split('/')[0])) {
43
- haproxy_hosts_ipv6.push([ipaddr.IPv6.parse(ip.split('/')[0]), parseInt(ip.split('/')[1] || 64)])
44
- } else {
45
- haproxy_hosts_ipv4.push([ipaddr.IPv4.parse(ip.split('/')[0]), parseInt(ip.split('/')[1] || 32)])
46
- }
47
- }
48
-
49
37
  class Connection {
50
- constructor(client, server, smtp_cfg) {
38
+ constructor(client, server) {
51
39
  this.client = client
52
40
  this.server = server
53
41
 
@@ -160,7 +148,7 @@ class Connection {
160
148
  self.fail()
161
149
  })
162
150
 
163
- self.client.on('close', (has_error) => {
151
+ self.client.on('close', () => {
164
152
  if (self.state >= states.DISCONNECTING) return
165
153
  self.remote.closed = true
166
154
  self.loginfo('client dropped connection', log_data)
@@ -190,8 +178,20 @@ class Connection {
190
178
  self.process_data(data)
191
179
  })
192
180
 
193
- const ha_list = net.isIPv6(self.remote.ip) ? haproxy_hosts_ipv6 : haproxy_hosts_ipv4
194
- if (ha_list.some((element) => ipaddr.parse(self.remote.ip).match(element[0], element[1]))) {
181
+ // SMTPS pre-parser state: proxy means the PROXY line was already consumed;
182
+ // peer_allowed means a trusted PROXY peer sent direct TLS instead.
183
+ const smtps = self.client.haraka_smtps
184
+ if (smtps?.proxy) {
185
+ self.proxy.allowed = true
186
+ return
187
+ }
188
+
189
+ if (smtps?.peer_allowed) {
190
+ plugins.run_hooks('connect_init', self)
191
+ return
192
+ }
193
+
194
+ if (net_utils.is_haproxy_allowed(self.remote.ip)) {
195
195
  self.proxy.allowed = true
196
196
  // Wait for PROXY command
197
197
  self.proxy.timer = setTimeout(() => {
@@ -678,8 +678,7 @@ class Connection {
678
678
  }
679
679
  /////////////////////////////////////////////////////////////////////////////
680
680
  // SMTP Responses
681
- connect_init_respond(retval, msg) {
682
- // retval and message are ignored
681
+ connect_init_respond() {
683
682
  this.logdebug('running connect_init_respond')
684
683
  plugins.run_hooks('lookup_rdns', this)
685
684
  }
@@ -889,7 +888,7 @@ class Connection {
889
888
  }
890
889
  }
891
890
  }
892
- capabilities_respond(retval, msg) {
891
+ capabilities_respond() {
893
892
  this.respond(250, this.capabilities)
894
893
  }
895
894
  quit_respond(retval, msg) {
@@ -940,7 +939,7 @@ class Connection {
940
939
  this.respond(250, 'OK')
941
940
  }
942
941
  }
943
- rset_respond(retval, msg) {
942
+ rset_respond() {
944
943
  this.respond(250, 'OK', () => {
945
944
  this.reset_transaction()
946
945
  })
@@ -1137,39 +1136,14 @@ class Connection {
1137
1136
  /////////////////////////////////////////////////////////////////////////////
1138
1137
  // HAProxy support
1139
1138
 
1140
- cmd_proxy(line) {
1141
- if (!this.proxy.allowed) {
1142
- this.respond(421, `PROXY not allowed from ${this.remote.ip}`)
1143
- return this.disconnect()
1139
+ apply_proxy(proxy) {
1140
+ if (this.proxy.timer) {
1141
+ clearTimeout(this.proxy.timer)
1142
+ this.proxy.timer = null
1144
1143
  }
1145
1144
 
1146
- const match = /(TCP4|TCP6|UNKNOWN) (\S+) (\S+) (\d+) (\d+)$/.exec(line)
1147
- if (!match) {
1148
- this.respond(421, 'Invalid PROXY format')
1149
- return this.disconnect()
1150
- }
1151
- const proto = match[1]
1152
- const src_ip = match[2]
1153
- const dst_ip = match[3]
1154
- const src_port = match[4]
1155
- const dst_port = match[5]
1156
-
1157
- // Validate source/destination IP
1158
- /*eslint no-fallthrough: 0 */
1159
- switch (proto) {
1160
- case 'TCP4':
1161
- if (ipaddr.IPv4.isValid(src_ip) && ipaddr.IPv4.isValid(dst_ip)) {
1162
- break
1163
- }
1164
- case 'TCP6':
1165
- if (ipaddr.IPv6.isValid(src_ip) && ipaddr.IPv6.isValid(dst_ip)) {
1166
- break
1167
- }
1168
- // case 'UNKNOWN':
1169
- default:
1170
- this.respond(421, 'Invalid PROXY format')
1171
- return this.disconnect()
1172
- }
1145
+ const { proto, src_ip, src_port, dst_ip, dst_port } = proxy
1146
+ const proxy_ip = proxy.proxy_ip || this.remote.ip
1173
1147
 
1174
1148
  // Apply changes
1175
1149
  this.loginfo('HAProxy', {
@@ -1185,11 +1159,11 @@ class Connection {
1185
1159
  src_port,
1186
1160
  dst_ip,
1187
1161
  dst_port,
1188
- proxy_ip: this.remote.ip,
1162
+ proxy_ip,
1189
1163
  }
1190
1164
 
1191
1165
  this.reset_transaction(() => {
1192
- this.set('proxy.ip', this.remote.ip)
1166
+ this.set('proxy.ip', proxy_ip)
1193
1167
  this.set('proxy.type', 'haproxy')
1194
1168
  this.relaying = false
1195
1169
  this.set('local.ip', dst_ip)
@@ -1198,9 +1172,26 @@ class Connection {
1198
1172
  this.set('remote.port', parseInt(src_port, 10))
1199
1173
  this.set('remote.host', null)
1200
1174
  this.set('hello.host', null)
1175
+ this.results.add({ name: 'local' }, this.local)
1176
+ this.results.add({ name: 'remote' }, this.remote)
1201
1177
  plugins.run_hooks('connect_init', this)
1202
1178
  })
1203
1179
  }
1180
+
1181
+ cmd_proxy(line) {
1182
+ if (!this.proxy.allowed) {
1183
+ this.respond(421, `PROXY not allowed from ${this.remote.ip}`)
1184
+ return this.disconnect()
1185
+ }
1186
+
1187
+ const proxy = net_utils.parse_proxy_line(line)
1188
+ if (!proxy) {
1189
+ this.respond(421, 'Invalid PROXY format')
1190
+ return this.disconnect()
1191
+ }
1192
+
1193
+ this.apply_proxy(proxy)
1194
+ }
1204
1195
  /////////////////////////////////////////////////////////////////////////////
1205
1196
  // SMTP Commands
1206
1197
 
@@ -1283,7 +1274,7 @@ class Connection {
1283
1274
  }
1284
1275
  plugins.run_hooks('rset', this)
1285
1276
  }
1286
- cmd_vrfy(line) {
1277
+ cmd_vrfy() {
1287
1278
  // only supported via plugins
1288
1279
  plugins.run_hooks('vrfy', this)
1289
1280
  }
@@ -1323,10 +1314,13 @@ class Connection {
1323
1314
  }
1324
1315
 
1325
1316
  let from
1317
+ const from_raw = results.shift()
1326
1318
  try {
1327
- from = new Address(results.shift())
1319
+ from = new Address(from_raw, { postel: cfg.main.postel })
1328
1320
  } catch (err) {
1329
- return this.respond(501, `Invalid MAIL FROM address`)
1321
+ const msg = `Invalid MAIL FROM address ${utils.sanitize(from_raw)}: ${err.message}`
1322
+ this.lognotice(msg)
1323
+ return this.respond(501, msg)
1330
1324
  }
1331
1325
 
1332
1326
  // Get rest of key=value pairs
@@ -1382,10 +1376,13 @@ class Connection {
1382
1376
  }
1383
1377
 
1384
1378
  let recip
1379
+ const recip_raw = results.shift()
1385
1380
  try {
1386
- recip = new Address(results.shift())
1381
+ recip = new Address(recip_raw, { postel: cfg.main.postel })
1387
1382
  } catch (err) {
1388
- return this.respond(501, `Invalid RCPT TO address`)
1383
+ const msg = `Invalid RCPT TO address ${utils.sanitize(recip_raw)}: ${err.message}`
1384
+ this.lognotice(msg)
1385
+ return this.respond(501, msg)
1389
1386
  }
1390
1387
 
1391
1388
  // Get rest of key=value pairs
@@ -1448,19 +1445,17 @@ class Connection {
1448
1445
  // value (e.g. a failed AUTH username, see auth_base) must not be
1449
1446
  // able to inject extra header lines into Authentication-Results.
1450
1447
  // 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
-
1453
1448
  // if message, store it in the appropriate note
1454
1449
  if (message) {
1455
1450
  if (has_tran === true) {
1456
- this.transaction.notes.authentication_results.push(ar_clean(message))
1451
+ this.transaction.notes.authentication_results.push(utils.sanitize(message))
1457
1452
  } else {
1458
- this.notes.authentication_results.push(ar_clean(message))
1453
+ this.notes.authentication_results.push(utils.sanitize(message))
1459
1454
  }
1460
1455
  }
1461
1456
 
1462
1457
  // assemble the new header
1463
- let header = [ar_clean(this.local.host), ...this.notes.authentication_results]
1458
+ let header = [utils.sanitize(this.local.host), ...this.notes.authentication_results]
1464
1459
  if (has_tran === true) {
1465
1460
  header = [...header, ...this.transaction.notes.authentication_results]
1466
1461
  }
@@ -1668,7 +1663,7 @@ class Connection {
1668
1663
  }
1669
1664
  }
1670
1665
  }
1671
- max_data_exceeded_respond(retval, msg) {
1666
+ max_data_exceeded_respond(retval) {
1672
1667
  // TODO: Maybe figure out what to do with other return codes
1673
1668
  this.respond(retval === constants.denysoft ? 450 : 550, 'Message too big!', () => {
1674
1669
  this.reset_transaction()
@@ -1703,9 +1698,11 @@ class Connection {
1703
1698
  break
1704
1699
  case constants.deny:
1705
1700
  case constants.denydisconnect:
1701
+ this.transaction.results.add(res_as, { fail: msg })
1702
+ break
1706
1703
  case constants.denysoft:
1707
1704
  case constants.denysoftdisconnect:
1708
- this.transaction.results.add(res_as, { fail: msg })
1705
+ this.transaction.results.add(res_as, { fail: msg, soft: true })
1709
1706
  break
1710
1707
  case constants.cont:
1711
1708
  break
@@ -1859,6 +1856,8 @@ class Connection {
1859
1856
 
1860
1857
  exports.Connection = Connection
1861
1858
 
1859
+ exports.cfg = cfg
1860
+
1862
1861
  exports.createConnection = (client, server, cfg) => {
1863
1862
  return new Connection(client, server, cfg)
1864
1863
  }
@@ -6,6 +6,8 @@
6
6
  # REQUIRE: NETWORKING ldconfig
7
7
  # KEYWORD: shutdown
8
8
 
9
+ PATH="$PATH:/usr/local/bin"
10
+
9
11
  . /etc/rc.subr
10
12
 
11
13
  name="haraka"
@@ -48,6 +48,8 @@ Per-connection limits and behaviours. See inline comments in the shipped
48
48
  | `main.spool_after` | `-1` | Size (bytes) at which to spool the message to disk. `-1` never spools; `0` always spools. |
49
49
  | `main.strict_rfc1869` | `false` | Reject `MAIL FROM` / `RCPT TO` that violates RFC 1869/821 (spurious spaces, missing brackets). |
50
50
  | `main.smtputf8` | `true` | Advertise `SMTPUTF8` (RFC 6531). |
51
+ | `main.postel` | `false` | Liberal envelope parsing. |
52
+ | `haproxy.enabled` | `true` | Enable HAProxy PROXY protocol handling. Set `false` to bypass PROXY support. |
51
53
  | `haproxy.hosts` | empty | Array of IPs/CIDRs allowed to send the PROXY protocol. See [HAProxy.md](HAProxy.md). |
52
54
  | `headers.add_received` | `true` | Add a `Received:` header to incoming mail. |
53
55
  | `headers.clean_auth_results` | `true` | Strip inbound `Authentication-Results:` headers before plugins run. |
package/docs/HAProxy.md CHANGED
@@ -6,15 +6,18 @@ The PROXY v2 binary header is not currently supported.
6
6
 
7
7
  ## Configuration
8
8
 
9
- PROXY support is disabled by default. To enable it, list the IPs (or CIDRs) of trusted proxies in `connection.ini`:
9
+ PROXY support is enabled by default, but inactive until trusted proxy IPs or CIDRs are listed in `connection.ini`:
10
10
 
11
11
  ```ini
12
12
  [haproxy]
13
+ enabled=true
13
14
  hosts[] = 192.0.2.4
14
15
  hosts[] = 192.0.2.5/30
15
16
  hosts[] = 2001:db8::1
16
17
  ```
17
18
 
19
+ Set `enabled=false` to disable PROXY protocol handling entirely. On SMTPS listeners this bypasses pre-TLS PROXY parsing and uses the standard implicit TLS server.
20
+
18
21
  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
22
 
20
23
  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`.