haraka 0.0.33 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/.githooks/pre-commit +41 -0
  3. package/.prettierignore +6 -0
  4. package/.qlty/.gitignore +7 -0
  5. package/.qlty/configs/.shellcheckrc +1 -0
  6. package/.qlty/qlty.toml +15 -0
  7. package/CHANGELOG.md +1894 -0
  8. package/CLAUDE.md +40 -0
  9. package/CONTRIBUTORS.md +34 -0
  10. package/Dockerfile +50 -0
  11. package/GEMINI.md +38 -0
  12. package/LICENSE +22 -0
  13. package/Plugins.md +227 -0
  14. package/README.md +119 -4
  15. package/SECURITY.md +178 -0
  16. package/TODO +22 -0
  17. package/address.js +53 -0
  18. package/bin/haraka +593 -0
  19. package/bin/haraka_grep +32 -0
  20. package/config/aliases +2 -0
  21. package/config/auth_flat_file.ini +7 -0
  22. package/config/auth_vpopmaild.ini +9 -0
  23. package/config/connection.ini +79 -0
  24. package/config/delay_deny.ini +7 -0
  25. package/config/dhparams.pem +8 -0
  26. package/config/host_list +3 -0
  27. package/config/host_list_regex +6 -0
  28. package/config/http.ini +11 -0
  29. package/config/lmtp.ini +7 -0
  30. package/config/log.ini +11 -0
  31. package/config/me +1 -0
  32. package/config/outbound.bounce_message +18 -0
  33. package/config/outbound.bounce_message_html +36 -0
  34. package/config/outbound.bounce_message_image +106 -0
  35. package/config/outbound.ini +24 -0
  36. package/config/plugins +67 -0
  37. package/config/smtp.ini +37 -0
  38. package/config/smtp_bridge.ini +4 -0
  39. package/config/smtp_forward.ini +31 -0
  40. package/config/smtp_proxy.ini +27 -0
  41. package/config/tarpit.timeout +1 -0
  42. package/config/tls.ini +83 -0
  43. package/config/tls_cert.pem +23 -0
  44. package/config/tls_key.pem +28 -0
  45. package/config/watch.ini +12 -0
  46. package/config/xclient.hosts +2 -0
  47. package/connection.js +1863 -0
  48. package/contrib/Haraka.cf +6 -0
  49. package/contrib/Haraka.pm +35 -0
  50. package/contrib/bad_smtp_server.pl +25 -0
  51. package/contrib/bsd-rc.d/haraka +61 -0
  52. package/contrib/debian-init.d/haraka +87 -0
  53. package/contrib/haraka.init +96 -0
  54. package/contrib/haraka.service +23 -0
  55. package/contrib/plugin2npm.sh +81 -0
  56. package/contrib/ubuntu-upstart/haraka.conf +27 -0
  57. package/coverage/coverage-final.json +2 -0
  58. package/coverage/coverage-summary.json +33 -0
  59. package/coverage/tmp/coverage-79131-1779241025146-0.json +1 -0
  60. package/coverage/tmp/coverage-79132-1779240999690-0.json +1 -0
  61. package/coverage/tmp/coverage-79172-1779241000095-0.json +1 -0
  62. package/coverage/tmp/coverage-79210-1779241000156-0.json +1 -0
  63. package/coverage/tmp/coverage-79211-1779241000209-0.json +1 -0
  64. package/coverage/tmp/coverage-79212-1779241000266-0.json +1 -0
  65. package/coverage/tmp/coverage-79213-1779241000441-0.json +1 -0
  66. package/coverage/tmp/coverage-79214-1779241000626-0.json +1 -0
  67. package/coverage/tmp/coverage-79215-1779241000795-0.json +1 -0
  68. package/coverage/tmp/coverage-79216-1779241000965-0.json +1 -0
  69. package/coverage/tmp/coverage-79218-1779241001013-0.json +1 -0
  70. package/coverage/tmp/coverage-79219-1779241001179-0.json +1 -0
  71. package/coverage/tmp/coverage-79220-1779241006249-0.json +1 -0
  72. package/coverage/tmp/coverage-79227-1779241011453-0.json +1 -0
  73. package/coverage/tmp/coverage-79229-1779241011537-0.json +1 -0
  74. package/coverage/tmp/coverage-79230-1779241011647-0.json +1 -0
  75. package/coverage/tmp/coverage-79231-1779241011765-0.json +1 -0
  76. package/coverage/tmp/coverage-79232-1779241011841-0.json +1 -0
  77. package/coverage/tmp/coverage-79233-1779241011909-0.json +1 -0
  78. package/coverage/tmp/coverage-79234-1779241011984-0.json +1 -0
  79. package/coverage/tmp/coverage-79235-1779241012055-0.json +1 -0
  80. package/coverage/tmp/coverage-79236-1779241012230-0.json +1 -0
  81. package/coverage/tmp/coverage-79237-1779241012300-0.json +1 -0
  82. package/coverage/tmp/coverage-79238-1779241012368-0.json +1 -0
  83. package/coverage/tmp/coverage-79239-1779241012438-0.json +1 -0
  84. package/coverage/tmp/coverage-79240-1779241012511-0.json +1 -0
  85. package/coverage/tmp/coverage-79241-1779241012582-0.json +1 -0
  86. package/coverage/tmp/coverage-79242-1779241012652-0.json +1 -0
  87. package/coverage/tmp/coverage-79243-1779241012814-0.json +1 -0
  88. package/coverage/tmp/coverage-79244-1779241012931-0.json +1 -0
  89. package/coverage/tmp/coverage-79245-1779241013007-0.json +1 -0
  90. package/coverage/tmp/coverage-79246-1779241013106-0.json +1 -0
  91. package/coverage/tmp/coverage-79247-1779241013178-0.json +1 -0
  92. package/coverage/tmp/coverage-79248-1779241013244-0.json +1 -0
  93. package/coverage/tmp/coverage-79249-1779241013409-0.json +1 -0
  94. package/coverage/tmp/coverage-79250-1779241013697-0.json +1 -0
  95. package/coverage/tmp/coverage-79251-1779241013847-0.json +1 -0
  96. package/coverage/tmp/coverage-79252-1779241014288-0.json +1 -0
  97. package/coverage/tmp/coverage-79253-1779241014378-0.json +1 -0
  98. package/coverage/tmp/coverage-79254-1779241014428-0.json +1 -0
  99. package/coverage/tmp/coverage-79255-1779241021774-0.json +1 -0
  100. package/coverage/tmp/coverage-80382-1779241021949-0.json +1 -0
  101. package/coverage/tmp/coverage-80383-1779241025019-0.json +1 -0
  102. package/coverage/tmp/coverage-80384-1779241025133-0.json +1 -0
  103. package/docs/Body.md +1 -0
  104. package/docs/Config.md +1 -0
  105. package/docs/Connection.md +153 -0
  106. package/docs/CoreConfig.md +96 -0
  107. package/docs/CustomReturnCodes.md +3 -0
  108. package/docs/HAProxy.md +62 -0
  109. package/docs/Header.md +1 -0
  110. package/docs/Logging.md +129 -0
  111. package/docs/Outbound.md +210 -0
  112. package/docs/Plugins.md +372 -0
  113. package/docs/Results.md +7 -0
  114. package/docs/Transaction.md +135 -0
  115. package/docs/Tutorial.md +183 -0
  116. package/docs/deprecated/access.md +3 -0
  117. package/docs/deprecated/backscatterer.md +9 -0
  118. package/docs/deprecated/connect.rdns_access.md +53 -0
  119. package/docs/deprecated/data.headers.md +3 -0
  120. package/docs/deprecated/data.nomsgid.md +7 -0
  121. package/docs/deprecated/data.noreceived.md +11 -0
  122. package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
  123. package/docs/deprecated/dkim_sign.md +97 -0
  124. package/docs/deprecated/dkim_verify.md +28 -0
  125. package/docs/deprecated/dnsbl.md +80 -0
  126. package/docs/deprecated/dnswl.md +73 -0
  127. package/docs/deprecated/lookup_rdns.strict.md +67 -0
  128. package/docs/deprecated/mail_from.access.md +52 -0
  129. package/docs/deprecated/mail_from.blocklist.md +18 -0
  130. package/docs/deprecated/mail_from.nobounces.md +8 -0
  131. package/docs/deprecated/rcpt_to.access.md +53 -0
  132. package/docs/deprecated/rcpt_to.blocklist.md +18 -0
  133. package/docs/deprecated/rcpt_to.routes.md +3 -0
  134. package/docs/deprecated/rdns.regexp.md +30 -0
  135. package/docs/plugins/aliases.md +3 -0
  136. package/docs/plugins/auth/auth_bridge.md +34 -0
  137. package/docs/plugins/auth/auth_ldap.md +4 -0
  138. package/docs/plugins/auth/auth_proxy.md +36 -0
  139. package/docs/plugins/auth/auth_vpopmaild.md +33 -0
  140. package/docs/plugins/auth/flat_file.md +40 -0
  141. package/docs/plugins/block_me.md +18 -0
  142. package/docs/plugins/data.signatures.md +11 -0
  143. package/docs/plugins/delay_deny.md +23 -0
  144. package/docs/plugins/max_unrecognized_commands.md +6 -0
  145. package/docs/plugins/prevent_credential_leaks.md +22 -0
  146. package/docs/plugins/process_title.md +42 -0
  147. package/docs/plugins/queue/deliver.md +3 -0
  148. package/docs/plugins/queue/discard.md +32 -0
  149. package/docs/plugins/queue/lmtp.md +24 -0
  150. package/docs/plugins/queue/qmail-queue.md +16 -0
  151. package/docs/plugins/queue/quarantine.md +87 -0
  152. package/docs/plugins/queue/smtp_bridge.md +32 -0
  153. package/docs/plugins/queue/smtp_forward.md +127 -0
  154. package/docs/plugins/queue/smtp_proxy.md +68 -0
  155. package/docs/plugins/queue/test.md +7 -0
  156. package/docs/plugins/rcpt_to.in_host_list.md +34 -0
  157. package/docs/plugins/rcpt_to.max_count.md +3 -0
  158. package/docs/plugins/record_envelope_addresses.md +20 -0
  159. package/docs/plugins/relay.md +3 -0
  160. package/docs/plugins/reseed_rng.md +16 -0
  161. package/docs/plugins/status.md +41 -0
  162. package/docs/plugins/tarpit.md +50 -0
  163. package/docs/plugins/tls.md +235 -0
  164. package/docs/plugins/toobusy.md +27 -0
  165. package/docs/plugins/xclient.md +10 -0
  166. package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
  167. package/docs/tutorials/SettingUpOutbound.md +62 -0
  168. package/eslint.config.mjs +2 -0
  169. package/haraka.js +74 -0
  170. package/haraka.sh +2 -0
  171. package/http/html/404.html +58 -0
  172. package/http/html/index.html +47 -0
  173. package/http/package.json +21 -0
  174. package/line_socket.js +24 -0
  175. package/logger.js +322 -0
  176. package/outbound/client_pool.js +59 -0
  177. package/outbound/config.js +134 -0
  178. package/outbound/hmail.js +1504 -0
  179. package/outbound/index.js +349 -0
  180. package/outbound/qfile.js +93 -0
  181. package/outbound/queue.js +399 -0
  182. package/outbound/tls.js +85 -0
  183. package/outbound/todo.js +17 -0
  184. package/package.json +99 -4
  185. package/plugins/.eslintrc.yaml +3 -0
  186. package/plugins/auth/auth_base.js +261 -0
  187. package/plugins/auth/auth_bridge.js +20 -0
  188. package/plugins/auth/auth_proxy.js +227 -0
  189. package/plugins/auth/auth_vpopmaild.js +162 -0
  190. package/plugins/auth/flat_file.js +44 -0
  191. package/plugins/block_me.js +88 -0
  192. package/plugins/data.signatures.js +30 -0
  193. package/plugins/delay_deny.js +153 -0
  194. package/plugins/prevent_credential_leaks.js +61 -0
  195. package/plugins/process_title.js +197 -0
  196. package/plugins/profile.js +11 -0
  197. package/plugins/queue/deliver.js +12 -0
  198. package/plugins/queue/discard.js +27 -0
  199. package/plugins/queue/lmtp.js +45 -0
  200. package/plugins/queue/qmail-queue.js +93 -0
  201. package/plugins/queue/quarantine.js +133 -0
  202. package/plugins/queue/smtp_bridge.js +45 -0
  203. package/plugins/queue/smtp_forward.js +371 -0
  204. package/plugins/queue/smtp_proxy.js +142 -0
  205. package/plugins/queue/test.js +15 -0
  206. package/plugins/rcpt_to.host_list_base.js +65 -0
  207. package/plugins/rcpt_to.in_host_list.js +56 -0
  208. package/plugins/record_envelope_addresses.js +17 -0
  209. package/plugins/reseed_rng.js +7 -0
  210. package/plugins/status.js +274 -0
  211. package/plugins/tarpit.js +45 -0
  212. package/plugins/tls.js +164 -0
  213. package/plugins/toobusy.js +47 -0
  214. package/plugins/xclient.js +124 -0
  215. package/plugins.js +604 -0
  216. package/queue/1772642154987_1775581346001_4_82235_TGwgfd_2_mattbook-m3.home.simerson.net +0 -0
  217. package/run_tests +11 -0
  218. package/server.js +827 -0
  219. package/smtp_client.js +504 -0
  220. package/test/.eslintrc.yaml +11 -0
  221. package/test/config/auth_flat_file.ini +5 -0
  222. package/test/config/block_me.recipient +1 -0
  223. package/test/config/block_me.senders +1 -0
  224. package/test/config/dhparams.pem +8 -0
  225. package/test/config/host_list +2 -0
  226. package/test/config/outbound_tls_cert.pem +1 -0
  227. package/test/config/outbound_tls_key.pem +1 -0
  228. package/test/config/plugins +7 -0
  229. package/test/config/smtp.ini +11 -0
  230. package/test/config/smtp_forward.ini +30 -0
  231. package/test/config/tls/example.com/_.example.com.key +28 -0
  232. package/test/config/tls/example.com/example.com.crt +25 -0
  233. package/test/config/tls/haraka.local.pem +51 -0
  234. package/test/config/tls.ini +45 -0
  235. package/test/config/tls_cert.pem +21 -0
  236. package/test/config/tls_key.pem +28 -0
  237. package/test/connection.js +817 -0
  238. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  239. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  240. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  241. package/test/fixtures/line_socket.js +21 -0
  242. package/test/fixtures/todo_qfile.txt +0 -0
  243. package/test/fixtures/util_hmailitem.js +156 -0
  244. package/test/installation/config/test-plugin-flat +1 -0
  245. package/test/installation/config/test-plugin.ini +10 -0
  246. package/test/installation/config/tls.ini +1 -0
  247. package/test/installation/node_modules/load_first/index.js +5 -0
  248. package/test/installation/node_modules/load_first/package.json +11 -0
  249. package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
  250. package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
  251. package/test/installation/node_modules/test-plugin/package.json +5 -0
  252. package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
  253. package/test/installation/plugins/base_plugin.js +3 -0
  254. package/test/installation/plugins/folder_plugin/index.js +3 -0
  255. package/test/installation/plugins/folder_plugin/package.json +11 -0
  256. package/test/installation/plugins/inherits.js +7 -0
  257. package/test/installation/plugins/load_first.js +3 -0
  258. package/test/installation/plugins/plugin.js +1 -0
  259. package/test/installation/plugins/tls.js +3 -0
  260. package/test/logger.js +217 -0
  261. package/test/loud/config/dhparams.pem +0 -0
  262. package/test/loud/config/tls/goobered.pem +45 -0
  263. package/test/loud/config/tls.ini +43 -0
  264. package/test/mail_specimen/base64-root-part.txt +23 -0
  265. package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
  266. package/test/outbound/bounce_net_errors.js +133 -0
  267. package/test/outbound/bounce_rfc3464.js +226 -0
  268. package/test/outbound/hmail.js +210 -0
  269. package/test/outbound/index.js +385 -0
  270. package/test/outbound/qfile.js +124 -0
  271. package/test/outbound/queue.js +325 -0
  272. package/test/plugins/auth/auth_base.js +620 -0
  273. package/test/plugins/auth/auth_bridge.js +80 -0
  274. package/test/plugins/auth/auth_vpopmaild.js +81 -0
  275. package/test/plugins/auth/flat_file.js +123 -0
  276. package/test/plugins/block_me.js +141 -0
  277. package/test/plugins/data.signatures.js +111 -0
  278. package/test/plugins/delay_deny.js +262 -0
  279. package/test/plugins/prevent_credential_leaks.js +174 -0
  280. package/test/plugins/process_title.js +141 -0
  281. package/test/plugins/queue/deliver.js +98 -0
  282. package/test/plugins/queue/discard.js +78 -0
  283. package/test/plugins/queue/lmtp.js +137 -0
  284. package/test/plugins/queue/qmail-queue.js +98 -0
  285. package/test/plugins/queue/quarantine.js +80 -0
  286. package/test/plugins/queue/smtp_bridge.js +152 -0
  287. package/test/plugins/queue/smtp_forward.js +1023 -0
  288. package/test/plugins/queue/smtp_proxy.js +138 -0
  289. package/test/plugins/rcpt_to.host_list_base.js +102 -0
  290. package/test/plugins/rcpt_to.in_host_list.js +186 -0
  291. package/test/plugins/record_envelope_addresses.js +66 -0
  292. package/test/plugins/reseed_rng.js +34 -0
  293. package/test/plugins/status.js +207 -0
  294. package/test/plugins/tarpit.js +90 -0
  295. package/test/plugins/tls.js +86 -0
  296. package/test/plugins/toobusy.js +21 -0
  297. package/test/plugins/xclient.js +119 -0
  298. package/test/plugins.js +230 -0
  299. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  300. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  301. package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  302. package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  303. package/test/queue/zero-length +0 -0
  304. package/test/server.js +1012 -0
  305. package/test/smtp_client.js +1303 -0
  306. package/test/tls_socket.js +321 -0
  307. package/test/transaction.js +554 -0
  308. package/tls_socket.js +771 -0
  309. package/transaction.js +267 -0
