haraka 0.0.33 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/.githooks/pre-commit +41 -0
  2. package/.prettierignore +7 -0
  3. package/.qlty/.gitignore +7 -0
  4. package/.qlty/configs/.shellcheckrc +1 -0
  5. package/.qlty/qlty.toml +15 -0
  6. package/CHANGELOG.md +1898 -0
  7. package/CONTRIBUTORS.md +34 -0
  8. package/Dockerfile +50 -0
  9. package/LICENSE +22 -0
  10. package/Plugins.md +227 -0
  11. package/README.md +119 -4
  12. package/SECURITY.md +178 -0
  13. package/TODO +22 -0
  14. package/bin/haraka +593 -0
  15. package/bin/haraka_grep +32 -0
  16. package/config/aliases +2 -0
  17. package/config/auth_flat_file.ini +7 -0
  18. package/config/auth_vpopmaild.ini +9 -0
  19. package/config/connection.ini +79 -0
  20. package/config/delay_deny.ini +7 -0
  21. package/config/host_list +3 -0
  22. package/config/host_list_regex +6 -0
  23. package/config/http.ini +11 -0
  24. package/config/lmtp.ini +7 -0
  25. package/config/log.ini +11 -0
  26. package/config/outbound.bounce_message +18 -0
  27. package/config/outbound.bounce_message_html +36 -0
  28. package/config/outbound.bounce_message_image +106 -0
  29. package/config/outbound.ini +24 -0
  30. package/config/plugins +67 -0
  31. package/config/smtp.ini +37 -0
  32. package/config/smtp_bridge.ini +4 -0
  33. package/config/smtp_forward.ini +31 -0
  34. package/config/smtp_proxy.ini +27 -0
  35. package/config/tarpit.timeout +1 -0
  36. package/config/tls.ini +83 -0
  37. package/config/watch.ini +12 -0
  38. package/config/xclient.hosts +2 -0
  39. package/connection.js +1865 -0
  40. package/contrib/Haraka.cf +6 -0
  41. package/contrib/Haraka.pm +35 -0
  42. package/contrib/bad_smtp_server.pl +25 -0
  43. package/contrib/bsd-rc.d/haraka +63 -0
  44. package/contrib/debian-init.d/haraka +87 -0
  45. package/contrib/haraka.init +96 -0
  46. package/contrib/haraka.service +23 -0
  47. package/contrib/plugin2npm.sh +81 -0
  48. package/contrib/ubuntu-upstart/haraka.conf +27 -0
  49. package/docs/Body.md +1 -0
  50. package/docs/Config.md +1 -0
  51. package/docs/Connection.md +153 -0
  52. package/docs/CoreConfig.md +96 -0
  53. package/docs/CustomReturnCodes.md +3 -0
  54. package/docs/HAProxy.md +62 -0
  55. package/docs/Header.md +1 -0
  56. package/docs/Logging.md +129 -0
  57. package/docs/Outbound.md +210 -0
  58. package/docs/Plugins.md +372 -0
  59. package/docs/Results.md +7 -0
  60. package/docs/Transaction.md +135 -0
  61. package/docs/Tutorial.md +183 -0
  62. package/docs/deprecated/access.md +3 -0
  63. package/docs/deprecated/backscatterer.md +9 -0
  64. package/docs/deprecated/connect.rdns_access.md +53 -0
  65. package/docs/deprecated/data.headers.md +3 -0
  66. package/docs/deprecated/data.nomsgid.md +7 -0
  67. package/docs/deprecated/data.noreceived.md +11 -0
  68. package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
  69. package/docs/deprecated/dkim_sign.md +97 -0
  70. package/docs/deprecated/dkim_verify.md +28 -0
  71. package/docs/deprecated/dnsbl.md +80 -0
  72. package/docs/deprecated/dnswl.md +73 -0
  73. package/docs/deprecated/lookup_rdns.strict.md +67 -0
  74. package/docs/deprecated/mail_from.access.md +52 -0
  75. package/docs/deprecated/mail_from.blocklist.md +18 -0
  76. package/docs/deprecated/mail_from.nobounces.md +8 -0
  77. package/docs/deprecated/rcpt_to.access.md +53 -0
  78. package/docs/deprecated/rcpt_to.blocklist.md +18 -0
  79. package/docs/deprecated/rcpt_to.routes.md +3 -0
  80. package/docs/deprecated/rdns.regexp.md +30 -0
  81. package/docs/plugins/aliases.md +3 -0
  82. package/docs/plugins/auth/auth_bridge.md +34 -0
  83. package/docs/plugins/auth/auth_ldap.md +4 -0
  84. package/docs/plugins/auth/auth_proxy.md +36 -0
  85. package/docs/plugins/auth/auth_vpopmaild.md +33 -0
  86. package/docs/plugins/auth/flat_file.md +40 -0
  87. package/docs/plugins/block_me.md +18 -0
  88. package/docs/plugins/data.signatures.md +11 -0
  89. package/docs/plugins/delay_deny.md +23 -0
  90. package/docs/plugins/max_unrecognized_commands.md +6 -0
  91. package/docs/plugins/prevent_credential_leaks.md +22 -0
  92. package/docs/plugins/process_title.md +42 -0
  93. package/docs/plugins/queue/deliver.md +3 -0
  94. package/docs/plugins/queue/discard.md +32 -0
  95. package/docs/plugins/queue/lmtp.md +24 -0
  96. package/docs/plugins/queue/qmail-queue.md +16 -0
  97. package/docs/plugins/queue/quarantine.md +87 -0
  98. package/docs/plugins/queue/smtp_bridge.md +32 -0
  99. package/docs/plugins/queue/smtp_forward.md +127 -0
  100. package/docs/plugins/queue/smtp_proxy.md +68 -0
  101. package/docs/plugins/queue/test.md +7 -0
  102. package/docs/plugins/rcpt_to.in_host_list.md +34 -0
  103. package/docs/plugins/rcpt_to.max_count.md +3 -0
  104. package/docs/plugins/record_envelope_addresses.md +20 -0
  105. package/docs/plugins/relay.md +3 -0
  106. package/docs/plugins/reseed_rng.md +16 -0
  107. package/docs/plugins/status.md +41 -0
  108. package/docs/plugins/tarpit.md +50 -0
  109. package/docs/plugins/tls.md +235 -0
  110. package/docs/plugins/toobusy.md +27 -0
  111. package/docs/plugins/xclient.md +10 -0
  112. package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
  113. package/docs/tutorials/SettingUpOutbound.md +62 -0
  114. package/eslint.config.mjs +2 -0
  115. package/haraka.js +74 -0
  116. package/haraka.sh +2 -0
  117. package/http/html/404.html +58 -0
  118. package/http/html/index.html +47 -0
  119. package/http/package.json +21 -0
  120. package/line_socket.js +24 -0
  121. package/logger.js +322 -0
  122. package/outbound/client_pool.js +59 -0
  123. package/outbound/config.js +134 -0
  124. package/outbound/hmail.js +1504 -0
  125. package/outbound/index.js +349 -0
  126. package/outbound/qfile.js +93 -0
  127. package/outbound/queue.js +399 -0
  128. package/outbound/tls.js +85 -0
  129. package/outbound/todo.js +17 -0
  130. package/package.json +100 -4
  131. package/plugins/.eslintrc.yaml +3 -0
  132. package/plugins/auth/auth_base.js +261 -0
  133. package/plugins/auth/auth_bridge.js +20 -0
  134. package/plugins/auth/auth_proxy.js +227 -0
  135. package/plugins/auth/auth_vpopmaild.js +162 -0
  136. package/plugins/auth/flat_file.js +44 -0
  137. package/plugins/block_me.js +88 -0
  138. package/plugins/data.signatures.js +30 -0
  139. package/plugins/delay_deny.js +153 -0
  140. package/plugins/prevent_credential_leaks.js +61 -0
  141. package/plugins/process_title.js +197 -0
  142. package/plugins/profile.js +11 -0
  143. package/plugins/queue/deliver.js +12 -0
  144. package/plugins/queue/discard.js +27 -0
  145. package/plugins/queue/lmtp.js +45 -0
  146. package/plugins/queue/qmail-queue.js +93 -0
  147. package/plugins/queue/quarantine.js +133 -0
  148. package/plugins/queue/smtp_bridge.js +45 -0
  149. package/plugins/queue/smtp_forward.js +371 -0
  150. package/plugins/queue/smtp_proxy.js +142 -0
  151. package/plugins/queue/test.js +15 -0
  152. package/plugins/rcpt_to.host_list_base.js +65 -0
  153. package/plugins/rcpt_to.in_host_list.js +56 -0
  154. package/plugins/record_envelope_addresses.js +17 -0
  155. package/plugins/reseed_rng.js +7 -0
  156. package/plugins/status.js +274 -0
  157. package/plugins/tarpit.js +45 -0
  158. package/plugins/tls.js +164 -0
  159. package/plugins/toobusy.js +47 -0
  160. package/plugins/xclient.js +124 -0
  161. package/plugins.js +605 -0
  162. package/run_tests +11 -0
  163. package/server.js +827 -0
  164. package/smtp_client.js +504 -0
  165. package/test/.eslintrc.yaml +11 -0
  166. package/test/config/auth_flat_file.ini +5 -0
  167. package/test/config/block_me.recipient +1 -0
  168. package/test/config/block_me.senders +1 -0
  169. package/test/config/dhparams.pem +8 -0
  170. package/test/config/host_list +2 -0
  171. package/test/config/outbound_tls_cert.pem +1 -0
  172. package/test/config/outbound_tls_key.pem +1 -0
  173. package/test/config/plugins +7 -0
  174. package/test/config/smtp.ini +11 -0
  175. package/test/config/smtp_forward.ini +30 -0
  176. package/test/config/tls/example.com/_.example.com.key +28 -0
  177. package/test/config/tls/example.com/example.com.crt +25 -0
  178. package/test/config/tls/haraka.local.pem +51 -0
  179. package/test/config/tls.ini +45 -0
  180. package/test/config/tls_cert.pem +21 -0
  181. package/test/config/tls_key.pem +28 -0
  182. package/test/connection.js +820 -0
  183. package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
  184. package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
  185. package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
  186. package/test/fixtures/line_socket.js +21 -0
  187. package/test/fixtures/todo_qfile.txt +0 -0
  188. package/test/fixtures/util_hmailitem.js +156 -0
  189. package/test/installation/config/test-plugin-flat +1 -0
  190. package/test/installation/config/test-plugin.ini +10 -0
  191. package/test/installation/config/tls.ini +1 -0
  192. package/test/installation/node_modules/load_first/index.js +5 -0
  193. package/test/installation/node_modules/load_first/package.json +11 -0
  194. package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
  195. package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
  196. package/test/installation/node_modules/test-plugin/package.json +5 -0
  197. package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
  198. package/test/installation/plugins/base_plugin.js +3 -0
  199. package/test/installation/plugins/folder_plugin/index.js +3 -0
  200. package/test/installation/plugins/folder_plugin/package.json +11 -0
  201. package/test/installation/plugins/inherits.js +7 -0
  202. package/test/installation/plugins/load_first.js +3 -0
  203. package/test/installation/plugins/plugin.js +1 -0
  204. package/test/installation/plugins/tls.js +3 -0
  205. package/test/logger.js +217 -0
  206. package/test/loud/config/dhparams.pem +0 -0
  207. package/test/loud/config/tls/goobered.pem +45 -0
  208. package/test/loud/config/tls.ini +43 -0
  209. package/test/mail_specimen/base64-root-part.txt +23 -0
  210. package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
  211. package/test/outbound/bounce_net_errors.js +133 -0
  212. package/test/outbound/bounce_rfc3464.js +226 -0
  213. package/test/outbound/hmail.js +210 -0
  214. package/test/outbound/index.js +385 -0
  215. package/test/outbound/qfile.js +124 -0
  216. package/test/outbound/queue.js +325 -0
  217. package/test/plugins/auth/auth_base.js +620 -0
  218. package/test/plugins/auth/auth_bridge.js +80 -0
  219. package/test/plugins/auth/auth_vpopmaild.js +81 -0
  220. package/test/plugins/auth/flat_file.js +123 -0
  221. package/test/plugins/block_me.js +141 -0
  222. package/test/plugins/data.signatures.js +111 -0
  223. package/test/plugins/delay_deny.js +262 -0
  224. package/test/plugins/prevent_credential_leaks.js +174 -0
  225. package/test/plugins/process_title.js +141 -0
  226. package/test/plugins/queue/deliver.js +98 -0
  227. package/test/plugins/queue/discard.js +78 -0
  228. package/test/plugins/queue/lmtp.js +137 -0
  229. package/test/plugins/queue/qmail-queue.js +98 -0
  230. package/test/plugins/queue/quarantine.js +80 -0
  231. package/test/plugins/queue/smtp_bridge.js +152 -0
  232. package/test/plugins/queue/smtp_forward.js +1023 -0
  233. package/test/plugins/queue/smtp_proxy.js +138 -0
  234. package/test/plugins/rcpt_to.host_list_base.js +102 -0
  235. package/test/plugins/rcpt_to.in_host_list.js +186 -0
  236. package/test/plugins/record_envelope_addresses.js +66 -0
  237. package/test/plugins/reseed_rng.js +34 -0
  238. package/test/plugins/status.js +207 -0
  239. package/test/plugins/tarpit.js +90 -0
  240. package/test/plugins/tls.js +86 -0
  241. package/test/plugins/toobusy.js +198 -0
  242. package/test/plugins/xclient.js +119 -0
  243. package/test/plugins.js +230 -0
  244. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  245. package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  246. package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  247. package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  248. package/test/queue/zero-length +0 -0
  249. package/test/server.js +1012 -0
  250. package/test/smtp_client.js +1303 -0
  251. package/test/tls_socket.js +321 -0
  252. package/test/transaction.js +554 -0
  253. package/tls_socket.js +771 -0
  254. package/transaction.js +267 -0
