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/logger.js ADDED
@@ -0,0 +1,322 @@
1
+ 'use strict'
2
+ // Log class
3
+
4
+ const util = require('node:util')
5
+ const tty = require('node:tty')
6
+
7
+ const config = require('haraka-config')
8
+ const constants = require('haraka-constants')
9
+
10
+ let plugins
11
+
12
+ const regex = /(^$|[ ="\\])/
13
+ const escape_replace_regex = /["\\]/g
14
+
15
+ function stringify(obj) {
16
+ let str = ''
17
+ let key
18
+ for (key in obj) {
19
+ let v = obj[key]
20
+ if (v == null) {
21
+ str += `${key}="" `
22
+ continue
23
+ }
24
+ v = v.toString()
25
+ if (regex.test(v)) {
26
+ str += `${key}="${v.replace(escape_replace_regex, '\\$&')}" `
27
+ } else {
28
+ str += `${key}=${v} `
29
+ }
30
+ }
31
+ return str.trim()
32
+ }
33
+
34
+ const logger = exports
35
+
36
+ logger.levels = {
37
+ DATA: 9,
38
+ PROTOCOL: 8,
39
+ DEBUG: 7,
40
+ INFO: 6,
41
+ NOTICE: 5,
42
+ WARN: 4,
43
+ ERROR: 3,
44
+ CRIT: 2,
45
+ ALERT: 1,
46
+ EMERG: 0,
47
+ }
48
+ const level_names = Object.keys(logger.levels)
49
+
50
+ for (const le in logger.levels) {
51
+ logger.levels[`LOG${le}`] = logger.levels[le]
52
+ logger[`LOG${le}`] = logger.levels[le]
53
+ }
54
+
55
+ logger.formats = {
56
+ DEFAULT: 'DEFAULT',
57
+ LOGFMT: 'LOGFMT',
58
+ JSON: 'JSON',
59
+ }
60
+
61
+ logger.loglevel = logger.levels.WARN
62
+ logger.format = logger.formats.DEFAULT
63
+ logger.timestamps = false
64
+ logger.deferred_logs = []
65
+ logger.name = 'logger'
66
+
67
+ logger.colors = {
68
+ DATA: 'green',
69
+ PROTOCOL: 'green',
70
+ DEBUG: 'grey',
71
+ INFO: 'cyan',
72
+ NOTICE: 'blue',
73
+ WARN: 'red',
74
+ ERROR: 'red',
75
+ CRIT: 'red',
76
+ ALERT: 'red',
77
+ EMERG: 'red',
78
+ }
79
+
80
+ const stdout_is_tty = tty.isatty(process.stdout.fd)
81
+
82
+ logger._init = function () {
83
+ this.load_log_ini()
84
+ this._init_loglevel()
85
+ this._init_timestamps()
86
+ }
87
+
88
+ logger.load_log_ini = function () {
89
+ this.cfg = config.get(
90
+ 'log.ini',
91
+ {
92
+ booleans: ['+main.timestamps'],
93
+ },
94
+ () => {
95
+ this.load_log_ini()
96
+ },
97
+ )
98
+
99
+ this.set_loglevel(this.cfg.main.level)
100
+ this.set_timestamps(this.cfg.main.timestamps)
101
+ this.set_format(this.cfg.main.format)
102
+ }
103
+
104
+ logger.colorize = (color, str) => {
105
+ if (!util.inspect.colors[color]) {
106
+ return str
107
+ } // unknown color
108
+ return `\u001b[${util.inspect.colors[color][0]}m${str}\u001b[${util.inspect.colors[color][1]}m`
109
+ }
110
+
111
+ logger.dump_logs = (cb) => {
112
+ while (logger.deferred_logs.length > 0) {
113
+ const log_item = logger.deferred_logs.shift()
114
+ plugins.run_hooks('log', logger, log_item)
115
+ }
116
+ // Run callback after flush
117
+ if (cb) process.stdout.write('', cb)
118
+ return true
119
+ }
120
+
121
+ logger.dump_and_exit = function (code) {
122
+ this.dump_logs(() => {
123
+ if (typeof code === 'function') return code()
124
+ process.exit(code)
125
+ })
126
+ }
127
+
128
+ logger.log = (level, data, logobj) => {
129
+ if (level === 'PROTOCOL') {
130
+ data = data.replace(/\n/g, '\\n')
131
+ }
132
+ data = data.replace(/\r/g, '\\r').replace(/\n$/, '')
133
+
134
+ const item = { level, data, obj: logobj }
135
+
136
+ // buffer until plugins are loaded
137
+ const emptyPluginList = !plugins || (Array.isArray(plugins.plugin_list) && !plugins.plugin_list.length)
138
+ if (emptyPluginList) {
139
+ logger.deferred_logs.push(item)
140
+ return true
141
+ }
142
+
143
+ // process buffered logs
144
+ while (logger.deferred_logs.length > 0) {
145
+ const log_item = logger.deferred_logs.shift()
146
+ plugins.run_hooks('log', logger, log_item)
147
+ }
148
+
149
+ plugins.run_hooks('log', logger, item)
150
+ return true
151
+ }
152
+
153
+ logger.log_respond = (retval, msg, data) => {
154
+ // any other return code is irrelevant
155
+ if (retval !== constants.cont) return false
156
+
157
+ let timestamp_string = ''
158
+ if (logger.timestamps) timestamp_string = `${new Date().toISOString()} `
159
+
160
+ const color = logger.colors[data.level]
161
+ if (color && stdout_is_tty) {
162
+ process.stdout.write(`${timestamp_string}${logger.colorize(color, data.data)}\n`)
163
+ } else {
164
+ process.stdout.write(`${timestamp_string}${data.data}\n`)
165
+ }
166
+
167
+ return true
168
+ }
169
+
170
+ logger.set_loglevel = function (level) {
171
+ if (level === undefined || level === null) return
172
+
173
+ const loglevel_num = parseInt(level)
174
+ if (typeof level === 'string') {
175
+ this.log('INFO', `loglevel: ${level.toUpperCase()}`)
176
+ logger.loglevel = logger.levels[level.toUpperCase()]
177
+ } else {
178
+ logger.loglevel = loglevel_num
179
+ }
180
+
181
+ if (!Number.isInteger(logger.loglevel)) {
182
+ this.log('WARN', `invalid loglevel: ${level} defaulting to LOGWARN`)
183
+ logger.loglevel = logger.levels.WARN
184
+ }
185
+ }
186
+
187
+ logger.set_format = function (format) {
188
+ if (format) {
189
+ logger.format = logger.formats[format.toUpperCase()]
190
+ this.log('INFO', `log format: ${format.toUpperCase()}`)
191
+ } else {
192
+ logger.format = null
193
+ }
194
+ if (!logger.format) {
195
+ this.log('WARN', `invalid log format: ${format} defaulting to DEFAULT`)
196
+ logger.format = logger.formats.DEFAULT
197
+ }
198
+ }
199
+
200
+ logger._init_loglevel = function () {
201
+ const _loglevel = config.get('loglevel', 'value', () => {
202
+ this._init_loglevel()
203
+ })
204
+
205
+ this.set_loglevel(_loglevel)
206
+ }
207
+
208
+ logger.would_log = (level) => {
209
+ if (logger.loglevel < level) return false
210
+ return true
211
+ }
212
+
213
+ logger.set_timestamps = (value) => {
214
+ logger.timestamps = !!value
215
+ }
216
+
217
+ logger._init_timestamps = function () {
218
+ const _timestamps = config.get('log_timestamps', 'value', () => {
219
+ this._init_timestamps()
220
+ })
221
+
222
+ // If we've already been toggled to true by the cfg, we should respect this.
223
+ this.set_timestamps(logger.timestamps || _timestamps)
224
+ }
225
+
226
+ logger._init()
227
+
228
+ logger.log_if_level = (level, key, origin) =>
229
+ function () {
230
+ if (logger.loglevel < logger[key]) return
231
+
232
+ let logobj = {
233
+ level,
234
+ uuid: '-',
235
+ origin: origin || 'core',
236
+ message: '',
237
+ }
238
+
239
+ for (const data of arguments) {
240
+ if (typeof data !== 'object') {
241
+ logobj.message += data
242
+ continue
243
+ }
244
+ if (!data) continue
245
+
246
+ // if the object is a connection, add the connection id
247
+ if (data.constructor?.name === 'Connection') {
248
+ logobj.uuid = data.uuid
249
+ if (data.tran_count > 0) logobj.uuid += `.${data.tran_count}`
250
+ } else if (data instanceof plugins.Plugin) {
251
+ logobj.origin = data.name
252
+ } else if (Object.hasOwn(data, 'name')) {
253
+ // outbound
254
+ logobj.origin = data.name
255
+ if (Object.hasOwn(data, 'uuid')) logobj.uuid = data.uuid
256
+ if (data.todo?.uuid) logobj.uuid = data.todo.uuid // outbound/hmail
257
+ } else if (logger.format === logger.formats.LOGFMT && data.constructor === Object) {
258
+ logobj = { ...logobj, ...data }
259
+ } else if (logger.format === logger.formats.JSON && data.constructor === Object) {
260
+ logobj = { ...logobj, ...data }
261
+ } else if (Object.hasOwn(data, 'uuid')) {
262
+ // outbound/client_pool
263
+ logobj.uuid = data.uuid
264
+ } else if (data.constructor === Object) {
265
+ if (!logobj.message.endsWith(' ')) logobj.message += ' '
266
+ logobj.message += stringify(data)
267
+ } else {
268
+ logobj.message += util.inspect(data)
269
+ }
270
+ }
271
+
272
+ switch (logger.format) {
273
+ case logger.formats.LOGFMT:
274
+ logger.log(level, stringify(logobj))
275
+ break
276
+ case logger.formats.JSON:
277
+ logger.log(level, JSON.stringify(logobj))
278
+ break
279
+ case logger.formats.DEFAULT:
280
+ default:
281
+ logger.log(level, `[${logobj.level}] [${logobj.uuid}] [${logobj.origin}] ${logobj.message}`)
282
+ }
283
+ return true
284
+ }
285
+
286
+ logger.add_log_methods = (object, logName) => {
287
+ if (!object) return
288
+
289
+ if (typeof object === 'function') {
290
+ // add logging methods to class prototypes (Connection, Plugin, etc.)
291
+
292
+ for (const level of level_names.map((l) => l.toLowerCase())) {
293
+ object.prototype[`log${level}`] = (function (level) {
294
+ return function () {
295
+ logger[level].apply(logger, [this, ...arguments])
296
+ }
297
+ })(`log${level}`)
298
+ }
299
+ } else if (typeof object === 'object') {
300
+ // add logging methods to objects
301
+
302
+ for (const level of level_names) {
303
+ // objects gets log function names: loginfo, logwarn, logdebug, ...
304
+ const fnNames = [`log${level.toLowerCase()}`]
305
+
306
+ // logger also gets short names
307
+ if (Object.hasOwn(object, 'name') && object.name === 'logger') {
308
+ fnNames.push(level.toLowerCase())
309
+ }
310
+
311
+ for (const fnName of fnNames) {
312
+ if (Object.hasOwn(object, fnName)) continue // already added
313
+ object[fnName] = logger.log_if_level(level, `LOG${level}`, logName)
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ logger.add_log_methods(logger)
320
+
321
+ // load these down here so it sees all the logger methods compiled above
322
+ plugins = require('./plugins')
@@ -0,0 +1,59 @@
1
+ 'use strict'
2
+
3
+ const util = require('node:util')
4
+
5
+ const utils = require('haraka-utils')
6
+ const net_utils = require('haraka-net-utils')
7
+
8
+ const tls_socket = require('../tls_socket')
9
+ const logger = require('../logger')
10
+ const obc = require('./config')
11
+
12
+ exports.name = 'outbound'
13
+
14
+ // Get a socket for the given attributes.
15
+ exports.get_client = function (mx, callback) {
16
+ const socketArgs = mx.path ? { path: mx.path } : { port: mx.port, host: mx.exchange, localAddress: mx.bind }
17
+
18
+ const socket = tls_socket.connect(socketArgs)
19
+ net_utils.add_line_processor(socket)
20
+
21
+ socket.name = `outbound::${JSON.stringify(socketArgs)}`
22
+ socket.__uuid = utils.uuid()
23
+ socket.setTimeout(obc.cfg.connect_timeout * 1000)
24
+
25
+ logger.debug(exports, `created ${socket.name}`, { uuid: socket.__uuid })
26
+
27
+ socket.once('connect', () => {
28
+ socket.removeAllListeners('error') // these get added after callback
29
+ socket.removeAllListeners('timeout')
30
+ callback(null, socket)
31
+ })
32
+
33
+ socket.once('error', (err) => {
34
+ socket.end()
35
+ socket.removeAllListeners()
36
+ socket.destroy()
37
+ const errMsg = err.message
38
+ ? err.message
39
+ : err instanceof AggregateError
40
+ ? err.errors.map((e) => e.message).join(', ')
41
+ : util.inspect(err, { depth: 3 })
42
+ callback(errMsg, null)
43
+ })
44
+
45
+ socket.once('timeout', () => {
46
+ socket.end()
47
+ socket.removeAllListeners()
48
+ socket.destroy()
49
+ callback(`connection timed out to ${socket.name}`, null)
50
+ })
51
+ }
52
+
53
+ exports.release_client = (socket, mx) => {
54
+ let logMsg = `release_client: ${socket.name}`
55
+ if (mx.bind) logMsg += ` from ${mx.bind}`
56
+ logger.debug(exports, logMsg)
57
+ socket.removeAllListeners()
58
+ socket.destroy()
59
+ }
@@ -0,0 +1,134 @@
1
+ 'use strict'
2
+
3
+ const config = require('haraka-config')
4
+ const logger = require('../logger')
5
+
6
+ exports.name = 'outbound/config'
7
+
8
+ function load_config() {
9
+ const cfg = (exports.cfg = config.get(
10
+ 'outbound.ini',
11
+ {
12
+ booleans: ['-disabled', '-always_split', '+enable_tls', '-local_mx_ok'],
13
+ },
14
+ () => {
15
+ load_config()
16
+ },
17
+ ).main)
18
+
19
+ if (!cfg.inet_prefer) cfg.inet_prefer = 'default'
20
+ if (!cfg.inet_prefer.match(/^(v4|v6|default)$/)) {
21
+ logger.warn(exports, `inet_prefer is set to an invalid value: ${cfg.inet_prefer}`)
22
+ cfg.inet_prefer = 'default'
23
+ }
24
+
25
+ // legacy config file support. Remove in Haraka 4.0
26
+ if (!cfg.disabled && config.get('outbound.disabled')) {
27
+ cfg.disabled = true
28
+ }
29
+ if (!cfg.enable_tls && config.get('outbound.enable_tls')) {
30
+ cfg.enable_tls = true
31
+ }
32
+ if (!cfg.temp_fail_intervals) {
33
+ cfg.temp_fail_intervals = config.get('outbound.temp_fail_intervals')
34
+ }
35
+ if (!cfg.maxTempFailures) {
36
+ cfg.maxTempFailures = config.get('outbound.maxTempFailures') || 13
37
+ }
38
+ if (!cfg.concurrency_max) {
39
+ cfg.concurrency_max = config.get('outbound.concurrency_max') || 10000
40
+ }
41
+ if (!cfg.connect_timeout) {
42
+ cfg.connect_timeout = 30
43
+ }
44
+ if (!cfg.received_header) {
45
+ cfg.received_header = config.get('outbound.received_header') || 'Haraka outbound'
46
+ }
47
+
48
+ exports.set_temp_fail_intervals()
49
+ }
50
+
51
+ exports.set_temp_fail_intervals = function () {
52
+ // Set the outbound temp fail intervals (retry times) using the following rules:
53
+ // 1) temp_fail_intervals takes precedence over maxTempFailures if both are specified
54
+ // 2) if temp_fail_intervals is not specified or is illegally specified, then initialize
55
+ // it with the equivalent times of maxTempFailures using the original 2^N formula
56
+ // 3) the word "none" can be specified if you do not want to retry a temp failure,
57
+ // equivalent behavior of specifying maxTempFailures=1
58
+ const { cfg } = this
59
+
60
+ // Fallback function to create an array of the original retry times
61
+ function set_old_defaults() {
62
+ cfg.temp_fail_intervals = []
63
+ for (let i = 1; i < cfg.maxTempFailures; i++) {
64
+ cfg.temp_fail_intervals.push(2 ** (i + 5))
65
+ }
66
+ }
67
+
68
+ // Helpful error function in case of parsing failure
69
+ function error(i, msg) {
70
+ logger.error(exports, `temp_fail_intervals syntax error parsing element ${i}: ${msg}`)
71
+ logger.warn(exports, 'Setting outbound temp_fail_intervals to old defaults')
72
+ set_old_defaults()
73
+ }
74
+
75
+ // If the new value isn't specified, then create the old defaults
76
+ if (!cfg.temp_fail_intervals) {
77
+ return set_old_defaults()
78
+ }
79
+
80
+ // If here then turn the text input into an expanded array of intervals (in seconds)
81
+ // i.e, turn "1m,5m*2,1h*3" into [60,300,300,3600,3600,3600]
82
+ // Parse manually to do better syntax checking and provide better failure messages
83
+ const times = []
84
+ let input = cfg.temp_fail_intervals.replace(/\s+/g, '').toLowerCase()
85
+ if (input.length === 0) return error(0, 'nothing specified')
86
+ if (input === 'none') {
87
+ cfg.temp_fail_intervals = []
88
+ return
89
+ }
90
+ input = input.split(',')
91
+
92
+ for (let i = 0; i < input.length; i++) {
93
+ const delay = input[i].split('*')
94
+ if (delay.length === 1) delay.push(1)
95
+ else if (delay.length === 2) delay[1] = Number(delay[1])
96
+ else return error(i, 'too many *')
97
+ if (!Number.isInteger(delay[1])) return error(i, 'multiplier is not an integer')
98
+
99
+ if (delay[0].length < 2) error(i, 'invalid time span')
100
+ const symbol = delay[0].charAt(delay[0].length - 1)
101
+ let num = Number(delay[0].slice(0, -1))
102
+ if (isNaN(num)) return error(i, 'invalid number or symbol')
103
+
104
+ switch (symbol) {
105
+ case 's':
106
+ // do nothing, this is the base unit
107
+ break
108
+ case 'm':
109
+ num *= 60
110
+ break
111
+ case 'h':
112
+ num *= 3600
113
+ break
114
+ case 'd':
115
+ num *= 86400
116
+ break
117
+ default:
118
+ return error(i, 'invalid time span symbol')
119
+ }
120
+ // Sanity check (what should this number be?)
121
+ if (num < 5) return error(i, 'delay time too small, should be >=5 seconds')
122
+ for (let j = 0; j < delay[1]; j++) {
123
+ times.push(num)
124
+ }
125
+ }
126
+
127
+ // One last check, just in case...should never be true
128
+ if (times.length === 0) return error(0, 'unexpected parsing result')
129
+
130
+ // If here, success, so actually store the calculated array in the config
131
+ cfg.temp_fail_intervals = times
132
+ }
133
+
134
+ load_config()