@@ -0,0 +1,142 @@
1
+ 'use strict'
2
+ // Proxy to an SMTP server
3
+ // Opens the connection to the ongoing SMTP server at MAIL FROM time
4
+ // and passes back any errors seen on the ongoing server to the
5
+ // originating server.
6
+
7
+ const smtp_client_mod = require('../../smtp_client')
8
+ const tls_socket = require('../../tls_socket')
9
+
10
+ exports.register = function () {
11
+ this.load_smtp_proxy_ini()
12
+
13
+ if (this.cfg.main.enable_outbound) {
14
+ this.register_hook('queue_outbound', 'hook_queue')
15
+ }
16
+ }
17
+
18
+ exports.load_smtp_proxy_ini = function () {
19
+ this.cfg = this.config.get(
20
+ 'smtp_proxy.ini',
21
+ {
22
+ booleans: [
23
+ '-main.enable_tls',
24
+ '+main.enable_outbound',
25
+ '+tls.requestCert',
26
+ '+tls.honorCipherOrder',
27
+ '-tls.rejectUnauthorized',
28
+ ],
29
+ },
30
+ () => {
31
+ this.load_smtp_proxy_ini()
32
+ },
33
+ )
34
+
35
+ // Build backend TLS options from tls.ini [main] + this plugin's [tls] section.
36
+ // Re-derived on every (re)load so SIGHUP picks up edits.
37
+ this.tls_options = tls_socket.load_plugin_tls_options(this.cfg.tls || {})
38
+
39
+ if (this.cfg.main.enable_outbound) {
40
+ this.lognotice('outbound enabled, will default to disabled in Haraka v3 (see #1472)')
41
+ }
42
+ }
43
+
44
+ exports.hook_mail = function (next, connection) {
45
+ const c = this.cfg.main
46
+ connection.loginfo(
47
+ this,
48
+ `forwarding to ${c.forwarding_host_pool ? 'configured forwarding_host_pool' : `${c.host}:${c.port}`}`,
49
+ )
50
+ smtp_client_mod.get_client_plugin(this, connection, c, (err, smtp_client) => {
51
+ connection.notes.smtp_client = smtp_client
52
+ smtp_client.next = next
53
+
54
+ smtp_client.on('mail', smtp_client.call_next)
55
+ smtp_client.on('rcpt', smtp_client.call_next)
56
+ smtp_client.on('data', smtp_client.call_next)
57
+
58
+ smtp_client.on('dot', () => {
59
+ if (smtp_client.is_dead_sender(this, connection)) {
60
+ delete connection.notes.smtp_client
61
+ return
62
+ }
63
+
64
+ smtp_client.call_next(OK, smtp_client.response)
65
+ smtp_client.release()
66
+ delete connection.notes.smtp_client
67
+ })
68
+
69
+ smtp_client.on('error', () => {
70
+ delete connection.notes.smtp_client
71
+ })
72
+
73
+ smtp_client.on('bad_code', (code) => {
74
+ smtp_client.call_next(code.match(/^4/) ? DENYSOFT : DENY, smtp_client.response.slice())
75
+
76
+ if (smtp_client.command !== 'rcpt') {
77
+ // errors are OK for rcpt, but nothing else
78
+ // this can also happen if the destination server
79
+ // times out, but that is okay.
80
+ connection.loginfo(this, 'message denied, proxying failed')
81
+ smtp_client.release()
82
+ delete connection.notes.smtp_client
83
+ }
84
+ })
85
+ })
86
+ }
87
+
88
+ exports.hook_rcpt_ok = (next, connection, recipient) => {
89
+ const { smtp_client } = connection.notes
90
+ if (!smtp_client) return next()
91
+ if (smtp_client.is_dead_sender(this, connection)) {
92
+ delete connection.notes.smtp_client
93
+ return
94
+ }
95
+ smtp_client.next = next
96
+ smtp_client.send_command('RCPT', `TO:${recipient.format(!smtp_client.smtputf8)}`)
97
+ }
98
+
99
+ exports.hook_data = (next, connection) => {
100
+ const { smtp_client } = connection.notes
101
+ if (!smtp_client) return next()
102
+
103
+ if (smtp_client.is_dead_sender(this, connection)) {
104
+ delete connection.notes.smtp_client
105
+ return
106
+ }
107
+ smtp_client.next = next
108
+ smtp_client.send_command('DATA')
109
+ }
110
+
111
+ exports.hook_queue = function (next, connection) {
112
+ if (!connection?.transaction || !connection?.notes) return next()
113
+
114
+ const { smtp_client } = connection.notes
115
+ if (!smtp_client) return next()
116
+
117
+ if (smtp_client.is_dead_sender(this, connection)) {
118
+ delete connection.notes.smtp_client
119
+ return
120
+ }
121
+ smtp_client.next = next
122
+ smtp_client.start_data(connection.transaction.message_stream)
123
+ }
124
+
125
+ exports.hook_rset = (next, connection) => {
126
+ const { smtp_client } = connection.notes
127
+ if (!smtp_client) return next()
128
+ smtp_client.release()
129
+ delete connection.notes.smtp_client
130
+ next()
131
+ }
132
+
133
+ exports.hook_quit = exports.hook_rset
134
+
135
+ exports.hook_disconnect = (next, connection) => {
136
+ const { smtp_client } = connection.notes
137
+ if (!smtp_client) return next()
138
+ smtp_client.release()
139
+ delete connection.notes.smtp_client
140
+ smtp_client.call_next()
141
+ next()
142
+ }
@@ -0,0 +1,15 @@
1
+ const fs = require('node:fs')
2
+ const os = require('node:os')
3
+
4
+ const tempDir = os.tmpdir()
5
+
6
+ exports.hook_queue = function (next, connection) {
7
+ const txn = connection?.transaction
8
+ if (!txn) return next()
9
+
10
+ const file_path = `${tempDir}/mail_${txn.uuid}.eml`
11
+ const ws = fs.createWriteStream(file_path)
12
+ connection.logdebug(this, `Saving to ${file_path}`)
13
+ ws.once('close', () => next(OK))
14
+ connection.transaction.message_stream.pipe(ws)
15
+ }
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+ // Base class for plugins that use config/host_list
3
+
4
+ exports.load_host_list = function () {
5
+ const lowered_list = {} // assemble
6
+ const raw_list = this.config.get('host_list', 'list', () => {
7
+ this.load_host_list()
8
+ })
9
+
10
+ for (const i in raw_list) {
11
+ lowered_list[raw_list[i].toLowerCase()] = true
12
+ }
13
+
14
+ this.host_list = lowered_list
15
+ }
16
+
17
+ exports.load_host_list_regex = function () {
18
+ this.host_list_regex = this.config.get('host_list_regex', 'list', () => {
19
+ this.load_host_list_regex()
20
+ })
21
+
22
+ this.hl_re = new RegExp(`^(?:${this.host_list_regex.join('|')})$`, 'i')
23
+ }
24
+
25
+ exports.hook_mail = function (next, connection, params) {
26
+ const txn = connection?.transaction
27
+ if (!txn) return
28
+
29
+ const email = params[0].address
30
+ if (!email) {
31
+ txn.results.add(this, { skip: 'mail_from.null', emit: true })
32
+ return next()
33
+ }
34
+
35
+ const domain = params[0].host.toLowerCase()
36
+
37
+ const anti_spoof = this.config.get('host_list.anti_spoof') || false
38
+
39
+ if (this.in_host_list(domain, connection) || this.in_host_regex(domain, connection)) {
40
+ if (anti_spoof && !connection.relaying) {
41
+ txn.results.add(this, { fail: 'mail_from.anti_spoof' })
42
+ return next(DENY, `Mail from domain '${domain}' is not allowed from your host`)
43
+ }
44
+ txn.results.add(this, { pass: 'mail_from' })
45
+ txn.notes.local_sender = true
46
+ return next()
47
+ }
48
+
49
+ txn.results.add(this, { msg: 'mail_from!local' })
50
+ return next()
51
+ }
52
+
53
+ exports.in_host_list = function (domain, connection) {
54
+ this.logdebug(connection, `checking ${domain} in config/host_list`)
55
+ return !!this.host_list[domain]
56
+ }
57
+
58
+ exports.in_host_regex = function (domain, connection) {
59
+ if (!this.host_list_regex) return false
60
+ if (!this.host_list_regex.length) return false
61
+
62
+ this.logdebug(connection, `checking ${domain} against config/host_list_regex `)
63
+
64
+ return !!this.hl_re.test(domain)
65
+ }
@@ -0,0 +1,56 @@
1
+ 'use strict'
2
+ // Check RCPT TO domain is in config/host_list
3
+
4
+ // Previous versions of this plugin (Haraka <= 2.4.0) did not account for
5
+ // relaying users. This plugin now permits relaying clients to send if
6
+ // the message is destined to or originating from a local domain.
7
+ //
8
+ // The mail hook always checks the MAIL FROM address and when detected, sets
9
+ // connection.transaction.notes.local_sender=true. During RCPT TO, if relaying
10
+ // is enabled and the sending domain is local, the receipt is OK.
11
+
12
+ exports.register = function () {
13
+ this.inherits('rcpt_to.host_list_base')
14
+
15
+ this.load_host_list()
16
+ this.load_host_list_regex()
17
+ }
18
+
19
+ exports.hook_rcpt = function (next, connection, params) {
20
+ const txn = connection?.transaction
21
+ if (!txn) return
22
+
23
+ const rcpt = params[0]
24
+
25
+ // Check for RCPT TO without an @ first - ignore those here
26
+ if (!rcpt.host) {
27
+ txn.results.add(this, { fail: 'rcpt!domain' })
28
+ return next()
29
+ }
30
+
31
+ connection.logdebug(this, `Checking if ${rcpt} host is in host_list`)
32
+
33
+ const domain = rcpt.host.toLowerCase()
34
+
35
+ if (this.in_host_list(domain, connection)) {
36
+ txn.results.add(this, { pass: 'rcpt_to' })
37
+ return next(OK)
38
+ }
39
+
40
+ if (this.in_host_regex(domain, connection)) {
41
+ txn.results.add(this, { pass: 'rcpt_to' })
42
+ return next(OK)
43
+ }
44
+
45
+ // in this case, a client with relaying privileges is sending FROM a local
46
+ // domain. For them, any RCPT address is accepted.
47
+ if (connection.relaying && txn.notes.local_sender) {
48
+ txn.results.add(this, { pass: 'relaying local_sender' })
49
+ return next(OK)
50
+ }
51
+
52
+ // the MAIL FROM domain is not local and neither is the RCPT TO
53
+ // Another RCPT plugin may yet vouch for this recipient.
54
+ txn.results.add(this, { msg: 'rcpt!local' })
55
+ return next()
56
+ }
@@ -0,0 +1,17 @@
1
+ // record_envelope_addresses
2
+
3
+ // documentation via: haraka -h plugins/record_envelope_addresses
4
+
5
+ exports.hook_rcpt = (next, connection, params) => {
6
+ if (connection?.transaction) {
7
+ connection.transaction.add_header('X-Envelope-To', params[0].address)
8
+ }
9
+ next()
10
+ }
11
+
12
+ exports.hook_mail = (next, connection, params) => {
13
+ if (connection?.transaction) {
14
+ connection.transaction.add_header('X-Envelope-From', params[0].address)
15
+ }
16
+ next()
17
+ }
@@ -0,0 +1,7 @@
1
+ const crypto = require('node:crypto')
2
+
3
+ exports.hook_init_child = function (next) {
4
+ Math.seedrandom(crypto.randomBytes(256).toString('hex'))
5
+ this.logdebug('reseeded rng')
6
+ next()
7
+ }
@@ -0,0 +1,274 @@
1
+ 'use strict'
2
+ /* global server */
3
+
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+
7
+ exports.register = function () {
8
+ this.outbound = require('../outbound')
9
+ this.queue_dir = require('../outbound/queue').queue_dir
10
+ }
11
+
12
+ exports.hook_capabilities = (next, connection) => {
13
+ if (connection.remote.is_local) {
14
+ connection.capabilities.push('STATUS')
15
+ }
16
+
17
+ next()
18
+ }
19
+
20
+ exports.hook_unrecognized_command = function (next, connection, params) {
21
+ if (params[0] !== 'STATUS') return next()
22
+ if (!connection.remote.is_local) return next(DENY, 'STATUS not allowed remotely')
23
+
24
+ this.run(params[1], (err, result) => {
25
+ if (err) return next(DENY, err.message)
26
+
27
+ connection.respond(211, result ? JSON.stringify(result) : 'null', () => next(OK))
28
+ })
29
+ }
30
+
31
+ exports.run = function (cmd, cb) {
32
+ if (server.cluster && !/^QUEUE LIST/.test(cmd)) {
33
+ this.call_master(cmd, cb)
34
+ } else {
35
+ this.command_action(cmd, cb)
36
+ }
37
+ }
38
+
39
+ exports.command_action = function (cmd, cb) {
40
+ const params = cmd.split(' ')
41
+
42
+ switch (params.shift()) {
43
+ case 'POOL':
44
+ return this.pool_action(params, cb)
45
+ case 'QUEUE':
46
+ return this.queue_action(params, cb)
47
+ default:
48
+ cb('unknown STATUS command')
49
+ }
50
+ }
51
+
52
+ exports.pool_action = function (params, cb) {
53
+ switch (params.shift()) {
54
+ case 'LIST':
55
+ return this.pool_list(cb)
56
+ default:
57
+ cb('unknown POOL command')
58
+ }
59
+ }
60
+
61
+ exports.queue_action = function (params, cb) {
62
+ switch (params.shift()) {
63
+ case 'LIST':
64
+ return this.queue_list(cb)
65
+ case 'STATS':
66
+ return this.queue_stats(cb)
67
+ case 'INSPECT':
68
+ return this.queue_inspect(cb)
69
+ case 'DISCARD':
70
+ return this.queue_discard(params.shift(), cb)
71
+ case 'PUSH':
72
+ return this.queue_push(params.shift(), cb)
73
+ default:
74
+ cb('unknown QUEUE command')
75
+ }
76
+ }
77
+
78
+ exports.pool_list = (cb) => {
79
+ const result = {}
80
+
81
+ if (server.notes.pool) {
82
+ for (const name of Object.keys(server.notes.pool)) {
83
+ const instance = server.notes.pool[name]
84
+
85
+ result[name] = {
86
+ inUse: instance.inUseObjectsCount(),
87
+ size: instance.getPoolSize(),
88
+ }
89
+ }
90
+ }
91
+
92
+ cb(null, result)
93
+ }
94
+
95
+ exports.queue_list = async function (cb) {
96
+ try {
97
+ const qlist = await this.outbound.list_queue()
98
+ const result = []
99
+
100
+ for (const todo of qlist) {
101
+ result.push({
102
+ file: todo.file,
103
+ uuid: todo.uuid,
104
+ queue_time: todo.queue_time,
105
+ domain: todo.domain,
106
+ from: todo.mail_from.toString(),
107
+ to: todo.rcpt_to.map((r) => r.toString()),
108
+ })
109
+ }
110
+
111
+ cb(null, result)
112
+ } catch (err) {
113
+ cb(err)
114
+ }
115
+ }
116
+
117
+ exports.queue_stats = function (cb) {
118
+ cb(null, this.outbound.get_stats())
119
+ }
120
+
121
+ exports.queue_inspect = function (cb) {
122
+ const delivery_queue_items = this.outbound.delivery_queue.tasks
123
+ const fail_queue_items = this.outbound.temp_fail_queue.queue
124
+
125
+ cb(null, {
126
+ delivery_queue: delivery_queue_items.map((hmail) => ({
127
+ id: hmail.file,
128
+ })),
129
+ temp_fail_queue: fail_queue_items.map((tqtimer) => ({
130
+ id: tqtimer.id,
131
+ fire_time: tqtimer.fire_time,
132
+ })),
133
+ })
134
+ }
135
+
136
+ exports.queue_discard = function (file, cb) {
137
+ try {
138
+ this.outbound.temp_fail_queue.discard(file)
139
+ } catch {
140
+ // ignore not found error
141
+ }
142
+
143
+ fs.unlink(path.join(this.queue_dir || '', file), () => {
144
+ cb(null, 'OK')
145
+ })
146
+ }
147
+
148
+ exports.queue_push = function (file, cb) {
149
+ const { queue } = this.outbound.temp_fail_queue
150
+
151
+ for (let i = 0; i < queue.length; i++) {
152
+ if (queue[i].id !== file) continue
153
+
154
+ const item = queue.splice(i, 1)[0]
155
+ item.cb()
156
+
157
+ break
158
+ }
159
+
160
+ cb(null, 'OK')
161
+ }
162
+
163
+ // cluster IPC
164
+
165
+ exports.hook_init_master = function (next) {
166
+ const plugin = this
167
+
168
+ if (!server.cluster) return next()
169
+
170
+ function message_handler(sender, msg) {
171
+ if (msg.event !== 'status.request') return
172
+
173
+ plugin.call_workers(msg, (response) => {
174
+ const valid = response.filter((el) => el != null)
175
+ msg.result = plugin.merge_worker_responses(msg.params, valid)
176
+ msg.event = 'status.result'
177
+ sender.send(msg)
178
+ })
179
+ }
180
+
181
+ server.cluster.on('message', message_handler)
182
+ next()
183
+ }
184
+
185
+ exports.hook_init_child = function (next) {
186
+ const self = this
187
+
188
+ function message_handler(msg) {
189
+ if (msg.event !== 'status.request') return
190
+
191
+ self.command_action(msg.params, (err, result) => {
192
+ msg.event = 'status.response'
193
+ msg.result = result
194
+ process.send(msg)
195
+ })
196
+ }
197
+
198
+ process.on('message', message_handler)
199
+ next()
200
+ }
201
+
202
+ exports.call_master = (cmd, cb) => {
203
+ function message_handler(msg) {
204
+ if (msg.event !== 'status.result') return
205
+
206
+ process.removeListener('message', message_handler)
207
+ cb(null, msg.result)
208
+ }
209
+
210
+ process.on('message', message_handler)
211
+ process.send({ event: 'status.request', params: cmd })
212
+ }
213
+
214
+ exports.call_workers = function (cmd, cb) {
215
+ Promise.allSettled(Object.values(server.cluster.workers).map((w) => this.call_worker(w, cmd))).then((r) => {
216
+ cb(r.filter((s) => s.status === 'fulfilled').map((s) => s.value))
217
+ })
218
+ }
219
+
220
+ // Merge per-worker responses into a single result matching non-cluster output shape.
221
+ exports.merge_worker_responses = (params, results) => {
222
+ const cmd = params.trim().split(/\s+/).slice(0, 2).join(' ').toUpperCase()
223
+
224
+ switch (cmd) {
225
+ case 'POOL LIST': {
226
+ return Object.assign({}, ...results)
227
+ }
228
+ case 'QUEUE INSPECT': {
229
+ const merged = { delivery_queue: [], temp_fail_queue: [] }
230
+ for (const r of results) {
231
+ if (!r) continue
232
+ if (Array.isArray(r.delivery_queue)) merged.delivery_queue.push(...r.delivery_queue)
233
+ if (Array.isArray(r.temp_fail_queue)) merged.temp_fail_queue.push(...r.temp_fail_queue)
234
+ }
235
+ return merged
236
+ }
237
+ case 'QUEUE STATS': {
238
+ const totals = [0, 0, 0]
239
+ for (const r of results) {
240
+ if (!r) continue
241
+ const parts = String(r).split('/')
242
+ for (let i = 0; i < 3; i++) totals[i] += parseInt(parts[i] ?? 0, 10)
243
+ }
244
+ return totals.join('/')
245
+ }
246
+ default:
247
+ return results
248
+ }
249
+ }
250
+
251
+ // sends command to worker and then wait for response or timeout
252
+ exports.call_worker = (worker, cmd) => {
253
+ return new Promise((resolve) => {
254
+ let timeout
255
+
256
+ function message_handler(sender, msg) {
257
+ if (sender.id !== worker.id) return
258
+ if (msg.event !== 'status.response') return
259
+
260
+ clearTimeout(timeout)
261
+ server.cluster.removeListener('message', message_handler)
262
+
263
+ resolve(msg.result)
264
+ }
265
+
266
+ timeout = setTimeout(() => {
267
+ server.cluster.removeListener('message', message_handler)
268
+ resolve()
269
+ }, 1000)
270
+
271
+ server.cluster.on('message', message_handler)
272
+ worker.send(cmd)
273
+ })
274
+ }
@@ -0,0 +1,45 @@
1
+ // tarpit
2
+
3
+ let hooks_to_delay = [
4
+ 'connect',
5
+ 'helo',
6
+ 'ehlo',
7
+ 'mail',
8
+ 'rcpt',
9
+ 'rcpt_ok',
10
+ 'data',
11
+ 'data_post',
12
+ 'queue',
13
+ 'unrecognized_command',
14
+ 'vrfy',
15
+ 'noop',
16
+ 'rset',
17
+ 'quit',
18
+ ]
19
+
20
+ exports.register = function () {
21
+ // Register tarpit function last
22
+
23
+ const cfg = this.config.get('tarpit.ini')
24
+ if (cfg?.main.hooks_to_delay) {
25
+ hooks_to_delay = cfg.main.hooks_to_delay.split(/[\s,;]+/)
26
+ }
27
+
28
+ for (const hook of hooks_to_delay) {
29
+ this.register_hook(hook, 'tarpit')
30
+ }
31
+ }
32
+
33
+ exports.tarpit = function (next, connection) {
34
+ const { transaction } = connection
35
+ if (!transaction) return next()
36
+
37
+ let delay = connection.notes.tarpit
38
+
39
+ if (!delay) delay = transaction.notes.tarpit
40
+
41
+ if (!delay) return next()
42
+
43
+ connection.loginfo(this, `tarpitting response for ${delay}s`)
44
+ setTimeout(() => next(), delay * 1000)
45
+ }