Haraka 3.0.3 → 3.0.5

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 (239) hide show
  1. package/.eslintrc.yaml +4 -9
  2. package/CONTRIBUTORS.md +11 -0
  3. package/Changes.md +1397 -1213
  4. package/Plugins.md +117 -105
  5. package/README.md +4 -13
  6. package/bin/haraka +198 -298
  7. package/config/auth_flat_file.ini +1 -0
  8. package/config/dhparams.pem +8 -0
  9. package/config/mail_from.is_resolvable.ini +4 -2
  10. package/config/me +1 -0
  11. package/config/outbound.ini +0 -2
  12. package/config/plugins +35 -36
  13. package/config/smtp.ini +1 -1
  14. package/config/smtp.json +17 -0
  15. package/config/tls.ini +2 -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 +37 -9
  28. package/endpoint.js +16 -13
  29. package/haraka.js +10 -14
  30. package/host_pool.js +5 -5
  31. package/http/html/index.html +6 -5
  32. package/line_socket.js +3 -4
  33. package/logger.js +44 -28
  34. package/outbound/client_pool.js +27 -23
  35. package/outbound/config.js +4 -6
  36. package/outbound/fsync_writestream.js +1 -1
  37. package/outbound/hmail.js +178 -218
  38. package/outbound/index.js +86 -99
  39. package/outbound/qfile.js +1 -1
  40. package/outbound/queue.js +51 -44
  41. package/outbound/timer_queue.js +3 -2
  42. package/outbound/tls.js +19 -7
  43. package/package.json +60 -51
  44. package/plugins/.eslintrc.yaml +0 -6
  45. package/plugins/auth/auth_base.js +4 -2
  46. package/plugins/auth/auth_proxy.js +14 -12
  47. package/plugins/auth/auth_vpopmaild.js +1 -1
  48. package/plugins/block_me.js +1 -1
  49. package/plugins/data.signatures.js +2 -4
  50. package/plugins/early_talker.js +2 -1
  51. package/plugins/mail_from.is_resolvable.js +65 -135
  52. package/plugins/queue/deliver.js +4 -5
  53. package/plugins/queue/lmtp.js +11 -12
  54. package/plugins/queue/qmail-queue.js +2 -2
  55. package/plugins/queue/quarantine.js +2 -2
  56. package/plugins/queue/rabbitmq.js +16 -17
  57. package/plugins/queue/smtp_forward.js +3 -3
  58. package/plugins/queue/smtp_proxy.js +10 -1
  59. package/plugins/queue/test.js +2 -2
  60. package/plugins/rcpt_to.host_list_base.js +5 -5
  61. package/plugins/rcpt_to.in_host_list.js +2 -2
  62. package/plugins/relay.js +6 -7
  63. package/plugins/reseed_rng.js +1 -1
  64. package/plugins/status.js +37 -33
  65. package/plugins/tls.js +2 -2
  66. package/plugins/xclient.js +3 -2
  67. package/plugins.js +50 -54
  68. package/run_tests +3 -30
  69. package/server.js +190 -190
  70. package/smtp_client.js +30 -23
  71. package/{tests → test}/config/plugins +0 -2
  72. package/{tests → test}/config/smtp.ini +3 -1
  73. package/test/config/tls/example.com/_.example.com.key +28 -0
  74. package/test/config/tls/example.com/example.com.crt +25 -0
  75. package/{tests/loud → test}/config/tls.ini +4 -2
  76. package/test/connection.js +302 -0
  77. package/test/endpoint.js +94 -0
  78. package/{tests → test}/fixtures/line_socket.js +1 -1
  79. package/{tests → test}/fixtures/util_hmailitem.js +19 -25
  80. package/{tests → test}/host_pool.js +42 -57
  81. package/test/logger.js +258 -0
  82. package/test/outbound/hmail.js +141 -0
  83. package/test/outbound/index.js +220 -0
  84. package/test/outbound/qfile.js +126 -0
  85. package/test/outbound_bounce_net_errors.js +142 -0
  86. package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
  87. package/test/plugins/auth/auth_base.js +484 -0
  88. package/test/plugins/auth/auth_vpopmaild.js +83 -0
  89. package/test/plugins/early_talker.js +104 -0
  90. package/test/plugins/mail_from.is_resolvable.js +35 -0
  91. package/test/plugins/queue/smtp_forward.js +206 -0
  92. package/test/plugins/rcpt_to.host_list_base.js +122 -0
  93. package/test/plugins/rcpt_to.in_host_list.js +193 -0
  94. package/test/plugins/relay.js +303 -0
  95. package/test/plugins/status.js +130 -0
  96. package/test/plugins/tls.js +70 -0
  97. package/test/plugins.js +228 -0
  98. package/test/rfc1869.js +73 -0
  99. package/test/server.js +491 -0
  100. package/test/smtp_client.js +299 -0
  101. package/test/tls_socket.js +277 -0
  102. package/test/transaction.js +270 -0
  103. package/tls_socket.js +202 -252
  104. package/transaction.js +8 -23
  105. package/CONTRIBUTING.md +0 -1
  106. package/bin/dkimverify +0 -40
  107. package/config/access.domains +0 -13
  108. package/config/attachment.ctype.regex +0 -2
  109. package/config/attachment.filename.regex +0 -1
  110. package/config/avg.ini +0 -5
  111. package/config/bounce.ini +0 -15
  112. package/config/data.headers.ini +0 -61
  113. package/config/dkim/dkim_key_gen.sh +0 -78
  114. package/config/dkim_sign.ini +0 -4
  115. package/config/dkim_verify.ini +0 -7
  116. package/config/dnsbl.ini +0 -23
  117. package/config/greylist.ini +0 -43
  118. package/config/helo.checks.ini +0 -52
  119. package/config/messagesniffer.ini +0 -18
  120. package/config/spamassassin.ini +0 -56
  121. package/dkim.js +0 -614
  122. package/docs/plugins/avg.md +0 -35
  123. package/docs/plugins/bounce.md +0 -69
  124. package/docs/plugins/clamd.md +0 -147
  125. package/docs/plugins/esets.md +0 -8
  126. package/docs/plugins/greylist.md +0 -90
  127. package/docs/plugins/helo.checks.md +0 -135
  128. package/docs/plugins/messagesniffer.md +0 -163
  129. package/docs/plugins/spamassassin.md +0 -180
  130. package/outbound/mx_lookup.js +0 -70
  131. package/plugins/auth/auth_ldap.js +0 -3
  132. package/plugins/avg.js +0 -162
  133. package/plugins/backscatterer.js +0 -25
  134. package/plugins/bounce.js +0 -381
  135. package/plugins/clamd.js +0 -382
  136. package/plugins/data.uribl.js +0 -4
  137. package/plugins/dkim_sign.js +0 -395
  138. package/plugins/dkim_verify.js +0 -62
  139. package/plugins/dns_list_base.js +0 -221
  140. package/plugins/dnsbl.js +0 -146
  141. package/plugins/dnswl.js +0 -58
  142. package/plugins/esets.js +0 -71
  143. package/plugins/graph.js +0 -5
  144. package/plugins/greylist.js +0 -645
  145. package/plugins/helo.checks.js +0 -533
  146. package/plugins/messagesniffer.js +0 -381
  147. package/plugins/rcpt_to.ldap.js +0 -3
  148. package/plugins/rcpt_to.max_count.js +0 -24
  149. package/plugins/spamassassin.js +0 -384
  150. package/tests/config/dkim/example.com/dns +0 -29
  151. package/tests/config/dkim/example.com/private +0 -6
  152. package/tests/config/dkim/example.com/public +0 -4
  153. package/tests/config/dkim/example.com/selector +0 -1
  154. package/tests/config/dkim.private.key +0 -6
  155. package/tests/config/dkim_sign.ini +0 -4
  156. package/tests/config/helo.checks.ini +0 -52
  157. package/tests/connection.js +0 -327
  158. package/tests/endpoint.js +0 -128
  159. package/tests/fixtures/vm_harness.js +0 -59
  160. package/tests/logger.js +0 -327
  161. package/tests/outbound/hmail.js +0 -112
  162. package/tests/outbound/index.js +0 -324
  163. package/tests/outbound/qfile.js +0 -67
  164. package/tests/outbound_bounce_net_errors.js +0 -173
  165. package/tests/plugins/auth/auth_base.js +0 -463
  166. package/tests/plugins/auth/auth_vpopmaild.js +0 -91
  167. package/tests/plugins/bounce.js +0 -307
  168. package/tests/plugins/clamd.js +0 -224
  169. package/tests/plugins/deprecated/relay_acl.js +0 -140
  170. package/tests/plugins/deprecated/relay_all.js +0 -59
  171. package/tests/plugins/dkim_sign.js +0 -315
  172. package/tests/plugins/dkim_signer.js +0 -108
  173. package/tests/plugins/dns_list_base.js +0 -259
  174. package/tests/plugins/dnsbl.js +0 -101
  175. package/tests/plugins/early_talker.js +0 -115
  176. package/tests/plugins/greylist.js +0 -58
  177. package/tests/plugins/helo.checks.js +0 -525
  178. package/tests/plugins/mail_from.is_resolvable.js +0 -116
  179. package/tests/plugins/queue/smtp_forward.js +0 -221
  180. package/tests/plugins/rcpt_to.host_list_base.js +0 -132
  181. package/tests/plugins/rcpt_to.in_host_list.js +0 -218
  182. package/tests/plugins/relay.js +0 -339
  183. package/tests/plugins/spamassassin.js +0 -171
  184. package/tests/plugins/status.js +0 -138
  185. package/tests/plugins/tls.js +0 -84
  186. package/tests/plugins.js +0 -247
  187. package/tests/rfc1869.js +0 -61
  188. package/tests/server.js +0 -510
  189. package/tests/smtp_client/auth.js +0 -105
  190. package/tests/smtp_client/basic.js +0 -101
  191. package/tests/smtp_client.js +0 -80
  192. package/tests/tls_socket.js +0 -333
  193. package/tests/transaction.js +0 -284
  194. /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
  195. /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
  196. /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
  197. /package/docs/{plugins → deprecated}/dnswl.md +0 -0
  198. /package/{tests → test}/.eslintrc.yaml +0 -0
  199. /package/{tests → test}/config/auth_flat_file.ini +0 -0
  200. /package/{tests → test}/config/dhparams.pem +0 -0
  201. /package/{tests → test}/config/host_list +0 -0
  202. /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
  203. /package/{tests → test}/config/outbound_tls_key.pem +0 -0
  204. /package/{tests → test}/config/smtp_forward.ini +0 -0
  205. /package/{tests → test}/config/tls/ec.pem +0 -0
  206. /package/{tests → test}/config/tls/haraka.local.pem +0 -0
  207. /package/{tests → test}/config/tls/mismatched.pem +0 -0
  208. /package/{tests → test}/config/tls_cert.pem +0 -0
  209. /package/{tests → test}/config/tls_key.pem +0 -0
  210. /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
  211. /package/{tests → test}/installation/config/test-plugin-flat +0 -0
  212. /package/{tests → test}/installation/config/test-plugin.ini +0 -0
  213. /package/{tests → test}/installation/config/tls.ini +0 -0
  214. /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
  215. /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
  216. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
  217. /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
  218. /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
  219. /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
  220. /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
  221. /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
  222. /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
  223. /package/{tests → test}/installation/plugins/inherits.js +0 -0
  224. /package/{tests → test}/installation/plugins/load_first.js +0 -0
  225. /package/{tests → test}/installation/plugins/plugin.js +0 -0
  226. /package/{tests → test}/installation/plugins/tls.js +0 -0
  227. /package/{tests → test}/loud/config/dhparams.pem +0 -0
  228. /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
  229. /package/{tests → test/loud}/config/tls.ini +0 -0
  230. /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
  231. /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
  232. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
  233. /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
  234. /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
  235. /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
  236. /package/{tests → test}/queue/multibyte +0 -0
  237. /package/{tests → test}/queue/plain +0 -0
  238. /package/{tests → test}/queue/zero-length +0 -0
  239. /package/{tests → test}/test-queue/delete-me +0 -0
