Haraka 3.0.3 → 3.0.4

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 (238) hide show
  1. package/.eslintrc.yaml +5 -9
  2. package/.prettierrc.yml +1 -0
  3. package/CONTRIBUTORS.md +11 -0
  4. package/Changes.md +1365 -1214
  5. package/Plugins.md +117 -105
  6. package/README.md +4 -13
  7. package/bin/haraka +197 -298
  8. package/config/auth_flat_file.ini +1 -0
  9. package/config/dhparams.pem +8 -0
  10. package/config/mail_from.is_resolvable.ini +4 -2
  11. package/config/me +1 -0
  12. package/config/outbound.ini +0 -2
  13. package/config/plugins +36 -35
  14. package/config/smtp.ini +0 -1
  15. package/config/smtp.json +17 -0
  16. package/config/tls_cert.pem +23 -0
  17. package/config/tls_key.pem +28 -0
  18. package/connection.js +46 -73
  19. package/contrib/bsd-rc.d/haraka +3 -1
  20. package/contrib/plugin2npm.sh +6 -36
  21. package/docs/CoreConfig.md +2 -2
  22. package/docs/Logging.md +7 -21
  23. package/docs/Outbound.md +104 -201
  24. package/docs/Plugins.md +2 -2
  25. package/docs/Transaction.md +59 -82
  26. package/docs/plugins/queue/smtp_proxy.md +5 -10
  27. package/docs/plugins/tls.md +29 -9
  28. package/endpoint.js +16 -13
  29. package/haraka.js +10 -14
  30. package/host_pool.js +5 -5
  31. package/line_socket.js +3 -4
  32. package/logger.js +44 -28
  33. package/outbound/client_pool.js +27 -23
  34. package/outbound/config.js +4 -6
  35. package/outbound/fsync_writestream.js +1 -1
  36. package/outbound/hmail.js +178 -218
  37. package/outbound/index.js +86 -99
  38. package/outbound/qfile.js +1 -1
  39. package/outbound/queue.js +51 -44
  40. package/outbound/timer_queue.js +3 -2
  41. package/outbound/tls.js +19 -7
  42. package/package.json +59 -48
  43. package/plugins/.eslintrc.yaml +0 -6
  44. package/plugins/auth/auth_base.js +4 -2
  45. package/plugins/auth/auth_proxy.js +14 -12
  46. package/plugins/auth/auth_vpopmaild.js +1 -1
  47. package/plugins/block_me.js +1 -1
  48. package/plugins/data.signatures.js +2 -4
  49. package/plugins/early_talker.js +2 -1
  50. package/plugins/mail_from.is_resolvable.js +65 -135
  51. package/plugins/queue/deliver.js +4 -5
  52. package/plugins/queue/lmtp.js +11 -14
  53. package/plugins/queue/qmail-queue.js +2 -2
  54. package/plugins/queue/quarantine.js +2 -2
  55. package/plugins/queue/rabbitmq.js +16 -17
  56. package/plugins/queue/smtp_forward.js +3 -3
  57. package/plugins/queue/smtp_proxy.js +10 -1
  58. package/plugins/queue/test.js +2 -2
  59. package/plugins/rcpt_to.host_list_base.js +5 -5
  60. package/plugins/rcpt_to.in_host_list.js +2 -2
  61. package/plugins/relay.js +6 -7
  62. package/plugins/reseed_rng.js +1 -1
  63. package/plugins/status.js +37 -33
  64. package/plugins/tls.js +2 -2
  65. package/plugins/xclient.js +3 -2
  66. package/plugins.js +50 -54
  67. package/run_tests +3 -30
  68. package/server.js +190 -190
  69. package/smtp_client.js +30 -23
  70. package/{tests → test}/config/plugins +0 -2
  71. package/{tests → test}/config/smtp.ini +1 -1
  72. package/test/config/tls/example.com/_.example.com.key +28 -0
  73. package/test/config/tls/example.com/example.com.crt +25 -0
  74. package/test/connection.js +302 -0
  75. package/test/endpoint.js +94 -0
  76. package/{tests → test}/fixtures/line_socket.js +1 -1
  77. package/{tests → test}/fixtures/util_hmailitem.js +19 -25
  78. package/{tests → test}/host_pool.js +42 -57
  79. package/test/logger.js +258 -0
  80. package/test/outbound/hmail.js +141 -0
  81. package/test/outbound/index.js +220 -0
  82. package/test/outbound/qfile.js +126 -0
  83. package/test/outbound_bounce_net_errors.js +142 -0
  84. package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
  85. package/test/plugins/auth/auth_base.js +484 -0
  86. package/test/plugins/auth/auth_vpopmaild.js +83 -0
  87. package/test/plugins/early_talker.js +104 -0
  88. package/test/plugins/mail_from.is_resolvable.js +35 -0
  89. package/test/plugins/queue/smtp_forward.js +206 -0
  90. package/test/plugins/rcpt_to.host_list_base.js +122 -0
  91. package/test/plugins/rcpt_to.in_host_list.js +193 -0
  92. package/test/plugins/relay.js +303 -0
  93. package/test/plugins/status.js +130 -0
  94. package/test/plugins/tls.js +70 -0
  95. package/test/plugins.js +228 -0
  96. package/test/rfc1869.js +73 -0
  97. package/test/server.js +491 -0
  98. package/test/smtp_client.js +299 -0
  99. package/test/tls_socket.js +273 -0
  100. package/test/transaction.js +270 -0
  101. package/tls_socket.js +202 -252
  102. package/transaction.js +8 -23
  103. package/CONTRIBUTING.md +0 -1
  104. package/bin/dkimverify +0 -40
  105. package/config/access.domains +0 -13
  106. package/config/attachment.ctype.regex +0 -2
  107. package/config/attachment.filename.regex +0 -1
  108. package/config/avg.ini +0 -5
  109. package/config/bounce.ini +0 -15
  110. package/config/data.headers.ini +0 -61
  111. package/config/dkim/dkim_key_gen.sh +0 -78
  112. package/config/dkim_sign.ini +0 -4
  113. package/config/dkim_verify.ini +0 -7
  114. package/config/dnsbl.ini +0 -23
  115. package/config/greylist.ini +0 -43
  116. package/config/helo.checks.ini +0 -52
  117. package/config/messagesniffer.ini +0 -18
  118. package/config/spamassassin.ini +0 -56
  119. package/dkim.js +0 -614
  120. package/docs/plugins/avg.md +0 -35
  121. package/docs/plugins/bounce.md +0 -69
  122. package/docs/plugins/clamd.md +0 -147
  123. package/docs/plugins/esets.md +0 -8
  124. package/docs/plugins/greylist.md +0 -90
  125. package/docs/plugins/helo.checks.md +0 -135
  126. package/docs/plugins/messagesniffer.md +0 -163
  127. package/docs/plugins/spamassassin.md +0 -180
  128. package/outbound/mx_lookup.js +0 -70
  129. package/plugins/auth/auth_ldap.js +0 -3
  130. package/plugins/avg.js +0 -162
  131. package/plugins/backscatterer.js +0 -25
  132. package/plugins/bounce.js +0 -381
  133. package/plugins/clamd.js +0 -382
  134. package/plugins/data.uribl.js +0 -4
  135. package/plugins/dkim_sign.js +0 -395
  136. package/plugins/dkim_verify.js +0 -62
  137. package/plugins/dns_list_base.js +0 -221
  138. package/plugins/dnsbl.js +0 -146
  139. package/plugins/dnswl.js +0 -58
  140. package/plugins/esets.js +0 -71
  141. package/plugins/graph.js +0 -5
  142. package/plugins/greylist.js +0 -645
  143. package/plugins/helo.checks.js +0 -533
  144. package/plugins/messagesniffer.js +0 -381
  145. package/plugins/rcpt_to.ldap.js +0 -3
  146. package/plugins/rcpt_to.max_count.js +0 -24
  147. package/plugins/spamassassin.js +0 -384
  148. package/tests/config/dkim/example.com/dns +0 -29
  149. package/tests/config/dkim/example.com/private +0 -6
  150. package/tests/config/dkim/example.com/public +0 -4
  151. package/tests/config/dkim/example.com/selector +0 -1
  152. package/tests/config/dkim.private.key +0 -6
  153. package/tests/config/dkim_sign.ini +0 -4
  154. package/tests/config/helo.checks.ini +0 -52
  155. package/tests/connection.js +0 -327
  156. package/tests/endpoint.js +0 -128
  157. package/tests/fixtures/vm_harness.js +0 -59
  158. package/tests/logger.js +0 -327
  159. package/tests/outbound/hmail.js +0 -112
  160. package/tests/outbound/index.js +0 -324
  161. package/tests/outbound/qfile.js +0 -67
  162. package/tests/outbound_bounce_net_errors.js +0 -173
  163. package/tests/plugins/auth/auth_base.js +0 -463
  164. package/tests/plugins/auth/auth_vpopmaild.js +0 -91
  165. package/tests/plugins/bounce.js +0 -307
  166. package/tests/plugins/clamd.js +0 -224
  167. package/tests/plugins/deprecated/relay_acl.js +0 -140
  168. package/tests/plugins/deprecated/relay_all.js +0 -59
  169. package/tests/plugins/dkim_sign.js +0 -315
  170. package/tests/plugins/dkim_signer.js +0 -108
  171. package/tests/plugins/dns_list_base.js +0 -259
  172. package/tests/plugins/dnsbl.js +0 -101
  173. package/tests/plugins/early_talker.js +0 -115
  174. package/tests/plugins/greylist.js +0 -58
  175. package/tests/plugins/helo.checks.js +0 -525
  176. package/tests/plugins/mail_from.is_resolvable.js +0 -116
  177. package/tests/plugins/queue/smtp_forward.js +0 -221
  178. package/tests/plugins/rcpt_to.host_list_base.js +0 -132
  179. package/tests/plugins/rcpt_to.in_host_list.js +0 -218
  180. package/tests/plugins/relay.js +0 -339
  181. package/tests/plugins/spamassassin.js +0 -171
  182. package/tests/plugins/status.js +0 -138
  183. package/tests/plugins/tls.js +0 -84
  184. package/tests/plugins.js +0 -247
  185. package/tests/rfc1869.js +0 -61
  186. package/tests/server.js +0 -510
  187. package/tests/smtp_client/auth.js +0 -105
  188. package/tests/smtp_client/basic.js +0 -101
  189. package/tests/smtp_client.js +0 -80
  190. package/tests/tls_socket.js +0 -333
  191. package/tests/transaction.js +0 -284
  192. /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
  193. /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
  194. /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
  195. /package/docs/{plugins → deprecated}/dnswl.md +0 -0
  196. /package/{tests → test}/.eslintrc.yaml +0 -0
  197. /package/{tests → test}/config/auth_flat_file.ini +0 -0
  198. /package/{tests → test}/config/dhparams.pem +0 -0
  199. /package/{tests → test}/config/host_list +0 -0
  200. /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
  201. /package/{tests → test}/config/outbound_tls_key.pem +0 -0
  202. /package/{tests → test}/config/smtp_forward.ini +0 -0
  203. /package/{tests → test}/config/tls/ec.pem +0 -0
  204. /package/{tests → test}/config/tls/haraka.local.pem +0 -0
  205. /package/{tests → test}/config/tls/mismatched.pem +0 -0
  206. /package/{tests → test}/config/tls.ini +0 -0
  207. /package/{tests → test}/config/tls_cert.pem +0 -0
  208. /package/{tests → test}/config/tls_key.pem +0 -0
  209. /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
  210. /package/{tests → test}/installation/config/test-plugin-flat +0 -0
  211. /package/{tests → test}/installation/config/test-plugin.ini +0 -0
  212. /package/{tests → test}/installation/config/tls.ini +0 -0
  213. /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
  214. /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
  215. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
  216. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
  217. /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
  218. /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
  219. /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
  220. /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
  221. /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
  222. /package/{tests → test}/installation/plugins/inherits.js +0 -0
  223. /package/{tests → test}/installation/plugins/load_first.js +0 -0
  224. /package/{tests → test}/installation/plugins/plugin.js +0 -0
  225. /package/{tests → test}/installation/plugins/tls.js +0 -0
  226. /package/{tests → test}/loud/config/dhparams.pem +0 -0
  227. /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
  228. /package/{tests → test}/loud/config/tls.ini +0 -0
  229. /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
  230. /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
  231. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  232. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  233. /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  234. /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  235. /package/{tests → test}/queue/multibyte +0 -0
  236. /package/{tests → test}/queue/plain +0 -0
  237. /package/{tests → test}/queue/zero-length +0 -0
  238. /package/{tests → test}/test-queue/delete-me +0 -0