@@ -0,0 +1,399 @@
1
+ 'use strict'
2
+
3
+ const child_process = require('node:child_process')
4
+ const fs = require('node:fs/promises')
5
+ const path = require('node:path')
6
+
7
+ const { Address } = require('@haraka/email-address')
8
+ const config = require('haraka-config')
9
+ const utils = require('haraka-utils')
10
+
11
+ const logger = require('../logger')
12
+ const TimerQueue = utils.TimerQueue
13
+ const HMailItem = require('./hmail')
14
+ const obc = require('./config')
15
+ const _qfile = require('./qfile')
16
+ const obtls = require('./tls')
17
+
18
+ class Queue {
19
+ constructor(worker) {
20
+ this.worker = worker
21
+ this.tasks = []
22
+ this.running = 0
23
+ this._scheduled = false
24
+ }
25
+
26
+ push(task) {
27
+ this.tasks.push(task)
28
+ this._schedule()
29
+ }
30
+
31
+ length() {
32
+ return this.tasks.length + this.running
33
+ }
34
+
35
+ _schedule() {
36
+ if (this._scheduled) return
37
+ this._scheduled = true
38
+ setImmediate(() => {
39
+ this._scheduled = false
40
+ this._process()
41
+ })
42
+ }
43
+
44
+ _process() {
45
+ while (this.running < obc.cfg.concurrency_max && this.tasks.length > 0) {
46
+ this.running++
47
+
48
+ this.worker(this.tasks.shift())
49
+ .catch((err) => {
50
+ logger.error(exports, `Queue worker error: ${err}`)
51
+ })
52
+ .finally(() => {
53
+ this.running--
54
+ this._schedule()
55
+ })
56
+ }
57
+ }
58
+ }
59
+
60
+ exports.name = 'outbound/queue'
61
+
62
+ if (config.get('queue_dir')) {
63
+ exports.queue_dir = path.resolve(config.get('queue_dir'))
64
+ } else if (process.env.HARAKA) {
65
+ exports.queue_dir = path.resolve(process.env.HARAKA, 'queue')
66
+ } else {
67
+ exports.queue_dir = path.resolve('test', 'test-queue')
68
+ }
69
+
70
+ const load_queue = new Queue(async (file) => {
71
+ const hmail = new HMailItem(file, path.join(exports.queue_dir, file))
72
+ exports._add_hmail(hmail)
73
+ await new Promise((resolve, reject) => {
74
+ const onReady = () => {
75
+ hmail.off('error', onError)
76
+ resolve()
77
+ }
78
+ const onError = (err) => {
79
+ hmail.off('ready', onReady)
80
+ reject(err)
81
+ }
82
+ hmail.once('ready', onReady)
83
+ hmail.once('error', onError)
84
+ })
85
+ })
86
+
87
+ let in_progress = 0
88
+ const delivery_queue = (exports.delivery_queue = new Queue(async (hmail) => {
89
+ in_progress++
90
+ await new Promise((resolve) => {
91
+ hmail.next_cb = () => {
92
+ in_progress--
93
+ resolve()
94
+ }
95
+ if (obtls.cfg) {
96
+ hmail.send()
97
+ } else {
98
+ obtls.init(() => {
99
+ hmail.send()
100
+ })
101
+ }
102
+ })
103
+ }))
104
+
105
+ const temp_fail_queue = (exports.temp_fail_queue = new TimerQueue(1000, { logger }))
106
+
107
+ let queue_count = 0
108
+
109
+ exports.get_stats = () => `${in_progress}/${exports.delivery_queue.length()}/${exports.temp_fail_queue.length()}`
110
+
111
+ exports.list_queue = async () => {
112
+ return exports._load_cur_queue(null, exports._list_file)
113
+ }
114
+
115
+ exports._stat_file = async () => {
116
+ queue_count++
117
+ }
118
+
119
+ exports.stat_queue = async () => {
120
+ await exports._load_cur_queue(null, exports._stat_file)
121
+ return exports.stats()
122
+ }
123
+
124
+ exports.init_queue = async (pid) => {
125
+ // Initialise and load queue
126
+ // This function is called first when not running under cluster,
127
+ await exports.ensure_queue_dir()
128
+ await exports.delete_dot_files()
129
+
130
+ await exports._load_cur_queue(pid, exports._add_file)
131
+ logger.info(exports, `[pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`)
132
+ logger.info(exports, `[pid: ${pid}] ${load_queue.length()} files in my load queue`)
133
+ logger.info(exports, `[pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`)
134
+ }
135
+
136
+ exports._load_cur_queue = async (pid, iteratee) => {
137
+ logger.info(exports, 'Loading outbound queue from ', exports.queue_dir)
138
+ let files
139
+ try {
140
+ files = await fs.readdir(exports.queue_dir)
141
+ } catch (err) {
142
+ logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
143
+ throw err
144
+ }
145
+
146
+ exports.cur_time = new Date() // set once so we're not calling it a lot
147
+
148
+ return await exports.load_queue_files(pid, files, iteratee)
149
+ }
150
+
151
+ exports.read_parts = (file) => {
152
+ if (file.startsWith(_qfile.platformDOT)) {
153
+ logger.warn(exports, `'Skipping' dot-file in queue folder: ${file}`)
154
+ return false
155
+ }
156
+
157
+ if (file.startsWith('error.')) {
158
+ logger.warn(exports, `'Skipping' error file in queue folder: ${file}`)
159
+ return false
160
+ }
161
+
162
+ const parts = _qfile.parts(file)
163
+ if (!parts) {
164
+ logger.error(exports, `Unrecognized file in queue folder: ${file}`)
165
+ return false
166
+ }
167
+
168
+ return parts
169
+ }
170
+
171
+ exports.rename_to_actual_pid = async (file, parts) => {
172
+ // maintain some original details for the rename
173
+ const new_filename = _qfile.name({
174
+ arrival: parts.arrival,
175
+ uid: parts.uid,
176
+ next_attempt: parts.next_attempt,
177
+ attempts: parts.attempts,
178
+ })
179
+
180
+ try {
181
+ await fs.rename(path.join(exports.queue_dir, file), path.join(exports.queue_dir, new_filename))
182
+ return new_filename
183
+ } catch (err) {
184
+ throw new Error(`Unable to rename queue file: ${file} to ${new_filename}`, {
185
+ cause: err,
186
+ })
187
+ }
188
+ }
189
+
190
+ exports._add_file = async (file) => {
191
+ const parts = _qfile.parts(file)
192
+
193
+ if (parts.next_attempt <= exports.cur_time) {
194
+ logger.debug(exports, `File ${file} needs processing now`)
195
+ load_queue.push(file)
196
+ } else {
197
+ logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - exports.cur_time}ms`)
198
+ temp_fail_queue.add(file, parts.next_attempt - exports.cur_time, () => {
199
+ load_queue.push(file)
200
+ })
201
+ }
202
+ return file
203
+ }
204
+
205
+ exports.load_queue_files = async (pid, input_files, iteratee) => {
206
+ const searchPid = parseInt(pid)
207
+
208
+ let stat_renamed = 0
209
+ let stat_loaded = 0
210
+
211
+ if (searchPid) {
212
+ logger.info(exports, `Grabbing queue files for pid: ${pid}`)
213
+ } else {
214
+ logger.info(exports, 'Loading the queue...')
215
+ }
216
+
217
+ const results = await Promise.all(
218
+ input_files.map(async (file) => {
219
+ const parts = exports.read_parts(file)
220
+ if (!parts) return null
221
+
222
+ if (!searchPid) {
223
+ stat_loaded++
224
+ return file
225
+ }
226
+
227
+ if (parts.pid !== searchPid) return null
228
+
229
+ try {
230
+ const renamed_file = await exports.rename_to_actual_pid(file, parts)
231
+ stat_renamed++
232
+ stat_loaded++
233
+ return renamed_file
234
+ } catch (error) {
235
+ logger.error(exports, `${error.message}`)
236
+ return null
237
+ }
238
+ }),
239
+ )
240
+
241
+ if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`)
242
+ logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`)
243
+
244
+ const iterateeResults = await Promise.all(results.filter((i) => i).map(async (item) => await iteratee(item)))
245
+
246
+ return iterateeResults.filter((result) => result !== null && result !== undefined)
247
+ }
248
+
249
+ exports.stats = () => {
250
+ return {
251
+ queue_dir: exports.queue_dir,
252
+ queue_count,
253
+ }
254
+ }
255
+
256
+ // position `position`. Loops to handle partial reads.
257
+ // Read exactly `length` bytes into `buffer` starting at `offset`, from file
258
+ async function readFull(handle, buffer, offset, length, position) {
259
+ let totalRead = 0
260
+ while (totalRead < length) {
261
+ const { bytesRead } = await handle.read(buffer, offset + totalRead, length - totalRead, position + totalRead)
262
+ if (bytesRead === 0) {
263
+ throw new Error(`Unexpected end of file: read ${totalRead} of ${length} bytes`)
264
+ }
265
+ totalRead += bytesRead
266
+ }
267
+ }
268
+
269
+ exports._list_file = async (file) => {
270
+ let handle
271
+ try {
272
+ const filePath = path.join(exports.queue_dir, file)
273
+
274
+ handle = await fs.open(filePath, 'r')
275
+
276
+ // Read first 4 bytes to get the todo length
277
+ const buf = Buffer.alloc(4)
278
+ await readFull(handle, buf, 0, 4, 0)
279
+ const todo_len = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]
280
+
281
+ const todoBuf = Buffer.alloc(todo_len)
282
+ await readFull(handle, todoBuf, 0, todo_len, 4)
283
+
284
+ const todo = todoBuf.toString('utf8')
285
+ const todo_struct = JSON.parse(todo)
286
+ todo_struct.rcpt_to = todo_struct.rcpt_to.map((a) => new Address(a))
287
+ todo_struct.mail_from = new Address(todo_struct.mail_from)
288
+ todo_struct.file = file
289
+ todo_struct.full_path = filePath
290
+ const parts = _qfile.parts(file)
291
+ todo_struct.pid = parts?.pid || null
292
+ return todo_struct
293
+ } catch (err) {
294
+ console.error(`Error reading queue file: ${file}:`, err)
295
+ return null
296
+ } finally {
297
+ if (handle)
298
+ await handle.close().catch((err) => console.error(`Failed to close queue file handle for ${file}:`, err))
299
+ }
300
+ }
301
+
302
+ exports.flush_queue = async (domain, pid) => {
303
+ if (domain) {
304
+ try {
305
+ const qlist = await exports.list_queue()
306
+ for (const todo of qlist) {
307
+ if (todo.domain.toLowerCase() !== domain.toLowerCase()) continue
308
+ if (pid && todo.pid !== pid) continue
309
+ delivery_queue.push(new HMailItem(todo.file, todo.full_path))
310
+ }
311
+ } catch (err) {
312
+ logger.error(exports, `Failed to load queue: ${err.message}`)
313
+ }
314
+ } else {
315
+ temp_fail_queue.drain()
316
+ }
317
+ }
318
+
319
+ exports.load_pid_queue = async (pid) => {
320
+ logger.info(exports, `Loading queue for pid: ${pid}`)
321
+ await exports.init_queue(pid)
322
+ }
323
+
324
+ exports.ensure_queue_dir = async () => {
325
+ // this code is only run at start-up.
326
+ try {
327
+ await fs.access(exports.queue_dir)
328
+ return // directory already exists
329
+ } catch (ignore) {
330
+ // directory doesn't exist, try to create it
331
+ }
332
+
333
+ logger.debug(exports, `Creating queue directory ${exports.queue_dir}`)
334
+ try {
335
+ await fs.mkdir(exports.queue_dir, { mode: 493 }) // 493 == 0755
336
+ const cfg = config.get('smtp.ini')
337
+ let uid
338
+ let gid
339
+ if (cfg.user) uid = parseInt(child_process.execSync(`id -u ${cfg.user}`).toString().trim(), 10)
340
+ if (cfg.group) gid = parseInt(child_process.execSync(`id -g ${cfg.group}`).toString().trim(), 10)
341
+ if (uid && gid) {
342
+ await fs.chown(exports.queue_dir, uid, gid)
343
+ } else if (uid) {
344
+ await fs.chown(exports.queue_dir, uid, -1)
345
+ }
346
+ } catch (err) {
347
+ if (err.code !== 'EEXIST') {
348
+ logger.error(exports, `Error creating queue directory: ${err}`)
349
+ throw err
350
+ }
351
+ }
352
+ }
353
+
354
+ exports.delete_dot_files = async () => {
355
+ try {
356
+ const files = await fs.readdir(exports.queue_dir)
357
+ for (const file of files) {
358
+ if (file.startsWith(_qfile.platformDOT)) {
359
+ logger.warn(exports, `Removing left over dot-file: ${file}`)
360
+ await fs.unlink(path.join(exports.queue_dir, file))
361
+ }
362
+ }
363
+ } catch (err) {
364
+ logger.error(exports, `Error deleting dot files: ${err}`)
365
+ }
366
+ }
367
+
368
+ exports._add_hmail = (hmail) => {
369
+ if (hmail.next_process <= exports.cur_time) {
370
+ delivery_queue.push(hmail)
371
+ } else {
372
+ temp_fail_queue.add(hmail.filename, hmail.next_process - exports.cur_time, () => {
373
+ delivery_queue.push(hmail)
374
+ })
375
+ }
376
+ }
377
+
378
+ exports.scan_queue_pids = async () => {
379
+ // Under cluster, this is called first by the master
380
+ await exports.ensure_queue_dir()
381
+ await exports.delete_dot_files()
382
+
383
+ let files
384
+ try {
385
+ files = await fs.readdir(exports.queue_dir)
386
+ } catch (err) {
387
+ logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
388
+ throw err
389
+ }
390
+
391
+ const pids = {}
392
+
393
+ for (const file of files) {
394
+ const parts = exports.read_parts(file)
395
+ if (parts) pids[parts.pid] = true
396
+ }
397
+
398
+ return Object.keys(pids)
399
+ }
@@ -0,0 +1,85 @@
1
+ 'use strict'
2
+
3
+ const net = require('node:net')
4
+
5
+ const config = require('haraka-config')
6
+ const hkredis = require('haraka-plugin-redis')
7
+
8
+ const logger = require('../logger')
9
+ const tls_socket = require('../tls_socket')
10
+
11
+ class OutboundTLS {
12
+ constructor() {
13
+ this.config = config
14
+ this.name = 'OutboundTLS'
15
+ logger.add_log_methods(this)
16
+ }
17
+
18
+ test_config(tls_config, our_config) {
19
+ tls_socket.config = tls_config
20
+ this.config = our_config
21
+ }
22
+
23
+ load_config() {
24
+ const tls_cfg = tls_socket.load_tls_ini({ role: 'client' })
25
+ this.cfg = tls_socket.load_plugin_tls_options(tls_cfg.outbound || {})
26
+ this.cfg.redis = tls_cfg.redis // outbound-only: TLS NO-GO db (don't clone — has methods)
27
+ }
28
+
29
+ init(cb) {
30
+ this.load_config()
31
+ // changing this var in-flight won't work
32
+ if (this.cfg.redis && !this.cfg.redis.disable_for_failed_hosts) return cb()
33
+ logger.debug(this, 'Will disable outbound TLS for failing TLS hosts')
34
+ Object.assign(this, hkredis)
35
+ this.merge_redis_ini()
36
+ this.init_redis_plugin(cb)
37
+ }
38
+
39
+ get_tls_options(mx) {
40
+ // do NOT set servername to an IP address
41
+ if (net.isIP(mx.exchange)) {
42
+ // when mx.exchange looked up in DNS, from_dns has the hostname
43
+ if (mx.from_dns) return { ...this.cfg, servername: mx.from_dns }
44
+ return { ...this.cfg }
45
+ } else {
46
+ // mx.exchange is a hostname
47
+ return { ...this.cfg, servername: mx.exchange }
48
+ }
49
+ }
50
+
51
+ // Check for if host is prohibited from TLS negotiation
52
+ check_tls_nogo(host, cb_ok, cb_nogo) {
53
+ if (!this.cfg.redis.disable_for_failed_hosts) return cb_ok()
54
+
55
+ const dbkey = `no_tls|${host}`
56
+ this.db
57
+ .get(dbkey)
58
+ .then((dbr) => {
59
+ dbr ? cb_nogo(dbr) : cb_ok()
60
+ })
61
+ .catch((err) => {
62
+ logger.debug(this, `Redis returned error: ${err}`)
63
+ cb_ok()
64
+ })
65
+ }
66
+
67
+ mark_tls_nogo(host, cb) {
68
+ const dbkey = `no_tls|${host}`
69
+ const expiry = this.cfg.redis.disable_expiry || 604800
70
+
71
+ if (!this.cfg.redis.disable_for_failed_hosts) return cb()
72
+
73
+ logger.notice(this, `TLS connection failed. Marking ${host} as non-TLS for ${expiry} seconds`)
74
+
75
+ this.db
76
+ .setEx(dbkey, expiry, new Date().toISOString())
77
+ .then(cb)
78
+ .catch((err) => {
79
+ logger.error(this, `Redis returned error: ${err}`)
80
+ })
81
+ }
82
+ }
83
+
84
+ // this is a singleton
85
+ module.exports = new OutboundTLS()
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ // queue file header data
4
+ class TODOItem {
5
+ constructor(domain, recipients, transaction) {
6
+ this.queue_time = Date.now()
7
+ this.domain = domain
8
+ this.rcpt_to = recipients
9
+ this.mail_from = transaction.mail_from
10
+ this.message_stream = transaction.message_stream
11
+ this.notes = transaction.notes
12
+ this.uuid = transaction.uuid
13
+ this.force_tls = false
14
+ }
15
+ }
16
+
17
+ module.exports = TODOItem
package/package.json CHANGED
@@ -1,7 +1,103 @@
1
1
  {
2
- "deprecated": true,
3
- "description": "Inactive module name",
4
- "license": "MIT",
2
+ "author": "Matt Sergeant <helpme@gmail.com>",
5
3
  "name": "haraka",
6
- "version": "0.0.33"
4
+ "license": "MIT",
5
+ "description": "An SMTP Server project.",
6
+ "keywords": [
7
+ "haraka",
8
+ "smtp",
9
+ "server",
10
+ "email"
11
+ ],
12
+ "version": "3.3.1",
13
+ "homepage": "http://haraka.github.io",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git://github.com/haraka/Haraka.git"
17
+ },
18
+ "main": "haraka.js",
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "dependencies": {
23
+ "@haraka/email-address": "~3.1.6",
24
+ "haraka-config": "~1.6.3",
25
+ "haraka-constants": "~1.0.8",
26
+ "haraka-dsn": "~1.2.0",
27
+ "haraka-email-message": "~1.4.0",
28
+ "haraka-net-utils": "~1.9.2",
29
+ "haraka-notes": "~1.1.3",
30
+ "haraka-plugin-redis": "~2.1.0",
31
+ "haraka-results": "~2.4.0",
32
+ "haraka-tld": "~1.3.6",
33
+ "haraka-utils": "~2.2.1",
34
+ "ipaddr.js": "~2.4.0",
35
+ "node-gyp": "~12.4.0",
36
+ "nopt": "~10.0.1",
37
+ "redis": "~6.0.0",
38
+ "semver": "~7.8.4"
39
+ },
40
+ "optionalDependencies": {
41
+ "@haraka/ocsp": "~1.2.0",
42
+ "haraka-plugin-access": "~1.4.0",
43
+ "haraka-plugin-aliases": "~1.1.0",
44
+ "haraka-plugin-asn": "~2.2.0",
45
+ "haraka-plugin-attachment": "~1.2.1",
46
+ "haraka-plugin-bounce": "~2.2.1",
47
+ "haraka-plugin-clamd": "~1.0.3",
48
+ "haraka-plugin-dcc": "~1.0.3",
49
+ "haraka-plugin-dkim": "~1.2.0",
50
+ "haraka-plugin-dns-list": "~1.3.0",
51
+ "haraka-plugin-early_talker": "~1.0.2",
52
+ "haraka-plugin-fcrdns": "~1.2.1",
53
+ "haraka-plugin-geoip": "~1.1.2",
54
+ "haraka-plugin-greylist": "~1.2.1",
55
+ "haraka-plugin-headers": "~1.2.0",
56
+ "haraka-plugin-helo.checks": "~1.1.3",
57
+ "haraka-plugin-karma": "~2.5.2",
58
+ "haraka-plugin-known-senders": "~1.2.0",
59
+ "haraka-plugin-limit": "~1.3.2",
60
+ "haraka-plugin-mail_from.is_resolvable": "~1.3.0",
61
+ "haraka-plugin-messagesniffer": "~1.0.2",
62
+ "haraka-plugin-qmail-deliverable": "~1.4.0",
63
+ "haraka-plugin-relay": "~1.0.2",
64
+ "haraka-plugin-rspamd": "~1.6.0",
65
+ "haraka-plugin-spamassassin": "~1.1.0",
66
+ "haraka-plugin-spf": "~1.3.0",
67
+ "haraka-plugin-syslog": "~1.1.2",
68
+ "haraka-plugin-uribl": "~2.0.0"
69
+ },
70
+ "devDependencies": {
71
+ "@haraka/eslint-config": "~3.0.0",
72
+ "haraka-test-fixtures": "^1.7.1",
73
+ "mock-require": "~3.0.3",
74
+ "toobusy-js": "^0.5.1"
75
+ },
76
+ "bugs": {
77
+ "mail": "haraka.mail@gmail.com",
78
+ "url": "https://github.com/haraka/Haraka/issues"
79
+ },
80
+ "bin": {
81
+ "haraka": "bin/haraka",
82
+ "haraka_grep": "bin/haraka_grep"
83
+ },
84
+ "scripts": {
85
+ "prepare": "git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true",
86
+ "format": "npm run prettier:fix && npm run lint:fix",
87
+ "lint": "npx eslint *.js outbound plugins plugins/*/*.js test test/*/*.js test/*/*/*.js",
88
+ "lint:fix": "npx eslint --fix *.js outbound plugins plugins/*/*.js test test/*/*.js test/*/*/*.js",
89
+ "prettier": "npx prettier . --check",
90
+ "prettier:fix": "npx prettier . --write --log-level=warn",
91
+ "qlty": "qlty smells --all",
92
+ "test": "sh ./run_tests",
93
+ "test:coverage": "node --test --test-concurrency=1 --experimental-test-coverage --test-coverage-include=*.js --test-coverage-include=plugins/**/*.js --test-coverage-include=outbound/*.js",
94
+ "test:coverage:lcov": "mkdir -p coverage && node --test --test-concurrency=1 --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-coverage-include=*.js --test-coverage-include=plugins/**/*.js --test-coverage-include=outbound/*.js",
95
+ "versions": "npx npm-dep-mgr check",
96
+ "versions:fix": "npx npm-dep-mgr update"
97
+ },
98
+ "prettier": {
99
+ "singleQuote": true,
100
+ "printWidth": 120,
101
+ "semi": false
102
+ }
7
103
  }
@@ -0,0 +1,3 @@
1
+ globals:
2
+ server: true
3
+ NEXT_HOOK: true