package/tls_socket.js CHANGED
@@ -1,25 +1,20 @@
1
1
  'use strict';
2
- /*--------------------------------------------------------------------------*/
3
- /* Obtained and modified from http://js.5sh.net/starttls.js on 8/18/2011. */
4
- /*--------------------------------------------------------------------------*/
5
-
6
- // node.js built-ins
7
- const cluster = require('cluster');
8
- const net = require('net');
9
- const path = require('path');
10
- const { spawn } = require('child_process');
11
- const stream = require('stream');
12
- const tls = require('tls');
13
- const util = require('util');
2
+
3
+ const cluster = require('node:cluster');
4
+ const net = require('node:net');
5
+ const path = require('node:path');
6
+ const { spawn } = require('node:child_process');
7
+ const stream = require('node:stream');
8
+ const tls = require('node:tls');
9
+ const util = require('node:util');
14
10
 
15
11
  // npm packages
16
- const async = require('async');
17
- const openssl = require('openssl-wrapper').exec;
18
12
  exports.config = require('haraka-config'); // exported for tests
13
+ const Notes = require('haraka-notes')
19
14
 
20
15
  const log = require('./logger');
21
16
 
22
- const certsByHost = {};
17
+ const certsByHost = new Notes();
23
18
  const ctxByHost = {};