package/outbound/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
5
 
6
- const async = require('async');
7
- const { Address } = require('address-rfc2821');
6
+ const { Address } = require('address-rfc2821');
8
7
  const config = require('haraka-config');
9
8
  const constants = require('haraka-constants');
10
9
  const net_utils = require('haraka-net-utils');
@@ -24,21 +23,21 @@ const _qfile = exports.qfile = require('./qfile');
24
23
 
25
24
  const { queue_dir, temp_fail_queue, delivery_queue } = queuelib;
26
25
 
26
+ const smtp_ini = config.get('smtp.ini', { booleans: [ '+headers.add_received' ] })
27
+
27
28
  exports.temp_fail_queue = temp_fail_queue;
28
29
  exports.delivery_queue = delivery_queue;
29
30
 
31
+ exports.name = 'outbound';
30
32
  exports.net_utils = net_utils;
31
33
  exports.config = config;
32
34
 
33
- exports.get_stats = queuelib.get_stats;
34
- exports.list_queue = queuelib.list_queue;
35
- exports.stat_queue = queuelib.stat_queue;
36
- exports.scan_queue_pids = queuelib.scan_queue_pids;
37
- exports.flush_queue = queuelib.flush_queue;
38
- exports.load_pid_queue = queuelib.load_pid_queue;
39
- exports.ensure_queue_dir = queuelib.ensure_queue_dir;
40
- exports.load_queue = queuelib.load_queue;
41
- exports.stats = queuelib.stats;
35
+ const qlfns = ['get_stats', 'list_queue', 'stat_queue', 'scan_queue_pids', 'flush_queue',
36
+ 'load_pid_queue', 'ensure_queue_dir', 'load_queue', 'stats'
37
+ ]
38
+ for (const n of qlfns) {
39
+ exports[n] = queuelib[n];
40
+ }
42
41
 
