Haraka 3.0.2 → 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 (267) hide show
  1. package/.eslintrc.yaml +5 -9
  2. package/.prettierrc.yml +1 -0
  3. package/CONTRIBUTORS.md +11 -0
  4. package/Changes.md +1393 -1211
  5. package/Dockerfile +3 -3
  6. package/Plugins.md +119 -106
  7. package/README.md +7 -16
  8. package/TODO +1 -24
  9. package/bin/haraka +197 -298
  10. package/config/auth_flat_file.ini +2 -0
  11. package/config/auth_vpopmaild.ini +4 -2
  12. package/config/dhparams.pem +8 -0
  13. package/config/mail_from.is_resolvable.ini +4 -2
  14. package/config/me +1 -0
  15. package/config/outbound.ini +0 -2
  16. package/config/plugins +36 -35
  17. package/config/rabbitmq_amqplib.ini +8 -1
  18. package/config/smtp.ini +0 -1
  19. package/config/smtp.json +17 -0
  20. package/config/tls_cert.pem +23 -0
  21. package/config/tls_key.pem +28 -0
  22. package/connection.js +46 -73
  23. package/contrib/bsd-rc.d/haraka +3 -1
  24. package/contrib/plugin2npm.sh +6 -36
  25. package/docs/Connection.md +1 -1
  26. package/docs/CoreConfig.md +2 -2
  27. package/docs/Logging.md +7 -21
  28. package/docs/Outbound.md +104 -210
  29. package/docs/Plugins.md +47 -40
  30. package/docs/Transaction.md +59 -82
  31. package/docs/{plugins → deprecated}/connect.rdns_access.md +1 -1
  32. package/docs/{plugins → deprecated}/mail_from.access.md +1 -1
  33. package/docs/{plugins → deprecated}/rcpt_to.access.md +1 -1
  34. package/docs/plugins/auth/auth_vpopmaild.md +15 -19
  35. package/docs/plugins/auth/flat_file.md +23 -30
  36. package/docs/plugins/queue/rabbitmq_amqplib.md +7 -0
  37. package/docs/plugins/queue/smtp_forward.md +1 -1
  38. package/docs/plugins/queue/smtp_proxy.md +5 -10
  39. package/docs/plugins/relay.md +2 -2
  40. package/docs/plugins/tls.md +29 -9
  41. package/endpoint.js +16 -13
  42. package/haraka.js +10 -14
  43. package/host_pool.js +5 -5
  44. package/line_socket.js +3 -4
  45. package/logger.js +44 -28
  46. package/outbound/client_pool.js +27 -23
  47. package/outbound/config.js +4 -6
  48. package/outbound/fsync_writestream.js +1 -1
  49. package/outbound/hmail.js +180 -220
  50. package/outbound/index.js +86 -99
  51. package/outbound/qfile.js +1 -1
  52. package/outbound/queue.js +55 -43
  53. package/outbound/timer_queue.js +3 -2
  54. package/outbound/tls.js +19 -7
  55. package/package.json +66 -55
  56. package/plugins/.eslintrc.yaml +0 -6
  57. package/plugins/auth/auth_base.js +30 -12
  58. package/plugins/auth/auth_proxy.js +14 -12
  59. package/plugins/auth/auth_vpopmaild.js +30 -20
  60. package/plugins/auth/flat_file.js +17 -12
  61. package/plugins/block_me.js +1 -1
  62. package/plugins/data.signatures.js +2 -4
  63. package/plugins/early_talker.js +2 -1
  64. package/plugins/mail_from.is_resolvable.js +65 -135
  65. package/plugins/queue/deliver.js +4 -5
  66. package/plugins/queue/lmtp.js +11 -14
  67. package/plugins/queue/qmail-queue.js +2 -2
  68. package/plugins/queue/quarantine.js +2 -2
  69. package/plugins/queue/rabbitmq.js +16 -17
  70. package/plugins/queue/rabbitmq_amqplib.js +1 -1
  71. package/plugins/queue/smtp_forward.js +6 -6
  72. package/plugins/queue/smtp_proxy.js +10 -1
  73. package/plugins/queue/test.js +2 -2
  74. package/plugins/rcpt_to.host_list_base.js +5 -5
  75. package/plugins/rcpt_to.in_host_list.js +2 -2
  76. package/plugins/relay.js +6 -7
  77. package/plugins/reseed_rng.js +1 -1
  78. package/plugins/status.js +37 -33
  79. package/plugins/tls.js +2 -2
  80. package/plugins/xclient.js +3 -2
  81. package/plugins.js +51 -54
  82. package/run_tests +3 -30
  83. package/server.js +190 -190
  84. package/smtp_client.js +30 -23
  85. package/{tests → test}/config/plugins +0 -2
  86. package/{tests → test}/config/smtp.ini +1 -1
  87. package/test/config/tls/example.com/_.example.com.key +28 -0
  88. package/test/config/tls/example.com/example.com.crt +25 -0
  89. package/test/connection.js +302 -0
  90. package/test/endpoint.js +94 -0
  91. package/{tests → test}/fixtures/line_socket.js +1 -1
  92. package/{tests → test}/fixtures/util_hmailitem.js +19 -25
  93. package/{tests → test}/host_pool.js +42 -57
  94. package/test/logger.js +258 -0
  95. package/test/outbound/hmail.js +141 -0
  96. package/test/outbound/index.js +220 -0
  97. package/test/outbound/qfile.js +126 -0
  98. package/test/outbound_bounce_net_errors.js +142 -0
  99. package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
  100. package/test/plugins/auth/auth_base.js +484 -0
  101. package/test/plugins/auth/auth_vpopmaild.js +83 -0
  102. package/test/plugins/early_talker.js +104 -0
  103. package/test/plugins/mail_from.is_resolvable.js +35 -0
  104. package/test/plugins/queue/smtp_forward.js +206 -0
  105. package/test/plugins/rcpt_to.host_list_base.js +122 -0
  106. package/test/plugins/rcpt_to.in_host_list.js +193 -0
  107. package/test/plugins/relay.js +303 -0
  108. package/test/plugins/status.js +130 -0
  109. package/test/plugins/tls.js +70 -0
  110. package/test/plugins.js +228 -0
  111. package/{tests → test}/queue/multibyte +0 -0
  112. package/{tests → test}/queue/plain +0 -0
  113. package/test/rfc1869.js +73 -0
  114. package/test/server.js +491 -0
  115. package/test/smtp_client.js +299 -0
  116. package/test/tls_socket.js +273 -0
  117. package/test/transaction.js +270 -0
  118. package/tls_socket.js +202 -252
  119. package/transaction.js +9 -24
  120. package/CONTRIBUTING.md +0 -1
  121. package/bin/dkimverify +0 -40
  122. package/config/access.domains +0 -13
  123. package/config/attachment.ctype.regex +0 -2
  124. package/config/attachment.filename.regex +0 -1
  125. package/config/avg.ini +0 -5
  126. package/config/bounce.ini +0 -15
  127. package/config/data.headers.ini +0 -61
  128. package/config/dkim/dkim_key_gen.sh +0 -78
  129. package/config/dkim_sign.ini +0 -4
  130. package/config/dkim_verify.ini +0 -7
  131. package/config/dnsbl.ini +0 -23
  132. package/config/greylist.ini +0 -43
  133. package/config/helo.checks.ini +0 -52
  134. package/config/lookup_rdns.strict.ini +0 -12
  135. package/config/lookup_rdns.strict.timeout +0 -1
  136. package/config/lookup_rdns.strict.whitelist +0 -1
  137. package/config/lookup_rdns.strict.whitelist_regex +0 -5
  138. package/config/messagesniffer.ini +0 -18
  139. package/config/rcpt_to.blocklist +0 -1
  140. package/config/rdns.allow_regexps +0 -0
  141. package/config/rdns.deny_regexps +0 -0
  142. package/config/spamassassin.ini +0 -56
  143. package/config.js +0 -6
  144. package/dkim.js +0 -614
  145. package/docs/plugins/avg.md +0 -35
  146. package/docs/plugins/bounce.md +0 -69
  147. package/docs/plugins/clamd.md +0 -147
  148. package/docs/plugins/esets.md +0 -8
  149. package/docs/plugins/greylist.md +0 -90
  150. package/docs/plugins/helo.checks.md +0 -135
  151. package/docs/plugins/messagesniffer.md +0 -163
  152. package/docs/plugins/relay_acl.md +0 -29
  153. package/docs/plugins/relay_all.md +0 -15
  154. package/docs/plugins/relay_force_routing.md +0 -33
  155. package/docs/plugins/spamassassin.md +0 -180
  156. package/outbound/mx_lookup.js +0 -70
  157. package/plugins/auth/auth_ldap.js +0 -3
  158. package/plugins/avg.js +0 -162
  159. package/plugins/backscatterer.js +0 -25
  160. package/plugins/bounce.js +0 -381
  161. package/plugins/clamd.js +0 -381
  162. package/plugins/data.headers.js +0 -4
  163. package/plugins/data.uribl.js +0 -4
  164. package/plugins/dkim_sign.js +0 -395
  165. package/plugins/dkim_verify.js +0 -62
  166. package/plugins/dns_list_base.js +0 -221
  167. package/plugins/dnsbl.js +0 -146
  168. package/plugins/dnswl.js +0 -58
  169. package/plugins/esets.js +0 -71
  170. package/plugins/graph.js +0 -5
  171. package/plugins/greylist.js +0 -645
  172. package/plugins/helo.checks.js +0 -533
  173. package/plugins/messagesniffer.js +0 -381
  174. package/plugins/rcpt_to.ldap.js +0 -3
  175. package/plugins/rcpt_to.max_count.js +0 -24
  176. package/plugins/relay_all.js +0 -13
  177. package/plugins/spamassassin.js +0 -384
  178. package/tests/config/dkim/example.com/dns +0 -29
  179. package/tests/config/dkim/example.com/private +0 -6
  180. package/tests/config/dkim/example.com/public +0 -4
  181. package/tests/config/dkim/example.com/selector +0 -1
  182. package/tests/config/dkim.private.key +0 -6
  183. package/tests/config/dkim_sign.ini +0 -4
  184. package/tests/config/helo.checks.ini +0 -52
  185. package/tests/connection.js +0 -327
  186. package/tests/endpoint.js +0 -128
  187. package/tests/fixtures/vm_harness.js +0 -59
  188. package/tests/logger.js +0 -327
  189. package/tests/outbound/hmail.js +0 -112
  190. package/tests/outbound/index.js +0 -324
  191. package/tests/outbound/qfile.js +0 -67
  192. package/tests/outbound_bounce_net_errors.js +0 -173
  193. package/tests/plugins/auth/auth_base.js +0 -463
  194. package/tests/plugins/auth/auth_vpopmaild.js +0 -91
  195. package/tests/plugins/bounce.js +0 -307
  196. package/tests/plugins/clamd.js +0 -224
  197. package/tests/plugins/deprecated/relay_acl.js +0 -140
  198. package/tests/plugins/deprecated/relay_all.js +0 -59
  199. package/tests/plugins/dkim_sign.js +0 -315
  200. package/tests/plugins/dkim_signer.js +0 -108
  201. package/tests/plugins/dns_list_base.js +0 -259
  202. package/tests/plugins/dnsbl.js +0 -101
  203. package/tests/plugins/early_talker.js +0 -115
  204. package/tests/plugins/greylist.js +0 -58
  205. package/tests/plugins/helo.checks.js +0 -525
  206. package/tests/plugins/mail_from.is_resolvable.js +0 -116
  207. package/tests/plugins/queue/smtp_forward.js +0 -221
  208. package/tests/plugins/rcpt_to.host_list_base.js +0 -132
  209. package/tests/plugins/rcpt_to.in_host_list.js +0 -218
  210. package/tests/plugins/relay.js +0 -339
  211. package/tests/plugins/spamassassin.js +0 -171
  212. package/tests/plugins/status.js +0 -138
  213. package/tests/plugins/tls.js +0 -84
  214. package/tests/plugins.js +0 -247
  215. package/tests/rfc1869.js +0 -61
  216. package/tests/server.js +0 -510
  217. package/tests/smtp_client/auth.js +0 -105
  218. package/tests/smtp_client/basic.js +0 -101
  219. package/tests/smtp_client.js +0 -80
  220. package/tests/tls_socket.js +0 -333
  221. package/tests/transaction.js +0 -284
  222. /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
  223. /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
  224. /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
  225. /package/docs/{plugins → deprecated}/dnswl.md +0 -0
  226. /package/docs/{plugins → deprecated}/rcpt_to.routes.md +0 -0
  227. /package/{tests → test}/.eslintrc.yaml +0 -0
  228. /package/{tests → test}/config/auth_flat_file.ini +0 -0
  229. /package/{tests → test}/config/dhparams.pem +0 -0
  230. /package/{tests → test}/config/host_list +0 -0
  231. /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
  232. /package/{tests → test}/config/outbound_tls_key.pem +0 -0
  233. /package/{tests → test}/config/smtp_forward.ini +0 -0
  234. /package/{tests → test}/config/tls/ec.pem +0 -0
  235. /package/{tests → test}/config/tls/haraka.local.pem +0 -0
  236. /package/{tests → test}/config/tls/mismatched.pem +0 -0
  237. /package/{tests → test}/config/tls.ini +0 -0
  238. /package/{tests → test}/config/tls_cert.pem +0 -0
  239. /package/{tests → test}/config/tls_key.pem +0 -0
  240. /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
  241. /package/{tests → test}/installation/config/test-plugin-flat +0 -0
  242. /package/{tests → test}/installation/config/test-plugin.ini +0 -0
  243. /package/{tests → test}/installation/config/tls.ini +0 -0
  244. /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
  245. /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
  246. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
  247. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
  248. /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
  249. /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
  250. /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
  251. /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
  252. /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
  253. /package/{tests → test}/installation/plugins/inherits.js +0 -0
  254. /package/{tests → test}/installation/plugins/load_first.js +0 -0
  255. /package/{tests → test}/installation/plugins/plugin.js +0 -0
  256. /package/{tests → test}/installation/plugins/tls.js +0 -0
  257. /package/{tests → test}/loud/config/dhparams.pem +0 -0
  258. /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
  259. /package/{tests → test}/loud/config/tls.ini +0 -0
  260. /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
  261. /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
  262. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  263. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  264. /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  265. /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  266. /package/{tests → test}/queue/zero-length +0 -0
  267. /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,40 +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}`);
104
+ return false;
105
+ }
106
+
107
+ if (file.startsWith('error.')) {
108
+ logger.warn(exports, `'Skipping' error file in queue folder: ${file}`);
103
109
  return false;
104
110
  }
105
111
 
106
112
  const parts = _qfile.parts(file);
107
113
  if (!parts) {
108
- logger.logerror(`[outbound] Unrecognized file in queue folder: ${file}`);
114
+ logger.error(exports, `Unrecognized file in queue folder: ${file}`);
109
115
  return false;
110
116
  }
111
117
 
@@ -135,11 +141,11 @@ exports._add_file = (file, cb) => {
135
141
  const parts = _qfile.parts(file);
136
142
 
137
143
  if (parts.next_attempt <= self.cur_time) {
138
- logger.logdebug(`[outbound] File ${file} needs processing now`);
144
+ logger.debug(exports, `File ${file} needs processing now`);
139
145
  load_queue.push(file);
140
146
  }
141
147
  else {
142
- 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`);
143
149
  temp_fail_queue.add(file, parts.next_attempt - self.cur_time, () => { load_queue.push(file);});