24
19
  let ocsp;
25
20
  let ocspCache;
@@ -64,13 +59,7 @@ class pluggableStream extends stream.Stream {
64
59
  this.emit('secureConnect', a, b);
65
60
  this.emit('secure', a, b);
66
61
  });
67
- this.targetsocket.on('secureConnection', (a, b) => {
68
- // investigate this for removal, see #2743
69
- this.emit('secureConnection', a, b);
70
- this.emit('secure', a, b);
71
- });
72
62
  this.targetsocket.on('secure', (a, b) => {
73
- this.emit('secureConnection', a, b);
74
63
  this.emit('secure', a, b);
75
64
  });
76
65
  this.targetsocket.on('end', () => {
@@ -105,13 +94,12 @@ class pluggableStream extends stream.Stream {
105
94
  this.localAddress = this.targetsocket.localAddress;
106
95
  }
107
96
  }
97
+
108
98
  clean (data) {
109
99
  if (this.targetsocket?.removeAllListeners) {
110
- [ 'data', 'secure', 'secureConnect', 'secureConnection',
111
- 'end', 'close', 'error', 'drain'
112
- ].forEach((name) => {
100
+ for (const name of ['data', 'secure', 'secureConnect', 'end', 'close', 'error', 'drain']) {
113
101
  this.targetsocket.removeAllListeners(name);
114
- })
102
+ }
115
103
  }
116
104
  this.targetsocket = {};
117
105
  this.targetsocket.write = () => {};
@@ -168,63 +156,44 @@ class pluggableStream extends stream.Stream {
168
156
  }
169
157
  }
170
158
 
171
- exports.parse_x509_names = string => {
172
- // receives the text value of a x509 certificate and returns an array of
173
- // of names extracted from the Subject CN and the v3 Subject Alternate Names
174
- const names_found = [];
175
-
176
- // log.loginfo(string);
177
-
178
- let match = /Subject:.*?CN=([^/\s]+)/.exec(string);
179
- if (match) {
180
- // log.loginfo(match[0]);
181
- if (match[1]) {
182
- // log.loginfo(match[1]);
183
- names_found.push(match[1]);
184
- }
185
- }
186
-
187
- match = /X509v3 Subject Alternative Name:[^]*X509/.exec(string);
188
- if (match) {
189
- let dns_name;
190
- const re = /DNS:([^,]+)[,\n]/g;
191
- while ((dns_name = re.exec(match[0])) !== null) {
192
- // log.loginfo(dns_name);
193
- if (names_found.includes(dns_name[1])) continue; // ignore dupes
194
- names_found.push(dns_name[1]);
195
- }
196
- }
159
+ exports.parse_x509 = async (string) => {
160
+ const res = {};
161
+ if (!string) return res
197
162
 
198
- return names_found;
199
- }
163
+ const keyRe = new RegExp('([-]+BEGIN (?:\\w+ )?PRIVATE KEY[-]+[^-]*[-]+END (?:\\w+ )?PRIVATE KEY[-]+)', 'gm')
164
+ res.keys = string.match(keyRe)
200
165
 
201
- exports.parse_x509_expire = (file, string) => {
166
+ const certRe = new RegExp('([-]+BEGIN CERTIFICATE[-]+[^-]*[-]+END CERTIFICATE[-]+)', 'gm')
167
+ res.chain = string.match(certRe)
202
168
 
203
- const dateMatch = /Not After : (.*)/.exec(string);
204
- if (!dateMatch) return;
169
+ if (res.chain?.length) {
170
+ const opensslArgs = [res.chain[0], 'x509', '-noout']
171
+ // shush openssl, https://github.com/openssl/openssl/issues/22893
172
+ // if (['darwin','linux','freebsd'].includes(process.platform))
173
+ // opensslArgs.push('-in', '/dev/stdin')
205
174
 
206
- // log.loginfo(dateMatch[1]);
207
- return new Date(dateMatch[1]);
208
- }
175
+ // it's cleaner to call openssl with each of -enddate, -subject, etc, but it costs
176
+ // 40-50ms per spawn with node v21 on a M1 MBP
177
+ const raw = await openssl(...opensslArgs, '-enddate', '-subject', '-ext', 'subjectAltName')
178
+ if (!raw) return res
209
179
 
210
- exports.parse_x509 = string => {
211
- const res = {};
212
- if (!string) return res
180
+ res.expire = new Date(raw.match(/notAfter=(.* [A-Z]{3})/)[1])
213
181
 
214
- const keyRe = new RegExp('([-]+BEGIN (?:\\w+ )?PRIVATE KEY[-]+[^-]*[-]+END (?:\\w+ )?PRIVATE KEY[-]+)', 'gm')
215
- const keys = string.match(keyRe)
216
- if (keys) res.key = Buffer.from(keys.join('\n'));
182
+ const match = /CN\s*=\s*([^/\s,]+)/.exec(raw);
183
+ if (match && match[1]) res.names = [ match[1] ]
217
184
 
218
- const certRe = new RegExp('([-]+BEGIN CERTIFICATE[-]+[^-]*[-]+END CERTIFICATE[-]+)', 'gm')
219
- const certs = string.match(certRe)
220
- if (certs) res.cert = Buffer.from(certs.join('\n'));
185
+ for (let name of Array.from(raw.matchAll(/DNS:([^\s,]+)/gm), (m) => m[0])) {
186
+ name = name.replace('DNS:', '')
187
+ if (!res.names.includes(name)) res.names.push(name)
188
+ }
189
+ }
221
190
 
222
191
  return res;
223
192
  }
224
193
 
225
194
  exports.load_tls_ini = (opts) => {
226
195
 
227
- log.loginfo(`loading tls.ini`); // from ${this.config.root_path}`);
196
+ log.info(`loading tls.ini`); // from ${this.config.root_path}`);
228
197
 
229
198
  const cfg = exports.config.get('tls.ini', {
230
199
  booleans: [
@@ -253,18 +222,18 @@ exports.load_tls_ini = (opts) => {
253
222
  if (cfg.mutual_auth_hosts_exclude === undefined) cfg.mutual_auth_hosts_exclude = {};
254
223
 
255
224
  if (cfg.main.enableOCSPStapling !== undefined) {
256
- log.logerror('deprecated setting enableOCSPStapling in tls.ini');
225
+ log.error('deprecated setting enableOCSPStapling in tls.ini');
257
226
  cfg.main.requestOCSP = cfg.main.enableOCSPStapling;
258
227
  }
259
228
 
260
229
  if (ocsp === undefined && cfg.main.requestOCSP) {
261
230
  try {
262
231
  ocsp = require('ocsp');
263
- log.logdebug('ocsp loaded');
232
+ log.debug('ocsp loaded');
264
233
  ocspCache = new ocsp.Cache();
265
234
  }
266
235
  catch (ignore) {
267
- log.lognotice("OCSP Stapling not available.");
236
+ log.notice("OCSP Stapling not available.");
268
237
  }
269
238
  }
270
239
 
@@ -287,15 +256,8 @@ exports.load_tls_ini = (opts) => {
287
256
  return cfg;
288
257
  }
289
258
 
290
- exports.saveOpt = (name, opt, val) => {
291
- if (certsByHost[name] === undefined) certsByHost[name] = {};
292
- certsByHost[name][opt] = val;
293
- }
294
-
295
259
  exports.applySocketOpts = name => {
296
260
 
297
- if (!certsByHost[name]) certsByHost[name] = {};
298
-
299
261
  // https://nodejs.org/api/tls.html#tls_new_tls_tlssocket_socket_options
300
262
  const TLSSocketOptions = [
301
263
  // 'server' // manually added
@@ -311,43 +273,38 @@ exports.applySocketOpts = name => {
311
273
  'ecdhCurve', 'secureProtocol', 'secureOptions', 'sessionIdContext'
312
274
  ];
313
275
 
314
- const allOpts = TLSSocketOptions.concat(createSecureContextOptions);
315
-
316
- for (const opt of allOpts) {
276
+ for (const opt of [ ...TLSSocketOptions, ...createSecureContextOptions ]) {
317
277
 
318
278
  if (this.cfg[name] && this.cfg[name][opt] !== undefined) {
319
279
  // if the setting exists in tls.ini [name]
320
- this.saveOpt(name, opt, this.cfg[name][opt]);
321
- continue;
280
+ certsByHost.set([name, opt], this.cfg[name][opt])
322
281
  }
323
-
324
- if (this.cfg.main[opt] !== undefined) {
325
- // if the setting exists in tls.ini [main]
326
- // then save it to the certsByHost options
327
- this.saveOpt(name, opt, this.cfg.main[opt]);
328
- continue;
282
+ else if (this.cfg.main[opt] !== undefined) {
283
+ // save settings in tls.ini [main] to each CN
284
+ certsByHost.set([name, opt], this.cfg.main[opt])
329
285
  }
330
-
331
- // defaults
332
- switch (opt) {
333
- case 'sessionIdContext':
334
- this.saveOpt(name, opt, 'haraka');
335
- break;
336
- case 'isServer':
337
- this.saveOpt(name, opt, true);
338
- break;
339
- case 'key':
340
- this.saveOpt(name, opt, 'tls_key.pem');
341
- break;
342
- case 'cert':
343
- this.saveOpt(name, opt, 'tls_cert.pem');
344
- break;
345
- case 'dhparam':
346
- this.saveOpt(name, opt, 'dhparams.pem');
347
- break;
348
- case 'SNICallback':
349
- this.saveOpt(name, opt, SNICallback);
350
- break;
286
+ else {
287
+ // defaults
288
+ switch (opt) {
289
+ case 'sessionIdContext':
290
+ certsByHost.set([name, opt], 'haraka')
291
+ break;
292
+ case 'isServer':
293
+ certsByHost.set([name, opt], true)
294
+ break;
295
+ case 'key':
296
+ certsByHost.set([name, opt], 'tls_key.pem')
297
+ break;
298
+ case 'cert':
299
+ certsByHost.set([name, opt], 'tls_cert.pem')
300
+ break;
301
+ case 'dhparam':
302
+ certsByHost.set([name, opt], 'dhparams.pem')
303
+ break;
304
+ case 'SNICallback':
305
+ certsByHost.set([name, opt], exports.SNICallback)
306
+ break;
307
+ }
351
308
  }
352
309
  }
353
310
  }
@@ -357,13 +314,13 @@ exports.load_default_opts = () => {
357
314
  const cfg = certsByHost['*'];
358
315
 
359
316
  if (cfg.dhparam && typeof cfg.dhparam === 'string') {
360
- log.logdebug(`loading dhparams from ${cfg.dhparam}`);
361
- this.saveOpt('*', 'dhparam', this.config.get(cfg.dhparam, 'binary'));
317
+ log.debug(`loading dhparams from ${cfg.dhparam}`);
318
+ certsByHost.set('*.dhparam', this.config.get(cfg.dhparam, 'binary'))
362
319
  }
363
320
 
364
321
  if (cfg.ca && typeof cfg.ca === 'string') {
365
- log.loginfo(`loading CA certs from ${cfg.ca}`);
366
- this.saveOpt('*', 'ca', this.config.get(cfg.ca, 'binary'));
322
+ log.info(`loading CA certs from ${cfg.ca}`);
323
+ certsByHost.set('*.ca', this.config.get(cfg.ca, 'binary'))
367
324
  }
368
325
 
369
326
  // make non-array key/cert option into Arrays with one entry
@@ -371,7 +328,7 @@ exports.load_default_opts = () => {
371
328
  if (!(Array.isArray(cfg.cert))) cfg.cert = [cfg.cert];
372
329
 
373
330
  if (cfg.key.length != cfg.cert.length) {
374
- log.logerror(`number of keys (${cfg.key.length}) not equal to certs (${cfg.cert.length}).`);
331
+ log.error(`number of keys (${cfg.key.length}) not equal to certs (${cfg.cert.length}).`);
375
332
  }
376
333
 
377
334
  // if key file has already been loaded, it'll be a Buffer.
@@ -381,11 +338,11 @@ exports.load_default_opts = () => {
381
338
  if (!keyFileName) return;
382
339
  const key = this.config.get(keyFileName, 'binary');
383
340
  if (!key) {
384
- log.logerror(`tls key ${path.join(this.config.root_path, keyFileName)} could not be loaded.`);
341
+ log.error(`tls key ${path.join(this.config.root_path, keyFileName)} could not be loaded.`);
385
342
  }
386
343
  return key;
387
344
  })
388
- this.saveOpt('*', 'key', asArray);
345
+ certsByHost.set('*.key', asArray)
389
346
  }
390
347
 
391
348
  if (typeof cfg.cert[0] === 'string') {
@@ -393,11 +350,11 @@ exports.load_default_opts = () => {
393
350
  if (!certFileName) return;
394
351
  const cert = this.config.get(certFileName, 'binary');
395
352
  if (!cert) {
396
- log.logerror(`tls cert ${path.join(this.config.root_path, certFileName)} could not be loaded.`);
353
+ log.error(`tls cert ${path.join(this.config.root_path, certFileName)} could not be loaded.`);
397
354
  }
398
355
  return cert;
399
356
  })
400
- this.saveOpt('*', 'cert', asArray);
357
+ certsByHost.set('*.cert', asArray)
401
358
  }
402
359
 
403
360
  if (cfg.cert[0] && cfg.key[0]) {
@@ -410,110 +367,122 @@ exports.load_default_opts = () => {
410
367
  }
411
368
  }
412
369
 
413
- function SNICallback (servername, sniDone) {
414
- log.logdebug(`SNI servername: ${servername}`);
415
-
416
- if (ctxByHost[servername] === undefined) servername = '*';
370
+ exports.SNICallback = function (servername, sniDone) {
371
+ log.debug(`SNI servername: ${servername}`);
417
372
 
418
- sniDone(null, ctxByHost[servername]);
373
+ sniDone(null, ctxByHost[servername] || ctxByHost['*']);
419
374
  }
420
375
 
421
- exports.get_certs_dir = (tlsDir, done) => {
376
+ exports.get_certs_dir = async (tlsDir) => {
377
+ const r = {}
378
+ const watcher = async () => {
379
+ exports.get_certs_dir(tlsDir)
380
+ }
381
+ const dirOpts = { type: 'binary', watchCb: watcher }
382
+
383
+ const files = await this.config.getDir(tlsDir, dirOpts)
384
+ for (const file of files) {
385
+ try {
386
+ r[file.path] = await exports.parse_x509(file.data.toString());
387
+ }
388
+ catch (err) {
389
+ log.debug(err.message)
390
+ }
391
+ }
392
+
393
+ log.debug(`found ${Object.keys(r).length} files in config/tls`);
394
+ if (Object.keys(r).length === 0) return
422
395
 
423
- this.config.getDir(tlsDir, {}, (iterErr, files) => {
424
- if (iterErr) return done(iterErr);
396
+ const s = {} // certs by name (CN)
425
397
 
426
- async.map(files, (file, iter_done) => {
398
+ for (const fp in r) {
427
399
 
428
- const parsed = exports.parse_x509(file.data.toString());
429
- if (!parsed.key) {
430
- return iter_done(null, {
431
- err: new Error(`no PRIVATE key in ${file.path}`),
432
- file
433
- });
434
- }
435
- if (!parsed.cert) {
436
- return iter_done(null, {
437
- err: new Error(`no CERT in ${file.path}`),
438
- file
439
- });
400
+ if (r[fp].expire && r[fp].expire < new Date()) {
401
+ log.error(`${fp} expired on ${r[fp].expire}`)
402
+ }
403
+
404
+ // a file with a key and no cert, get name from file
405
+ if (!r[fp].names) r[fp].names = [ path.parse(fp).name ]
406
+
407
+ for (let name of r[fp].names) {
408
+ if (name[0] === '_') name = name.replace('_', '*') // windows
409
+ if (s[name] === undefined) s[name] = {}
410
+ if (!s[name].key && r[fp].keys) s[name].key = r[fp].keys[0]
411
+ if (!s[name].cert && r[fp].chain) {
412
+ s[name].cert = r[fp].chain[0]
413
+ s[name].file = fp
440
414
  }
415
+ }
416
+ }
441
417
 
442
- const x509args = { noout: true, text: true };
443
-
444
- openssl('x509', parsed.cert, x509args, (e, as_str) => {
445
- if (e) {
446
- log.logerror(`BAD TLS in ${file.path}`);
447
- log.logerror(e);
448
- }
449
-
450
- const expire = this.parse_x509_expire(file, as_str);
451
- if (expire && expire < new Date()) {
452
- log.logerror(`${file.path} expired on ${expire}`);
453
- }
454
-
455
- iter_done(null, {
456
- err: e,
457
- file: path.basename(file.path),
458
- key: parsed.key,
459
- cert: parsed.cert,
460
- names: this.parse_x509_names(as_str),
461
- expires: expire,
462
- })
463
- })
464
- },
465
- (finalErr, certs) => {
418
+ for (const cn in s) {
419
+ if (!s[cn].cert || !s[cn].key) {
420
+ delete s[cn]
421
+ continue
422
+ }
466
423
 
467
- if (finalErr) log.logerror(finalErr);
424
+ this.applySocketOpts(cn) // from tls.ini
425
+ certsByHost.set([cn, 'cert'], Buffer.from(s[cn].cert))
426
+ certsByHost.set([cn, 'key'], Buffer.from(s[cn].key))
427
+ certsByHost.set([cn, 'dhparam'], certsByHost['*'].dhparam, true);
468
428
 
469
- if (!certs || !certs.length) {
470
- log.loginfo('found 0 TLS certs in config/tls');
471
- return done(null, certs);
472
- }
429
+ // all opts are applied, generate TLS context
430
+ try {
431
+ ctxByHost[cn] = tls.createSecureContext(certsByHost.get([cn]));
432
+ }
433
+ catch (err) {
434
+ log.error(`CN '${cn}' loading got: ${err.message}`)
435
+ delete ctxByHost[cn]
436
+ delete certsByHost[cn]
437
+ }
438
+ }
473
439
 
474
- log.loginfo(`found ${certs.length} TLS certs in config/tls`);
475
- certs.forEach(cert => {
476
- if (undefined === cert) return;
477
- if (cert.err) {
478
- log.logerror(`${cert.file} had error: ${cert.err.message}`);
479
- return;
480
- }
481
-
482
- // log.logdebug(cert); // DANGER: Also logs private key!
483
- cert.names.forEach(name => {
484
- this.applySocketOpts(name);
485
-
486
- this.saveOpt(name, 'cert', cert.cert);
487
- this.saveOpt(name, 'key', cert.key);
488
- if (certsByHost['*'] !== undefined && certsByHost['*'].dhparam) {
489
- // copy in dhparam from default '*' TLS config
490
- this.saveOpt(name, 'dhparam', certsByHost['*'].dhparam);
491
- }
492
-
493
- // now that all opts are applied, generate TLS context
494
- ctxByHost[name] = tls.createSecureContext(certsByHost[name]);
495
- })
496
- })
440
+ log.info(`found ${Object.keys(s).length} TLS certs in config/tls`);
441
+
442
+ return certsByHost // used only by tests
443
+ }
444
+
445
+ function openssl (crt, ...params) {
446
+ return new Promise((resolve) => {
447
+ let crtTxt = ''
497
448
 
498
- // log.loginfo(exports.certsByHost);
499
- done(null, exports.certsByHost);
449
+ const o = spawn('openssl', [...params], { timeout: 1000 });
450
+ o.stdout.on('data', data => {
451
+ crtTxt += data
500
452
  })
453
+
454
+ o.stderr.on('data', data => {
455
+ log.debug(`err: ${data.toString().trim()}`)
456
+ })
457
+
458
+ o.on('close', code => {
459
+ if (code !== 0) {
460
+ if (code) console.error(code)
461
+ }
462
+ resolve(crtTxt)
463
+ })
464
+
465
+ o.stdin.write(crt)
466
+ o.stdin.write('\n')
501
467
  })
502
468
  }
503
469
 
504
- exports.getSocketOpts = (name, done) => {
470
+ exports.getSocketOpts = async (name) => {
505
471
 
506
472
  // startup time, load the config/tls dir
507
473
  if (!certsByHost['*']) this.load_tls_ini();
508
474
 
509
- this.get_certs_dir('tls', () => {
510
- if (certsByHost[name]) {
511
- // log.logdebug(certsByHost[name]);
512
- return done(certsByHost[name]);
475
+ try {
476
+ await this.get_certs_dir('tls')
477
+ }
478
+ catch (err) {
479
+ if (err.code !== 'ENOENT') {
480
+ console.error(err.messsage)
481
+ log.error(err)
513
482
  }
514
- // log.logdebug(certsByHost['*']);
515
- done(certsByHost['*']);
516
- });
483
+ }
484
+
485
+ return certsByHost[name] || certsByHost['*']
517
486
  }
518
487
 
519
488
  function pipe (cleartext, socket) {
@@ -543,12 +512,12 @@ exports.ensureDhparams = done => {
543
512
  const filePath = this.cfg.main.dhparam || 'dhparams.pem';
544
513
  const fpResolved = path.resolve(exports.config.root_path, filePath);
545
514
 
546
- log.loginfo(`Generating a 2048 bit dhparams file at ${fpResolved}`);
515
+ log.info(`Generating a 2048 bit dhparams file at ${fpResolved}`);
547
516
 
548
517
  const o = spawn('openssl', ['dhparam', '-out', `${fpResolved}`, '2048']);
549
518
  o.stdout.on('data', data => {
550
519
  // normally empty output
551
- log.logdebug(data);
520
+ log.debug(data);
552
521
  })
553
522
 
554
523
  o.stderr.on('data', data => {
@@ -560,52 +529,48 @@ exports.ensureDhparams = done => {
560
529
  return done(`Error code: ${code}`);
561
530
  }
562
531
 
563
- log.loginfo(`Saved to ${fpResolved}`);
532
+ log.info(`Saved to ${fpResolved}`);
564
533
  const content = this.config.get(filePath, 'binary');
565
534
 
566
- this.saveOpt('*', 'dhparam', content);
535
+ certsByHost.set('*.dhparam', content)
567
536
  done(null, certsByHost['*'].dhparam);
568
537
  });
569
538
  }
570
539
 
571
540
  exports.addOCSP = server => {
572
541
  if (!ocsp) {
573
- log.logdebug('addOCSP: not available');
542
+ log.debug(`addOCSP: 'ocsp' not available`);
574
543
  return;
575
544
  }
576
545
 
577
546
  if (server.listenerCount('OCSPRequest') > 0) {
578
- log.logdebug('OCSPRequest already listening');
547
+ log.debug('OCSPRequest already listening');
579
548
  return;
580
549
  }
581
550
 
582
- log.logdebug('adding OCSPRequest listener');
551
+ log.debug('adding OCSPRequest listener');
583
552
  server.on('OCSPRequest', (cert, issuer, ocr_cb) => {
584
- log.logdebug(`OCSPRequest: ${cert}`);
585
- ocsp.getOCSPURI(cert, (err, uri) => {
586
- log.logdebug(`OCSP Request, URI: ${uri }, err=${ err}`);
553
+ log.debug(`OCSPRequest: ${cert}`);
554
+ ocsp.getOCSPURI(cert, async (err, uri) => {
555
+ log.debug(`OCSP Request, URI: ${uri}, err=${err}`);
587
556
  if (err) return ocr_cb(err);
588
557
  if (uri === null) return ocr_cb(); // not working OCSP server
589
558
 
590
559
  const req = ocsp.request.generate(cert, issuer);
560
+ const cached = await ocspCache.probe(req.id)
591
561
 
592
- // look for a cached value first
593
- ocspCache.probe(req.id, (err2, cached) => {
594
- if (err2) return ocr_cb(err2);
595
-
596
- if (cached) {
597
- log.logdebug(`OCSP cache: ${util.inspect(cached)}`);
598
- return ocr_cb(err2, cached.response);
599
- }
562
+ if (cached) {
563
+ log.debug(`OCSP cache: ${util.inspect(cached)}`);
564
+ return ocr_cb(null, cached.response);
565
+ }
600
566
 
601
- const options = {
602
- url: uri,
603
- ocsp: req.data
604
- };
567
+ const options = {
568
+ url: uri,
569
+ ocsp: req.data
570
+ };
605
571
 
606
- log.logdebug(`OCSP req:${util.inspect(req)}`);
607
- ocspCache.request(req.id, options, ocr_cb);
608
- })
572
+ log.debug(`OCSP req:${util.inspect(req)}`);
573
+ ocspCache.request(req.id, options, ocr_cb);
609
574
  })
610
575
  })
611
576
  }
@@ -615,7 +580,7 @@ exports.shutdown = () => {
615
580
  }
616
581
 
617
582
  function cleanOcspCache () {
618
- log.logdebug(`Cleaning ocspCache. How many keys? ${Object.keys(ocspCache.cache).length}`);
583
+ log.debug(`Cleaning ocspCache. How many keys? ${Object.keys(ocspCache.cache).length}`);
619
584
  Object.keys(ocspCache.cache).forEach((key) => {
620
585
  clearTimeout(ocspCache.cache[key].timer);
621
586
  });
@@ -640,7 +605,7 @@ function createServer (cb) {
640
605
  exports.addOCSP(server);
641
606
 
642
607
  socket.upgrade = cb2 => {
643
- log.logdebug('Upgrading to TLS');
608
+ log.debug('Upgrading to TLS');
644
609
 
645
610
  socket.clean();
646
611
 
@@ -661,7 +626,7 @@ function createServer (cb) {
661
626
  socket.emit('error', exception);
662
627
  })
663
628
  .on('secure', () => {
664
- log.logdebug('TLS secured.');
629
+ log.debug('TLS secured.');
665
630
  socket.emit('secure');
666
631
  const cipher = cleartext.getCipher();
667
632
  cipher.version = cleartext.getProtocol();
@@ -695,18 +660,10 @@ function getCertFor (host) {
695
660
  return certsByHost['*']; // the default TLS cert
696
661
  }
697
662
 
698
- function connect (port, host) {
699
- let conn_options = {};
700
- if (typeof port === 'object') {
701
- conn_options = port;
702
- }
703
- else {
704
- conn_options.port = port;
705
- conn_options.host = host;
706
- }
663
+ function connect (conn_options = {}) {
664
+ // called by outbound/client_pool, smtp_client, plugins/spamassassin,avg,clamd
707
665
 
708
666
  const cryptoSocket = net.connect(conn_options);
709
-
710
667
  const socket = new pluggableStream(cryptoSocket);
711
668
 
712
669
  socket.upgrade = (options, cb2) => {
@@ -714,12 +671,7 @@ function connect (port, host) {
714
671
  cryptoSocket.removeAllListeners('data');
715
672
 
716
673
  if (exports.tls_valid) {
717
- /* SUNSET notice: code added 2021-01. We've changed the default to not
718
- send TLS client certificates. The mutual_tls flag switches them back
719
- on. If no need for these settings surfaces in 2 years, nuke this block
720
- of code. If you care about these options, create a PR removing this
721
- comment. See #2693.
722
- */
674
+ const host = conn_options.host
723
675
  if (exports.cfg === undefined) exports.load_tls_ini();
724
676
  if (exports.cfg.mutual_auth_hosts[host]) {
725
677
  options = Object.assign(options, getCertFor(exports.cfg.mutual_auth_hosts[host]));
@@ -738,13 +690,11 @@ function connect (port, host) {
738
690
  pipe(cleartext, cryptoSocket);
739
691
 
740
692
  cleartext.on('error', err => {
741
- if (err.reason) {
742
- log.logerror(`client TLS error: ${err}`);
743
- }
693
+ if (err.reason) log.error(`client TLS error: ${err}`);
744
694
  })
745
695
 
746
696
  cleartext.once('secureConnect', () => {
747
- log.logdebug('client TLS secured.');
697
+ log.debug('client TLS secured.');
748
698
  const cipher = cleartext.getCipher();
749
699
  cipher.version = cleartext.getProtocol();
750
700
  if (cb2) cb2(
@@ -765,7 +715,7 @@ function connect (port, host) {
765
715
 
766
716
  socket.attach(socket.cleartext);
767
717
 
768
- log.logdebug('client TLS upgrade in progress, awaiting secured.');
718
+ log.debug('client TLS upgrade in progress, awaiting secured.');
769
719
  }
770
720
 
771
721
  return socket;