43
42
  process.on('message', msg => {
44
43
  if (!msg.event) return
@@ -52,40 +51,27 @@ process.on('message', msg => {
52
51
  return;
53
52
  }
54
53
  if (msg.event === 'outbound.shutdown') {
55
- logger.loginfo("[outbound] Shutting down temp fail queue");
54
+ logger.info(exports, "Shutting down temp fail queue");
56
55
  temp_fail_queue.shutdown();
57
56
  return;
58
57
  }
59
58
  // ignores the message
60
59
  });
61
60
 
62
- exports.send_email = function () {
61
+ exports.send_email = function (from, to, contents, next, options = {}) {
63
62
 
64
- if (arguments.length === 2) {
65
- logger.logdebug("[outbound] Sending email as a transaction");
66
- return this.send_trans_email(arguments[0], arguments[1]);
67
- }
63
+ const dot_stuffed = options.dot_stuffed ?? false;
64
+ const notes = options.notes ?? null;
65
+ const origin = options.origin ?? exports;
68
66
 
69
- let from = arguments[0];
70
- let to = arguments[1];
71
- let contents = arguments[2];
72
- const next = arguments[3];
73
- const options = arguments[4] || {};
67
+ logger.info("Sending email via params", origin);
74
68
 
75
- const dot_stuffed = options.dot_stuffed ? options.dot_stuffed : false;
76
- const notes = options.notes ? options.notes : null;
77
- const origin = options.origin ? options.origin : null;
69
+ const transaction = trans.createTransaction(null, smtp_ini);
78
70
 
79
- logger.loginfo("[outbound] Sending email via params", origin);
71
+ logger.info(`Created transaction: ${transaction.uuid}`, origin);
80
72
 
81
- const transaction = trans.createTransaction();
82
-
83
- logger.loginfo(`[outbound] Created transaction: ${transaction.uuid}`, origin);
84
-
85
- //Adding notes passed as parameter
86
- if (notes) {
87
- transaction.notes = notes;
88
- }
73
+ // Adding notes passed as parameter
74
+ if (notes) transaction.notes = notes;
89
75
 
90
76
  // set MAIL FROM address, and parse if it's not an Address object
91
77
  if (from instanceof Address) {
@@ -102,10 +88,7 @@ exports.send_email = function () {
102
88
  }
103
89
 
104
90
  // Make sure to is an array
105
- if (!(Array.isArray(to))) {
106
- // turn into an array
107
- to = [ to ];
108
- }
91
+ if (!(Array.isArray(to))) to = [ to ];
109
92
 
110
93
  if (to.length === 0) {
111
94
  return next(constants.deny, "No recipients for email");
@@ -199,10 +182,10 @@ function get_deliveries (transaction) {
199
182
  const deliveries = [];
200
183
 
201
184
  if (obc.cfg.always_split) {
202
- logger.logdebug({name: "outbound"}, "always split");
203
- transaction.rcpt_to.forEach(rcpt => {
185
+ logger.debug(exports, "always split");
186
+ for (const rcpt of transaction.rcpt_to) {
204
187
  deliveries.push({domain: rcpt.host, rcpts: [ rcpt ]});
205
- });
188
+ }
206
189
  return deliveries;
207
190
  }
208
191
 
@@ -223,11 +206,16 @@ exports.send_trans_email = function (transaction, next) {
223
206
 
224
207
  // add potentially missing headers
225
208
  if (!transaction.header.get_all('Message-Id').length) {
226
- logger.loginfo("[outbound] Adding missing Message-Id header");
209
+ logger.info(exports, "Adding missing Message-Id header");
210
+ transaction.add_header('Message-Id', `<${transaction.uuid}@${net_utils.get_primary_host_name()}>`);
211
+ }
212
+ if (transaction.header.get('Message-Id') === '<>') {
213
+ logger.info(exports, "Replacing empty Message-Id header");
214
+ transaction.remove_header('Message-Id');
227
215
  transaction.add_header('Message-Id', `<${transaction.uuid}@${net_utils.get_primary_host_name()}>`);
228
216
  }
229
217
  if (!transaction.header.get_all('Date').length) {
230
- logger.loginfo("[outbound] Adding missing Date header");
218
+ logger.info(exports, "Adding missing Date header");
231
219
  transaction.add_header('Date', utils.date_to_str(new Date()));
232
220
  }
233
221
 
@@ -239,79 +227,80 @@ exports.send_trans_email = function (transaction, next) {
239
227
 
240
228
  logger.add_log_methods(connection);
241
229
  if (!transaction.results) {
242
- logger.logdebug('adding results store');
230
+ logger.debug(exports, 'adding results store');
243
231
  transaction.results = new ResultStore(connection);
244
232
  }
245
233
 
246
- connection.pre_send_trans_email_respond = retval => {
234
+ connection.pre_send_trans_email_respond = async (retval) => {
247
235
  const deliveries = get_deliveries(transaction);
248
236
  const hmails = [];
249
237
  const ok_paths = [];
250
238
 
251
239
  let todo_index = 1;
252
240
 
253
- async.forEachSeries(deliveries, (deliv, cb) => {
254
- const todo = new TODOItem(deliv.domain, deliv.rcpts, transaction);
255
- todo.uuid = `${todo.uuid}.${todo_index}`;
256
- todo_index++;
257
- this.process_delivery(ok_paths, todo, hmails, cb);
258
- },
259
- (err) => {
260
- if (err) {
261
- for (let i=0, l=ok_paths.length; i<l; i++) {
262
- fs.unlink(ok_paths[i], () => {});
263
- }
264
- transaction.results.add({ name: 'outbound'}, { err });
265
- if (next) next(constants.denysoft, err);
266
- return;
241
+ try {
242
+ for (const deliv of deliveries) {
243
+ const todo = new TODOItem(deliv.domain, deliv.rcpts, transaction);
244
+ todo.uuid = `${todo.uuid}.${todo_index}`;
245
+ todo_index++;
246
+ await this.process_delivery(ok_paths, todo, hmails);
267
247
  }
268
-
269
- for (const hmail of hmails) {
270
- delivery_queue.push(hmail);
248
+ }
249
+ catch (err) {
250
+ for (let i=0, l=ok_paths.length; i<l; i++) {
251
+ fs.unlink(ok_paths[i], () => {});
271
252
  }
253
+ transaction.results.add({ name: 'outbound'}, { err });
254
+ if (next) next(constants.denysoft, err);
255
+ return;
256
+ }
272
257
 
273
- transaction.results.add({ name: 'outbound'}, { pass: "queued" });
274
- if (next) {
275
- next(constants.ok, `Message Queued (${transaction.uuid})`);
276
- }
277
- });
258
+ for (const hmail of hmails) {
259
+ delivery_queue.push(hmail);
260
+ }
261
+
262
+ transaction.results.add({ name: 'outbound'}, { pass: "queued" });
263
+ if (next) next(constants.ok, `Message Queued (${transaction.uuid})`);
278
264
  }
279
265
 
280
266
  plugins.run_hooks('pre_send_trans_email', connection);
281
267
  }
282
268
 
283
- exports.process_delivery = function (ok_paths, todo, hmails, cb) {
284
- logger.loginfo(`[outbound] Transaction delivery for domain: ${todo.domain}`);
285
- const fname = _qfile.name();
286
- const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`);
287
- const ws = new FsyncWriteStream(tmp_path, { flags: constants.WRITE_EXCL });
269
+ exports.process_delivery = function (ok_paths, todo, hmails) {
270
+ return new Promise((resolve, reject) => {
271
+
272
+ logger.info(exports, `Transaction delivery for domain: ${todo.domain}`);
273
+ const fname = _qfile.name();
274
+ const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`);
275
+ const ws = new FsyncWriteStream(tmp_path, { flags: constants.WRITE_EXCL });
276
+
277
+ ws.on('close', () => {
278
+ const dest_path = path.join(queue_dir, fname);
279
+ fs.rename(tmp_path, dest_path, err => {
280
+ if (err) {
281
+ logger.error(exports, `Unable to rename tmp file!: ${err}`);
282
+ fs.unlink(tmp_path, () => {});
283
+ reject("Queue error");
284
+ }
285
+ else {
286
+ hmails.push(new HMailItem (fname, dest_path, todo.notes));
287
+ ok_paths.push(dest_path);
288
+ resolve();
289
+ }
290
+ })
291
+ })
288
292
 
289
- ws.on('close', () => {
290
- const dest_path = path.join(queue_dir, fname);
291
- fs.rename(tmp_path, dest_path, err => {
292
- if (err) {
293
- logger.logerror(`[outbound] Unable to rename tmp file!: ${err}`);
294
- fs.unlink(tmp_path, () => {});
295
- cb("Queue error");
296
- }
297
- else {
298
- hmails.push(new HMailItem (fname, dest_path, todo.notes));
299
- ok_paths.push(dest_path);
300
- cb();
301
- }
293
+ ws.on('error', err => {
294
+ logger.error(exports, `Unable to write queue file (${fname}): ${err}`);
295
+ ws.destroy();
296
+ fs.unlink(tmp_path, () => {});
297
+ reject("Queueing failed");
302
298
  })
303
- })
304
299
 
305
- ws.on('error', err => {
306
- logger.logerror(`[outbound] Unable to write queue file (${fname}): ${err}`);
307
- ws.destroy();
308
- fs.unlink(tmp_path, () => {});
309
- cb("Queueing failed");
300
+ this.build_todo(todo, ws, () => {
301
+ todo.message_stream.pipe(ws, { dot_stuffing: true });
302
+ });
310
303
  })
311
-
312
- this.build_todo(todo, ws, () => {
313
- todo.message_stream.pipe(ws, { line_endings: '\r\n', dot_stuffing: true, ending_dot: false });
314
- });
315
304
  }
316
305
 
317
306
  exports.build_todo = (todo, ws, write_more) => {
@@ -346,5 +335,3 @@ function exclude_from_json (key, value) {
346
335
  exports.TODOItem = TODOItem;
347
336
 
348
337
  exports.HMailItem = HMailItem;
349
-
350
- exports.lookup_mx = require('./mx_lookup').lookup_mx;
package/outbound/qfile.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const os = require('os');
3
+ const os = require('node:os');
4
4
  const platform_dot = `${(['win32','win64'].includes(process.platform)) ? '' : '__tmp__'}.`;
5
5
 
6
6
  let QFILECOUNTER = 0;
package/outbound/queue.js CHANGED
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const async = require('async');
4
- const fs = require('fs');
5
- const path = require('path');
3
+ const child_process = require('node:child_process');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
6
 
7
- const { Address } = require('address-rfc2821');
7
+ const async = require('async');
8
+ const { Address } = require('address-rfc2821');
8
9
  const config = require('haraka-config');
9
10
 
10
11
  const logger = require('../logger');
@@ -14,6 +15,8 @@ const obc = require('./config');
14
15
  const _qfile = require('./qfile');
15
16
  const obtls = require('./tls');
16
17
 
18
+ exports.name = 'outbound/queue';
19
+
17
20
  let queue_dir;
18
21
  if (config.get('queue_dir')) {
19
22
  queue_dir = path.resolve(config.get('queue_dir'));
@@ -22,7 +25,7 @@ else if (process.env.HARAKA) {
22
25
  queue_dir = path.resolve(process.env.HARAKA, 'queue');
23
26
  }
24
27
  else {
25
- queue_dir = path.resolve('tests', 'test-queue');
28
+ queue_dir = path.resolve('test', 'test-queue');
26
29
  }
27
30
 
28
31
  exports.queue_dir = queue_dir;
@@ -72,45 +75,43 @@ exports.stat_queue = cb => {
72
75
  exports.load_queue = pid => {
73
76
  // Initialise and load queue
74
77
  // This function is called first when not running under cluster,
75
- // so we create the queue directory if it doesn't already exist.
76
78
  exports.ensure_queue_dir();
77
79
  exports.delete_dot_files();
78
80
 
79
81
  exports._load_cur_queue(pid, exports._add_file, () => {
80
- logger.loginfo(`[outbound] [pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`);
81
- logger.loginfo(`[outbound] [pid: ${pid}] ${load_queue.length()} files in my load queue`);
82
- logger.loginfo(`[outbound] [pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`);
82
+ logger.info(exports, `[pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`);
83
+ logger.info(exports, `[pid: ${pid}] ${load_queue.length()} files in my load queue`);
84
+ logger.info(exports, `[pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`);
83
85
  });
84
86
  }
85
87
 
86
88
  exports._load_cur_queue = (pid, iteratee, cb) => {
87
- const self = exports;
88
- logger.loginfo("[outbound] Loading outbound queue from ", queue_dir);
89
+ logger.info(exports, "Loading outbound queue from ", queue_dir);
89
90
  fs.readdir(queue_dir, (err, files) => {
90
91
  if (err) {
91
- return logger.logerror(`[outbound] Failed to load queue directory (${queue_dir}): ${err}`);
92
+ return logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`);
92
93
  }
93
94
 
94
- self.cur_time = new Date(); // set once so we're not calling it a lot
95
+ this.cur_time = new Date(); // set once so we're not calling it a lot
95
96
 
96
- self.load_queue_files(pid, files, iteratee, cb);
97
+ this.load_queue_files(pid, files, iteratee, cb);
97
98
  });
98
99
  }
99
100
 
100
101
  exports.read_parts = file => {
101
102
  if (file.indexOf(_qfile.platformDOT) === 0) {
102
- logger.logwarn(`[outbound] 'Skipping' dot-file in queue folder: ${file}`);
103
+ logger.warn(exports, `'Skipping' dot-file in queue folder: ${file}`);
103
104
  return false;
104
105
  }
105
106
 
106
107
  if (file.startsWith('error.')) {
107
- logger.logwarn(`[outbound] 'Skipping' error file in queue folder: ${file}`);
108
+ logger.warn(exports, `'Skipping' error file in queue folder: ${file}`);
108
109
  return false;
109
110
  }
110
111
 
111
112
  const parts = _qfile.parts(file);
112
113
  if (!parts) {
113
- logger.logerror(`[outbound] Unrecognized file in queue folder: ${file}`);
114
+ logger.error(exports, `Unrecognized file in queue folder: ${file}`);
114
115
  return false;
115
116
  }
116
117
 
@@ -140,11 +141,11 @@ exports._add_file = (file, cb) => {
140
141
  const parts = _qfile.parts(file);
141
142
 
142
143
  if (parts.next_attempt <= self.cur_time) {
143
- logger.logdebug(`[outbound] File ${file} needs processing now`);
144
+ logger.debug(exports, `File ${file} needs processing now`);
144
145
  load_queue.push(file);
145
146
  }
146
147
  else {
147
- logger.logdebug(`[outbound] File ${file} needs processing later: ${parts.next_attempt - self.cur_time}ms`);
148
+ logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - self.cur_time}ms`);
148
149
  temp_fail_queue.add(file, parts.next_attempt - self.cur_time, () => { load_queue.push(file);});
149
150
  }
150
151
 
@@ -159,10 +160,10 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
159
160
  let stat_loaded = 0;
160
161
 
161
162
  if (searchPid) {
162
- logger.loginfo(`[outbound] Grabbing queue files for pid: ${pid}`);
163
+ logger.info(exports, `Grabbing queue files for pid: ${pid}`);
163
164
  }
164
165
  else {
165
- logger.loginfo("[outbound] Loading the queue...");
166
+ logger.info(exports, "Loading the queue...");
166
167
  }
167
168
 
168
169
  async.map(input_files, (file, cb) => {
@@ -174,7 +175,7 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
174
175
 
175
176
  self.rename_to_actual_pid(file, parts, (error, renamed_file) => {
176
177
  if (error) {
177
- logger.logerror(`[outbound] ${error}`);
178
+ logger.error(exports, `${error}`);
178
179
  return cb();
179
180
  }
180
181
 
@@ -189,16 +190,15 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
189
190
  }
190
191
 
191
192
  }, (err, results) => {
192
- if (err) logger.logerr(`[outbound] [pid: ${pid}] ${err}`);
193
- if (searchPid) logger.loginfo(`[outbound] [pid: ${pid}] ${stat_renamed} files old PID queue fixed up`);
194
- logger.logdebug(`[outbound] [pid: ${pid}] ${stat_loaded} files loaded`);
193
+ if (err) logger.err(exports, `[pid: ${pid}] ${err}`);
194
+ if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`);
195
+ logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`);
195
196
 
196
197
  async.map(results.filter((i) => i), iteratee, callback);
197
198
  });
198
199
  }
199
200
 
200
201
  exports.stats = () => {
201
-
202
202
  return {
203
203
  queue_dir,
204
204
  queue_count,
@@ -223,7 +223,7 @@ exports._list_file = (file, cb) => {
223
223
  // we read everything
224
224
  const todo_struct = JSON.parse(todo);
225
225
  todo_struct.rcpt_to = todo_struct.rcpt_to.map(a => new Address (a));
226
- todo_struct.mail_from = new Address (todo_struct.mail_from);
226
+ todo_struct.mail_from = new Address(todo_struct.mail_from);
227
227
  todo_struct.file = file;
228
228
  todo_struct.full_path = path.join(queue_dir, file);
229
229
  const parts = _qfile.parts(file);
@@ -243,13 +243,13 @@ exports._list_file = (file, cb) => {
243
243
  exports.flush_queue = (domain, pid) => {
244
244
  if (domain) {
245
245
  exports.list_queue((err, qlist) => {
246
- if (err) return logger.logerror(`[outbound] Failed to load queue: ${err}`);
247
- qlist.forEach(todo => {
246
+ if (err) return logger.error(exports, `Failed to load queue: ${err}`);
247
+ for (const todo of qlist) {
248
248
  if (todo.domain.toLowerCase() != domain.toLowerCase()) return;
249
249
  if (pid && todo.pid != pid) return;
250
250
  // console.log("requeue: ", todo);
251
251
  delivery_queue.push(new HMailItem(todo.file, todo.full_path));
252
- });
252
+ }
253
253
  })
254
254
  }
255
255
  else {
@@ -258,36 +258,44 @@ exports.flush_queue = (domain, pid) => {
258
258
  }
259
259
 
260
260
  exports.load_pid_queue = pid => {
261
- logger.loginfo(`[outbound] Loading queue for pid: ${pid}`);
261
+ logger.info(exports, `Loading queue for pid: ${pid}`);
262
262
  exports.load_queue(pid);
263
263
  }
264
264
 
265
265
  exports.ensure_queue_dir = () => {
266
- // No reason to do this asynchronously
267
266
  // this code is only run at start-up.
268
267
  if (fs.existsSync(queue_dir)) return;
269
268
 
270
- logger.logdebug(`[outbound] Creating queue directory ${queue_dir}`);
269
+ logger.debug(exports, `Creating queue directory ${queue_dir}`);
271
270
  try {
272
271
  fs.mkdirSync(queue_dir, 493); // 493 == 0755
272
+ const cfg = config.get('smtp.ini');
273
+ let uid
274
+ let gid
275
+ if (cfg.user) uid = child_process.execSync(`id -u ${cfg.user}`).toString().trim();
276
+ if (cfg.group) gid = child_process.execSync(`id -g ${cfg.group}`).toString().trim();
277
+ if (uid && gid) {
278
+ fs.chown(queue_dir, uid, gid)
279
+ }
280
+ else if (uid) {
281
+ fs.chown(queue_dir, uid)
282
+ }
273
283
  }
274
284
  catch (err) {
275
285
  if (err.code !== 'EEXIST') {
276
- logger.logerror(`[outbound] Error creating queue directory: ${err}`);
286
+ logger.error(exports, `Error creating queue directory: ${err}`);
277
287
  throw err;
278
288
  }
279
289
  }
280
290
  }
281
291
 
282
292
  exports.delete_dot_files = () => {
283
- const files = fs.readdirSync(queue_dir);
284
-
285
- files.forEach(file => {
293
+ for (const file of fs.readdirSync(queue_dir)) {
286
294
  if (file.indexOf(_qfile.platformDOT) === 0) {
287
- logger.logwarn(`[outbound] Removing left over dot-file: ${file}`);
295
+ logger.warn(exports, `Removing left over dot-file: ${file}`);
288
296
  return fs.unlinkSync(path.join(queue_dir, file));
289
297
  }
290
- });
298
+ }
291
299
  }
292
300
 
293
301
  exports._add_hmail = hmail => {
@@ -304,23 +312,22 @@ exports._add_hmail = hmail => {
304
312
  exports.scan_queue_pids = cb => {
305
313
  const self = exports;
306
314
 
307
- // Under cluster, this is called first by the master so
308
- // we create the queue directory if it doesn't exist.
315
+ // Under cluster, this is called first by the master
309
316
  self.ensure_queue_dir();
310
317
  self.delete_dot_files();
311
318
 
312
319
  fs.readdir(queue_dir, (err, files) => {
313
320
  if (err) {
314
- logger.logerror(`[outbound] Failed to load queue directory (${queue_dir}): ${err}`);
321
+ logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`);
315
322
  return cb(err);
316
323
  }
317
324
 
318
325
  const pids = {};
319
326
 
320
- files.forEach(file => {
327
+ for (const file of files) {
321
328
  const parts = self.read_parts(file);
322
329
  if (parts) pids[parts.pid] = true;
323
- });
330
+ }
324
331
 
325
332
  return cb(null, Object.keys(pids));
326
333
  });
@@ -19,8 +19,10 @@ class TQTimer {
19
19
  class TimerQueue {
20
20
 
21
21
  constructor (interval = 1000) {
22
+ this.name = 'outbound/timer_queue'
22
23
  this.queue = [];
23
24
  this.interval_timer = setInterval(() => { this.fire(); }, interval);
25
+ this.interval_timer.unref() // allow server to exit
24
26
  }
25
27
 
26
28
  add (id, ms, cb) {
@@ -71,7 +73,7 @@ class TimerQueue {
71
73
  }
72
74
 
73
75
  drain () {
74
- logger.logdebug(`Draining ${this.queue.length} items from the queue`);
76
+ logger.debug(this, `Draining ${this.queue.length} items from the queue`);
75
77
  while (this.queue.length) {
76
78
  const to_run = this.queue.shift();
77
79
  if (to_run.cb) to_run.cb();
@@ -84,4 +86,3 @@ class TimerQueue {
84
86
  }
85
87
 
86
88
  module.exports = TimerQueue;
87
-
package/outbound/tls.js CHANGED
@@ -1,10 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const logger = require('../logger');
4
- const tls_socket = require('../tls_socket');
3
+ const net = require('node:net')
4
+
5
5
  const config = require('haraka-config');
6
6
  const hkredis = require('haraka-plugin-redis');
7
7
 
8
+ const logger = require('../logger');
9
+ const tls_socket = require('../tls_socket');
10
+
8
11
  const inheritable_opts = [
9
12
  'key', 'cert', 'ciphers', 'minVersion', 'dhparam',
10
13
  'requestCert', 'honorCipherOrder', 'rejectUnauthorized',
@@ -62,14 +65,23 @@ class OutboundTLS {
62
65
  this.load_config();
63
66
  // changing this var in-flight won't work
64
67
  if (this.cfg.redis && !this.cfg.redis.disable_for_failed_hosts) return cb();
65
- logger.logdebug(this, 'Will disable outbound TLS for failing TLS hosts');
68
+ logger.debug(this, 'Will disable outbound TLS for failing TLS hosts');
66
69
  Object.assign(this, hkredis);
67
70
  this.merge_redis_ini();
68
71
  this.init_redis_plugin(cb);
69
72
  }
70
73
 
71
74
  get_tls_options (mx) {
72
- return Object.assign(this.cfg, {servername: mx.exchange});
75
+ // do NOT set servername to an IP address
76
+ if (net.isIP(mx.exchange)) {
77
+ // when mx.exchange looked up in DNS, from_dns has the hostname
78
+ if (mx.from_dns) return { ...this.cfg, servername: mx.from_dns }
79
+ return { ...this.cfg }
80
+ }
81
+ else {
82
+ // mx.exchange is a hostname
83
+ return { ...this.cfg, servername: mx.exchange }
84
+ }
73
85
  }
74
86
 
75
87
  // Check for if host is prohibited from TLS negotiation
@@ -82,7 +94,7 @@ class OutboundTLS {
82
94
  dbr ? cb_nogo(dbr) : cb_ok();
83
95
  })
84
96
  .catch(err => {
85
- this.logdebug(this, `Redis returned error: ${err}`);
97
+ logger.debug(this, `Redis returned error: ${err}`);
86
98
  cb_ok();
87
99
  })
88
100
  }
@@ -93,12 +105,12 @@ class OutboundTLS {
93
105
 
94
106
  if (!this.cfg.redis.disable_for_failed_hosts) return cb();
95
107
 
96
- logger.lognotice(this, `TLS connection failed. Marking ${host} as non-TLS for ${expiry} seconds`);
108
+ logger.notice(this, `TLS connection failed. Marking ${host} as non-TLS for ${expiry} seconds`);
97
109
 
98
110
  this.db.setEx(dbkey, expiry, (new Date()).toISOString())
99
111
  .then(cb)
100
112
  .catch(err => {
101
- logger.logerror(this, `Redis returned error: ${err}`);
113
+ logger.error(this, `Redis returned error: ${err}`);
102
114
  })
103
115
  }
104
116
  }