144
150
  }
145
151
 
@@ -154,10 +160,10 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
154
160
  let stat_loaded = 0;
155
161
 
156
162
  if (searchPid) {
157
- logger.loginfo(`[outbound] Grabbing queue files for pid: ${pid}`);
163
+ logger.info(exports, `Grabbing queue files for pid: ${pid}`);
158
164
  }
159
165
  else {
160
- logger.loginfo("[outbound] Loading the queue...");
166
+ logger.info(exports, "Loading the queue...");
161
167
  }
162
168
 
163
169
  async.map(input_files, (file, cb) => {
@@ -169,7 +175,7 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
169
175
 
170
176
  self.rename_to_actual_pid(file, parts, (error, renamed_file) => {
171
177
  if (error) {
172
- logger.logerror(`[outbound] ${error}`);
178
+ logger.error(exports, `${error}`);
173
179
  return cb();
174
180
  }
175
181
 
@@ -184,16 +190,15 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
184
190
  }
185
191
 
186
192
  }, (err, results) => {
187
- if (err) logger.logerr(`[outbound] [pid: ${pid}] ${err}`);
188
- if (searchPid) logger.loginfo(`[outbound] [pid: ${pid}] ${stat_renamed} files old PID queue fixed up`);
189
- 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`);
190
196
 
191
197
  async.map(results.filter((i) => i), iteratee, callback);
192
198
  });
193
199
  }
194
200
 
195
201
  exports.stats = () => {
196
-
197
202
  return {
198
203
  queue_dir,
199
204
  queue_count,
@@ -218,7 +223,7 @@ exports._list_file = (file, cb) => {
218
223
  // we read everything
219
224
  const todo_struct = JSON.parse(todo);
220
225
  todo_struct.rcpt_to = todo_struct.rcpt_to.map(a => new Address (a));
221
- todo_struct.mail_from = new Address (todo_struct.mail_from);
226
+ todo_struct.mail_from = new Address(todo_struct.mail_from);
222
227
  todo_struct.file = file;
223
228
  todo_struct.full_path = path.join(queue_dir, file);
224
229
  const parts = _qfile.parts(file);
@@ -238,13 +243,13 @@ exports._list_file = (file, cb) => {
238
243
  exports.flush_queue = (domain, pid) => {
239
244
  if (domain) {
240
245
  exports.list_queue((err, qlist) => {
241
- if (err) return logger.logerror(`[outbound] Failed to load queue: ${err}`);
242
- qlist.forEach(todo => {
246
+ if (err) return logger.error(exports, `Failed to load queue: ${err}`);
247
+ for (const todo of qlist) {
243
248
  if (todo.domain.toLowerCase() != domain.toLowerCase()) return;
244
249
  if (pid && todo.pid != pid) return;
245
250
  // console.log("requeue: ", todo);
246
251
  delivery_queue.push(new HMailItem(todo.file, todo.full_path));
247
- });
252
+ }
248
253
  })
249
254
  }
250
255
  else {
@@ -253,36 +258,44 @@ exports.flush_queue = (domain, pid) => {
253
258
  }
254
259
 
255
260
  exports.load_pid_queue = pid => {
256
- logger.loginfo(`[outbound] Loading queue for pid: ${pid}`);
261
+ logger.info(exports, `Loading queue for pid: ${pid}`);
257
262
  exports.load_queue(pid);
258
263
  }
259
264
 
260
265
  exports.ensure_queue_dir = () => {
261
- // No reason to do this asynchronously
262
266
  // this code is only run at start-up.
263
267
  if (fs.existsSync(queue_dir)) return;
264
268
 
265
- logger.logdebug(`[outbound] Creating queue directory ${queue_dir}`);
269
+ logger.debug(exports, `Creating queue directory ${queue_dir}`);
266
270
  try {
267
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
+ }
268
283
  }
269
284
  catch (err) {
270
285
  if (err.code !== 'EEXIST') {
271
- logger.logerror(`[outbound] Error creating queue directory: ${err}`);
286
+ logger.error(exports, `Error creating queue directory: ${err}`);
272
287
  throw err;
273
288
  }
274
289
  }
275
290
  }
276
291
 
277
292
  exports.delete_dot_files = () => {
278
- const files = fs.readdirSync(queue_dir);
279
-
280
- files.forEach(file => {
293
+ for (const file of fs.readdirSync(queue_dir)) {
281
294
  if (file.indexOf(_qfile.platformDOT) === 0) {
282
- logger.logwarn(`[outbound] Removing left over dot-file: ${file}`);
295
+ logger.warn(exports, `Removing left over dot-file: ${file}`);
283
296
  return fs.unlinkSync(path.join(queue_dir, file));
284
297
  }
285
- });
298
+ }
286
299
  }
287
300
 
288
301
  exports._add_hmail = hmail => {
@@ -299,23 +312,22 @@ exports._add_hmail = hmail => {
299
312
  exports.scan_queue_pids = cb => {
300
313
  const self = exports;
301
314
 
302
- // Under cluster, this is called first by the master so
303
- // we create the queue directory if it doesn't exist.
315
+ // Under cluster, this is called first by the master
304
316
  self.ensure_queue_dir();
305
317
  self.delete_dot_files();
306
318
 
307
319
  fs.readdir(queue_dir, (err, files) => {
308
320
  if (err) {
309
- logger.logerror(`[outbound] Failed to load queue directory (${queue_dir}): ${err}`);
321
+ logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`);
310
322
  return cb(err);
311
323
  }
312
324
 
313
325
  const pids = {};
314
326
 
315
- files.forEach(file => {
327
+ for (const file of files) {
316
328
  const parts = self.read_parts(file);
317
329
  if (parts) pids[parts.pid] = true;
318
- });
330
+ }
319
331
 
320
332
  return cb(null, Object.keys(pids));
321
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
  }