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
package/plugins.js ADDED
@@ -0,0 +1,604 @@
1
+ 'use strict'
2
+ // load all defined plugins
3
+
4
+ // node built-ins
5
+ const fs = require('node:fs')
6
+ const path = require('node:path')
7
+ const vm = require('node:vm')
8
+
9
+ // npm modules
10
+ exports.config = require('haraka-config')
11
+ const constants = require('haraka-constants')
12
+
13
+ // local modules
14
+ const logger = require('./logger')
15
+
16
+ exports.registered_hooks = {}
17
+ exports.registered_plugins = {}
18
+ exports.plugin_list = []
19
+
20
+ let order = 0
21
+
22
+ class Plugin {
23
+ constructor(name) {
24
+ this.name = name
25
+ this.base = {}
26
+ this.timeout = get_timeout(name)
27
+ this._get_plugin_path()
28
+ this.config = this._get_config()
29
+ this.hooks = {}
30
+ }
31
+
32
+ haraka_require(name) {
33
+ return require(`./${name}`)
34
+ }
35
+
36
+ _get_plugin_path() {
37
+ /* From https://github.com/haraka/Haraka/pull/1278#issuecomment-168856528
38
+ In Development mode, or install via a plain "git clone":
39
+
40
+ Plain plugin in plugins/ folder
41
+ Plugin in a folder in plugins/<name>/ folder. Contains a package.json.
42
+ Plugin in node_modules. Contains a package.json file.
43
+
44
+ In "installed" mode (via haraka -i <path>):
45
+
46
+ Plain plugin in <path>/plugins/ folder
47
+ Plugin in a folder in <path>/plugins/<name>/folder. (same concept as above)
48
+ Plugin in <path>/node_modules. Contains a package.json file.
49
+ Core plugin in <core_haraka_dir>/plugins/ folder
50
+ Plugin in a folder in <core_haraka_dir>/plugins/<name>/ folder. (same concept as above)
51
+ Plugin in <core_haraka_dir>/node_modules.
52
+ */
53
+
54
+ this.hasPackageJson = false
55
+ const name = this.name.startsWith('haraka-plugin-') ? this.name.slice(14) : this.name
56
+ if (this.name !== name) this.name = name
57
+
58
+ let paths = []
59
+ if (process.env.HARAKA) {
60
+ // Installed mode - started via bin/haraka
61
+ paths = [...paths, ...plugin_search_paths(process.env.HARAKA, name)]
62
+
63
+ // permit local "folder" plugins (/$name/package.json) (see #1649)
64
+ paths.push(
65
+ path.resolve(process.env.HARAKA, 'plugins', name, 'package.json'),
66
+ path.resolve(process.env.HARAKA, 'node_modules', name, 'package.json'),
67
+ )
68
+ }
69
+
70
+ // development mode
71
+ paths = [...paths, ...plugin_search_paths(__dirname, name)]
72
+ for (const pp of paths) {
73
+ if (this.plugin_path) continue
74
+ try {
75
+ fs.statSync(pp)
76
+ this.plugin_path = pp
77
+ if (path.basename(pp) === 'package.json') {
78
+ this.hasPackageJson = true
79
+ }
80
+ } catch (ignore) {}
81
+ }
82
+ }
83
+
84
+ _get_config() {
85
+ if (this.hasPackageJson) {
86
+ // It's a package/folder plugin - look in plugin folder for defaults,
87
+ // haraka/config folder for overrides
88
+ return exports.config.module_config(path.dirname(this.plugin_path), process.env.HARAKA || __dirname)
89
+ }
90
+
91
+ if (process.env.HARAKA) {
92
+ // Plain .js file, installed mode - look in core folder for defaults,
93
+ // install dir for overrides
94
+ return exports.config.module_config(__dirname, process.env.HARAKA)
95
+ }
96
+
97
+ if (process.env.HARAKA_TEST_DIR) {
98
+ return exports.config.module_config(process.env.HARAKA_TEST_DIR)
99
+ }
100
+
101
+ // Plain .js file, git mode - just look in this folder
102
+ return exports.config.module_config(__dirname)
103
+ }
104
+
105
+ register_hook(hook_name, method_name, priority) {
106
+ priority = parseInt(priority)
107
+ if (!priority) priority = 0
108
+ if (priority > 100) priority = 100
109
+ if (priority < -100) priority = -100
110
+
111
+ if (!Array.isArray(exports.registered_hooks[hook_name])) {
112
+ exports.registered_hooks[hook_name] = []
113
+ }
114
+ exports.registered_hooks[hook_name].push({
115
+ plugin: this.name,
116
+ method: method_name,
117
+ priority,
118
+ timeout: this.timeout,
119
+ order: order++,
120
+ })
121
+ this.hooks[hook_name] = this.hooks[hook_name] || []
122
+ this.hooks[hook_name].push(method_name)
123
+
124
+ plugins.logdebug(`registered hook ${hook_name} to ${this.name}.${method_name} priority ${priority}`)
125
+ }
126
+
127
+ register() {} // noop
128
+
129
+ inherits(parent_name) {
130
+ const parent_plugin = plugins._load_and_compile_plugin(parent_name)
131
+ for (const method in parent_plugin) {
132
+ if (!this[method]) {
133
+ this[method] = parent_plugin[method]
134
+ }
135
+ }
136
+ if (parent_plugin.register) {
137
+ parent_plugin.register.call(this)
138
+ }
139
+ this.base[parent_name] = parent_plugin
140
+ }
141
+
142
+ _make_custom_require() {
143
+ return (module) => {
144
+ if (this.hasPackageJson) {
145
+ const mod = require(module)
146
+ constants.import(global)
147
+ global.server = plugins.server
148
+ return mod
149
+ }
150
+
151
+ if (module === './config') {
152
+ return this.config
153
+ }
154
+
155
+ if (!/^\./.test(module)) {
156
+ return require(module)
157
+ }
158
+
159
+ for (const ext of [`${module}.js`, module]) {
160
+ if (fs.existsSync(path.join(__dirname, ext))) {
161
+ return require(module)
162
+ }
163
+ }
164
+
165
+ return require(path.join(path.dirname(this.plugin_path), module))
166
+ }
167
+ }
168
+
169
+ _get_code(pi_path) {
170
+ if (this.hasPackageJson) {
171
+ let packageDir = path.dirname(pi_path)
172
+ if (/^win(32|64)/.test(process.platform)) {
173
+ // escape the c:\path\back\slashes else they disappear
174
+ packageDir = packageDir.replace(/\\/g, '\\\\')
175
+ }
176
+ return `var _p = require("${packageDir}"); for (var k in _p) { exports[k] = _p[k] }`
177
+ }
178
+
179
+ try {
180
+ return `"use strict";${fs.readFileSync(pi_path)}`
181
+ } catch (err) {
182
+ if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
183
+ plugins.logcrit(`Loading ${this.name} failed: ${err}`)
184
+ return
185
+ }
186
+ throw `Loading plugin ${this.name} failed: ${err}`
187
+ }
188
+ }
189
+
190
+ _compile() {
191
+ const pp = this.plugin_path
192
+ const code = this._get_code(pp)
193
+ if (!code) return
194
+
195
+ const sandbox = {
196
+ require: this._make_custom_require(),
197
+ __filename: pp,
198
+ __dirname: path.dirname(pp),
199
+ exports: this,
200
+ setTimeout,
201
+ clearTimeout,
202
+ setInterval,
203
+ clearInterval,
204
+ process,
205
+ Buffer,
206
+ Math,
207
+ server: plugins.server,
208
+ setImmediate,
209
+ }
210
+ if (this.hasPackageJson) {
211
+ delete sandbox.__filename
212
+ }
213
+ constants.import(sandbox)
214
+ try {
215
+ vm.runInNewContext(code, sandbox, pp)
216
+ } catch (err) {
217
+ plugins.logcrit(`compiling '${this.name}' failed`)
218
+ if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
219
+ plugins.logcrit(`Loading '${this.name}' failed: ${err.message} - skipping`)
220
+ return
221
+ }
222
+ throw err // default is to re-throw and stop Haraka
223
+ }
224
+ }
225
+ }
226
+
227
+ exports.shutdown_plugins = () => {
228
+ for (const i in exports.registered_plugins) {
229
+ if (exports.registered_plugins[i].shutdown) {
230
+ exports.registered_plugins[i].shutdown()
231
+ }
232
+ }
233
+ }
234
+
235
+ process.on('message', (msg) => {
236
+ if (msg.event && msg.event == 'plugins.shutdown') {
237
+ plugins.loginfo('Shutting down')
238
+ exports.shutdown_plugins()
239
+ }
240
+ })
241
+
242
+ function plugin_search_paths(prefix, name) {
243
+ return [
244
+ // Haraka/plugins/*.js
245
+ path.resolve(prefix, 'plugins', `${name}.js`),
246
+
247
+ // Haraka/node_modules/haraka-plugin-*/package.json
248
+ path.resolve(prefix, 'node_modules', `haraka-plugin-${name}`, 'package.json'),
249
+
250
+ // global node_modules/haraka-plugin-*/package.json
251
+ path.resolve(prefix, '..', `haraka-plugin-${name}`, 'package.json'),
252
+ ]
253
+ }
254
+
255
+ function get_timeout(name) {
256
+ let timeout = parseFloat(exports.config.get(`${name}.timeout`))
257
+ if (isNaN(timeout)) {
258
+ plugins.logdebug(`no timeout in ${name}.timeout`)
259
+ timeout = parseFloat(exports.config.get('plugin_timeout'))
260
+ }
261
+ if (isNaN(timeout)) {
262
+ plugins.logdebug('no timeout in plugin_timeout')
263
+ timeout = 30
264
+ }
265
+
266
+ plugins.logdebug(`plugin ${name} timeout is: ${timeout}s`)
267
+ return timeout
268
+ }
269
+
270
+ logger.add_log_methods(Plugin)
271
+
272
+ const plugins = exports
273
+
274
+ logger.add_log_methods(plugins, 'plugins')
275
+
276
+ plugins.Plugin = Plugin
277
+
278
+ plugins.load_plugins = (override) => {
279
+ plugins.logdebug('Loading')
280
+ let plugin_list
281
+ if (override) {
282
+ if (!Array.isArray(override)) override = [override]
283
+ plugin_list = override
284
+ } else {
285
+ plugin_list = exports.config.get('plugins', 'list')
286
+ }
287
+
288
+ for (let plugin of plugin_list) {
289
+ if (plugin.startsWith('haraka-plugin-')) plugin = plugin.substring(14)
290
+ if (plugins.deprecated[plugin]) {
291
+ plugins.lognotice(
292
+ `${plugin} has been replaced by '${plugins.deprecated[plugin]}'. Please update config/plugins`,
293
+ )
294
+ plugins.load_plugin(plugins.deprecated[plugin])
295
+ } else {
296
+ plugins.load_plugin(plugin)
297
+ }
298
+ }
299
+
300
+ plugins.plugin_list = Object.keys(plugins.registered_plugins)
301
+
302
+ // Sort registered_hooks by priority
303
+ for (const hook of Object.keys(plugins.registered_hooks)) {
304
+ plugins.registered_hooks[hook].sort((a, b) => {
305
+ if (a.priority < b.priority) return -1
306
+ if (a.priority > b.priority) return 1
307
+ if (a.priority == b.priority) {
308
+ if (a.order > b.order) return 1
309
+ return -1
310
+ }
311
+ return 0
312
+ })
313
+ }
314
+
315
+ logger.dump_logs() // now logging plugins are loaded.
316
+ }
317
+
318
+ plugins.deprecated = {
319
+ 'auth/auth_ldap': 'auth-ldap',
320
+ backscatterer: 'dns-list',
321
+ 'connect.asn': 'asn',
322
+ 'connect.fcrdns': 'fcrdns',
323
+ 'connect.geoip': 'geoip',
324
+ 'connect.p0f': 'p0f',
325
+ 'connect.rdns_access': 'access',
326
+ 'data.nomsgid': 'headers',
327
+ 'data.noreceived': 'headers',
328
+ 'data.rfc5322_header_checks': 'headers',
329
+ 'data.headers': 'headers',
330
+ dkim_sign: 'dkim',
331
+ dkim_verify: 'dkim',
332
+ 'data.uribl': 'uribl',
333
+ dnsbl: 'dns-list',
334
+ dnswl: 'dns-list',
335
+ 'log.syslog': 'syslog',
336
+ 'mail_from.access': 'access',
337
+ 'mail_from.blocklist': 'access',
338
+ 'mail_from.nobounces': 'bounce',
339
+ max_unrecognized_commands: 'limit',
340
+ rate_limit: 'limit',
341
+ 'rcpt_to.access': 'access',
342
+ 'rcpt_to.blocklist': 'access',
343
+ 'rcpt_to.ldap': 'rcpt-ldap',
344
+ 'rcpt_to.max_count': 'limit',
345
+ 'rcpt_to.qmail_deliverable': 'qmail-deliverable',
346
+ 'rdns.regexp': 'access',
347
+ relay_acl: 'relay',
348
+ relay_all: 'relay',
349
+ relay_force_routing: 'relay',
350
+ }
351
+
352
+ plugins.load_plugin = (name) => {
353
+ plugins.loginfo(`loading ${name}`)
354
+
355
+ const plugin = plugins._load_and_compile_plugin(name)
356
+ if (plugin) {
357
+ plugins._register_plugin(plugin)
358
+ }
359
+
360
+ plugins.registered_plugins[name] = plugin
361
+ }
362
+
363
+ // Set in server.js; initialized to empty object
364
+ // to prevent it from blowing up any unit tests.
365
+ plugins.server = { notes: {} }
366
+
367
+ plugins._load_and_compile_plugin = (name) => {
368
+ const plugin = new Plugin(name)
369
+ if (!plugin.plugin_path) {
370
+ const err = `Loading plugin ${plugin.name} failed: No plugin with this name found`
371
+ if (exports.config.get('smtp.ini').main.ignore_bad_plugins) {
372
+ plugins.logcrit(err)
373
+ return
374
+ }
375
+ throw err
376
+ }
377
+ plugin._compile()
378
+ return plugin
379
+ }
380
+
381
+ plugins._register_plugin = (plugin) => {
382
+ plugin.register()
383
+
384
+ // register any hook_blah methods.
385
+ for (const method in plugin) {
386
+ const result = method.match(/^hook_(\w+)\b/)
387
+ if (result) {
388
+ plugin.register_hook(result[1], method)
389
+ }
390
+ }
391
+ }
392
+
393
+ plugins.run_hooks = (hook, object, params) => {
394
+ if (client_disconnected(object) && !is_required_hook(hook)) {
395
+ object.logdebug(`aborting ${hook} hook`)
396
+ return
397
+ }
398
+
399
+ if (hook !== 'log') object.logdebug(`running ${hook} hooks`)
400
+
401
+ if (is_required_hook(hook) && object.current_hook) {
402
+ object.current_hook[2]() // call cancel function
403
+ }
404
+
405
+ if (!is_required_hook(hook) && hook !== 'deny' && object.hooks_to_run && object.hooks_to_run.length) {
406
+ throw new Error('We are already running hooks! Fatal error!')
407
+ }
408
+
409
+ if (hook === 'deny') {
410
+ // Save the hooks_to_run list so that we can run any remaining
411
+ // plugins on the previous hook once this hook is complete.
412
+ object.saved_hooks_to_run = object.hooks_to_run
413
+ }
414
+ object.hooks_to_run = []
415
+
416
+ if (plugins.registered_hooks[hook]) {
417
+ for (const item of plugins.registered_hooks[hook]) {
418
+ const plugin = plugins.registered_plugins[item.plugin]
419
+ object.hooks_to_run.push([plugin, item.method])
420
+ }
421
+ }
422
+
423
+ plugins.run_next_hook(hook, object, params)
424
+ }
425
+
426
+ plugins.run_next_hook = (hook, object, params) => {
427
+ if (client_disconnected(object) && !is_required_hook(hook)) {
428
+ object.logdebug(`aborting ${hook} hook`)
429
+ return
430
+ }
431
+ let called_once = false
432
+ let timeout_id
433
+ let timed_out = false
434
+ let cancelled = false
435
+ let item
436
+
437
+ function cancel() {
438
+ if (timeout_id) clearTimeout(timeout_id)
439
+ cancelled = true
440
+ }
441
+ function callback(retval, msg) {
442
+ if (timeout_id) clearTimeout(timeout_id)
443
+ object.current_hook = null
444
+ if (cancelled) return // This hook has been cancelled
445
+
446
+ // Bail if client has disconnected
447
+ if (client_disconnected(object) && !is_required_hook(hook)) {
448
+ object.logdebug(`ignoring ${item[0].name} plugin callback`)
449
+ return
450
+ }
451
+ if (called_once && hook !== 'log') {
452
+ if (!timed_out) {
453
+ object.logerror(`${item[0].name} plugin ran callback ` + `multiple times - ignoring subsequent calls`)
454
+ // Write a stack trace to the log to aid debugging
455
+ object.logerror(new Error().stack)
456
+ }
457
+ return
458
+ }
459
+ called_once = true
460
+ if (!retval) retval = constants.cont
461
+
462
+ log_run_item(item, hook, retval, object, params, msg)
463
+
464
+ if (object.hooks_to_run.length !== 0) {
465
+ if (retval === constants.cont) {
466
+ return plugins.run_next_hook(hook, object, params)
467
+ }
468
+ if (hook === 'connect_init' || hook === 'disconnect') {
469
+ // these hooks ignore retval and always run for every plugin
470
+ return plugins.run_next_hook(hook, object, params)
471
+ }
472
+ }
473
+
474
+ const respond_method = `${hook}_respond`
475
+ if (item && is_deny_retval(retval) && hook.substring(0, 5) !== 'init_') {
476
+ object.deny_respond = get_denyfn(object, hook, params, retval, msg, respond_method)
477
+ plugins.run_hooks('deny', object, [retval, msg, item[0].name, item[1], params, hook])
478
+ } else {
479
+ object.hooks_to_run = []
480
+ object[respond_method](retval, msg, params)
481
+ }
482
+ }
483
+
484
+ if (!object.hooks_to_run.length) return callback()
485
+
486
+ // shift the next one off the stack and run it.
487
+ item = object.hooks_to_run.shift()
488
+ item.push(cancel)
489
+
490
+ if (hook !== 'log' && item[0].timeout) {
491
+ timeout_id = setTimeout(() => {
492
+ timed_out = true
493
+ object.logcrit(`Plugin ${item[0].name} timed out on hook ${hook} - make sure it calls the callback`)
494
+ callback(constants.denysoft, 'plugin timeout')
495
+ }, item[0].timeout * 1000)
496
+ }
497
+
498
+ if (hook !== 'log') {
499
+ object.logdebug(`running ${hook} hook in ${item[0].name} plugin`)
500
+ }
501
+
502
+ if (object.transaction?.notes.skip_plugins.includes(item[0].name)) {
503
+ object.logdebug(`skipping ${item[0].name}_${hook} by request in notes`)
504
+ return callback()
505
+ }
506
+
507
+ try {
508
+ object.current_hook = item
509
+ object.hook = hook
510
+ item[0][item[1]].call(item[0], callback, object, params)
511
+ } catch (err) {
512
+ if (hook !== 'log') {
513
+ object.logcrit(`Plugin ${item[0].name} failed: ${err.stack || err}`)
514
+ }
515
+ callback()
516
+ }
517
+ }
518
+
519
+ function client_disconnected(object) {
520
+ if (object.constructor.name === 'Connection' && object.state >= constants.connection.state.DISCONNECTING) {
521
+ object.logdebug('client has disconnected')
522
+ return true
523
+ }
524
+ return false
525
+ }
526
+
527
+ function is_required_hook(hook) {
528
+ // Hooks that must always run
529
+ switch (hook) {
530
+ case 'reset_transaction':
531
+ case 'disconnect':
532
+ case 'log':
533
+ return true
534
+ default:
535
+ return false
536
+ }
537
+ }
538
+
539
+ function log_run_item(item, hook, retval, object, params, msg) {
540
+ if (!item) return
541
+ if (hook === 'log') return
542
+
543
+ let log = 'logdebug'
544
+ const is_not_cont = retval !== constants.cont && logger.would_log(logger.LOGINFO)
545
+ if (is_not_cont) log = 'loginfo'
546
+ if (is_not_cont || logger.would_log(logger.LOGDEBUG)) {
547
+ object[log]({
548
+ hook,
549
+ plugin: item[0].name,
550
+ function: item[1],
551
+ params: params ? (typeof params === 'string' ? params : params[0]) : '',
552
+ retval: constants.translate(retval),
553
+ msg: sanitize(msg),
554
+ })
555
+ }
556
+ }
557
+
558
+ function sanitize(msg) {
559
+ if (!msg) return ''
560
+ if (typeof msg === 'string') return msg
561
+ if (typeof msg === 'object') {
562
+ if (msg.constructor.name === 'DSN') return msg.reply
563
+ const sanitized = { ...msg } // copy the message
564
+ for (const priv of ['password', 'auth_pass']) {
565
+ delete sanitized[priv]
566
+ }
567
+ return JSON.stringify(sanitized)
568
+ }
569
+ logger.logerror(`what is ${msg} (typeof ${typeof msg})?`)
570
+ }
571
+
572
+ function is_deny_retval(val) {
573
+ switch (val) {
574
+ case constants.deny:
575
+ case constants.denysoft:
576
+ case constants.denydisconnect:
577
+ case constants.denysoftdisconnect:
578
+ return true
579
+ }
580
+ return false
581
+ }
582
+
583
+ function get_denyfn(object, hook, params, retval, msg, respond_method) {
584
+ return (deny_retval, deny_msg) => {
585
+ switch (deny_retval) {
586
+ case constants.ok:
587
+ // Override rejection
588
+ object.loginfo(`deny(soft?) overriden by deny hook${deny_msg ? ': deny_msg' : ''}`)
589
+ // Restore hooks_to_run with saved copy so that
590
+ // any other plugins on this hook can also run.
591
+ if (object.saved_hooks_to_run.length > 0) {
592
+ object.hooks_to_run = object.saved_hooks_to_run
593
+ plugins.run_next_hook(hook, object, params)
594
+ } else {
595
+ object[respond_method](constants.cont, deny_msg, params)
596
+ }
597
+ break
598
+ default:
599
+ object.saved_hooks_to_run = []
600
+ object.hooks_to_run = []
601
+ object[respond_method](retval, msg, params)
602
+ }
603
+ }
604
+ }
package/run_tests ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+
3
+ # Files using node:test
4
+ NODE_TEST_FILES="test/*.js test/outbound/*.js test/plugins/*.js test/plugins/auth/*.js test/plugins/queue/*.js"
5
+
6
+ if [ -n "$1" ]; then
7
+ node --test "$1"
8
+ else
9
+ # default, run 'em all
10
+ node --test --test-concurrency=1 $NODE_TEST_FILES
11
+ fi