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.
- package/.eslintrc.yaml +4 -9
- package/CONTRIBUTORS.md +11 -0
- package/Changes.md +1397 -1213
- package/Plugins.md +117 -105
- package/README.md +4 -13
- package/bin/haraka +198 -298
- package/config/auth_flat_file.ini +1 -0
- package/config/dhparams.pem +8 -0
- package/config/mail_from.is_resolvable.ini +4 -2
- package/config/me +1 -0
- package/config/outbound.ini +0 -2
- package/config/plugins +35 -36
- package/config/smtp.ini +1 -1
- package/config/smtp.json +17 -0
- package/config/tls.ini +2 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/connection.js +46 -73
- package/contrib/bsd-rc.d/haraka +3 -1
- package/contrib/plugin2npm.sh +6 -36
- package/docs/CoreConfig.md +2 -2
- package/docs/Logging.md +7 -21
- package/docs/Outbound.md +104 -201
- package/docs/Plugins.md +2 -2
- package/docs/Transaction.md +59 -82
- package/docs/plugins/queue/smtp_proxy.md +5 -10
- package/docs/plugins/tls.md +37 -9
- package/endpoint.js +16 -13
- package/haraka.js +10 -14
- package/host_pool.js +5 -5
- package/http/html/index.html +6 -5
- package/line_socket.js +3 -4
- package/logger.js +44 -28
- package/outbound/client_pool.js +27 -23
- package/outbound/config.js +4 -6
- package/outbound/fsync_writestream.js +1 -1
- package/outbound/hmail.js +178 -218
- package/outbound/index.js +86 -99
- package/outbound/qfile.js +1 -1
- package/outbound/queue.js +51 -44
- package/outbound/timer_queue.js +3 -2
- package/outbound/tls.js +19 -7
- package/package.json +60 -51
- package/plugins/.eslintrc.yaml +0 -6
- package/plugins/auth/auth_base.js +4 -2
- package/plugins/auth/auth_proxy.js +14 -12
- package/plugins/auth/auth_vpopmaild.js +1 -1
- package/plugins/block_me.js +1 -1
- package/plugins/data.signatures.js +2 -4
- package/plugins/early_talker.js +2 -1
- package/plugins/mail_from.is_resolvable.js +65 -135
- package/plugins/queue/deliver.js +4 -5
- package/plugins/queue/lmtp.js +11 -12
- package/plugins/queue/qmail-queue.js +2 -2
- package/plugins/queue/quarantine.js +2 -2
- package/plugins/queue/rabbitmq.js +16 -17
- package/plugins/queue/smtp_forward.js +3 -3
- package/plugins/queue/smtp_proxy.js +10 -1
- package/plugins/queue/test.js +2 -2
- package/plugins/rcpt_to.host_list_base.js +5 -5
- package/plugins/rcpt_to.in_host_list.js +2 -2
- package/plugins/relay.js +6 -7
- package/plugins/reseed_rng.js +1 -1
- package/plugins/status.js +37 -33
- package/plugins/tls.js +2 -2
- package/plugins/xclient.js +3 -2
- package/plugins.js +50 -54
- package/run_tests +3 -30
- package/server.js +190 -190
- package/smtp_client.js +30 -23
- package/{tests → test}/config/plugins +0 -2
- package/{tests → test}/config/smtp.ini +3 -1
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/{tests/loud → test}/config/tls.ini +4 -2
- package/test/connection.js +302 -0
- package/test/endpoint.js +94 -0
- package/{tests → test}/fixtures/line_socket.js +1 -1
- package/{tests → test}/fixtures/util_hmailitem.js +19 -25
- package/{tests → test}/host_pool.js +42 -57
- package/test/logger.js +258 -0
- package/test/outbound/hmail.js +141 -0
- package/test/outbound/index.js +220 -0
- package/test/outbound/qfile.js +126 -0
- package/test/outbound_bounce_net_errors.js +142 -0
- package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
- package/test/plugins/auth/auth_base.js +484 -0
- package/test/plugins/auth/auth_vpopmaild.js +83 -0
- package/test/plugins/early_talker.js +104 -0
- package/test/plugins/mail_from.is_resolvable.js +35 -0
- package/test/plugins/queue/smtp_forward.js +206 -0
- package/test/plugins/rcpt_to.host_list_base.js +122 -0
- package/test/plugins/rcpt_to.in_host_list.js +193 -0
- package/test/plugins/relay.js +303 -0
- package/test/plugins/status.js +130 -0
- package/test/plugins/tls.js +70 -0
- package/test/plugins.js +228 -0
- package/test/rfc1869.js +73 -0
- package/test/server.js +491 -0
- package/test/smtp_client.js +299 -0
- package/test/tls_socket.js +277 -0
- package/test/transaction.js +270 -0
- package/tls_socket.js +202 -252
- package/transaction.js +8 -23
- package/CONTRIBUTING.md +0 -1
- package/bin/dkimverify +0 -40
- package/config/access.domains +0 -13
- package/config/attachment.ctype.regex +0 -2
- package/config/attachment.filename.regex +0 -1
- package/config/avg.ini +0 -5
- package/config/bounce.ini +0 -15
- package/config/data.headers.ini +0 -61
- package/config/dkim/dkim_key_gen.sh +0 -78
- package/config/dkim_sign.ini +0 -4
- package/config/dkim_verify.ini +0 -7
- package/config/dnsbl.ini +0 -23
- package/config/greylist.ini +0 -43
- package/config/helo.checks.ini +0 -52
- package/config/messagesniffer.ini +0 -18
- package/config/spamassassin.ini +0 -56
- package/dkim.js +0 -614
- package/docs/plugins/avg.md +0 -35
- package/docs/plugins/bounce.md +0 -69
- package/docs/plugins/clamd.md +0 -147
- package/docs/plugins/esets.md +0 -8
- package/docs/plugins/greylist.md +0 -90
- package/docs/plugins/helo.checks.md +0 -135
- package/docs/plugins/messagesniffer.md +0 -163
- package/docs/plugins/spamassassin.md +0 -180
- package/outbound/mx_lookup.js +0 -70
- package/plugins/auth/auth_ldap.js +0 -3
- package/plugins/avg.js +0 -162
- package/plugins/backscatterer.js +0 -25
- package/plugins/bounce.js +0 -381
- package/plugins/clamd.js +0 -382
- package/plugins/data.uribl.js +0 -4
- package/plugins/dkim_sign.js +0 -395
- package/plugins/dkim_verify.js +0 -62
- package/plugins/dns_list_base.js +0 -221
- package/plugins/dnsbl.js +0 -146
- package/plugins/dnswl.js +0 -58
- package/plugins/esets.js +0 -71
- package/plugins/graph.js +0 -5
- package/plugins/greylist.js +0 -645
- package/plugins/helo.checks.js +0 -533
- package/plugins/messagesniffer.js +0 -381
- package/plugins/rcpt_to.ldap.js +0 -3
- package/plugins/rcpt_to.max_count.js +0 -24
- package/plugins/spamassassin.js +0 -384
- package/tests/config/dkim/example.com/dns +0 -29
- package/tests/config/dkim/example.com/private +0 -6
- package/tests/config/dkim/example.com/public +0 -4
- package/tests/config/dkim/example.com/selector +0 -1
- package/tests/config/dkim.private.key +0 -6
- package/tests/config/dkim_sign.ini +0 -4
- package/tests/config/helo.checks.ini +0 -52
- package/tests/connection.js +0 -327
- package/tests/endpoint.js +0 -128
- package/tests/fixtures/vm_harness.js +0 -59
- package/tests/logger.js +0 -327
- package/tests/outbound/hmail.js +0 -112
- package/tests/outbound/index.js +0 -324
- package/tests/outbound/qfile.js +0 -67
- package/tests/outbound_bounce_net_errors.js +0 -173
- package/tests/plugins/auth/auth_base.js +0 -463
- package/tests/plugins/auth/auth_vpopmaild.js +0 -91
- package/tests/plugins/bounce.js +0 -307
- package/tests/plugins/clamd.js +0 -224
- package/tests/plugins/deprecated/relay_acl.js +0 -140
- package/tests/plugins/deprecated/relay_all.js +0 -59
- package/tests/plugins/dkim_sign.js +0 -315
- package/tests/plugins/dkim_signer.js +0 -108
- package/tests/plugins/dns_list_base.js +0 -259
- package/tests/plugins/dnsbl.js +0 -101
- package/tests/plugins/early_talker.js +0 -115
- package/tests/plugins/greylist.js +0 -58
- package/tests/plugins/helo.checks.js +0 -525
- package/tests/plugins/mail_from.is_resolvable.js +0 -116
- package/tests/plugins/queue/smtp_forward.js +0 -221
- package/tests/plugins/rcpt_to.host_list_base.js +0 -132
- package/tests/plugins/rcpt_to.in_host_list.js +0 -218
- package/tests/plugins/relay.js +0 -339
- package/tests/plugins/spamassassin.js +0 -171
- package/tests/plugins/status.js +0 -138
- package/tests/plugins/tls.js +0 -84
- package/tests/plugins.js +0 -247
- package/tests/rfc1869.js +0 -61
- package/tests/server.js +0 -510
- package/tests/smtp_client/auth.js +0 -105
- package/tests/smtp_client/basic.js +0 -101
- package/tests/smtp_client.js +0 -80
- package/tests/tls_socket.js +0 -333
- package/tests/transaction.js +0 -284
- /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
- /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
- /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
- /package/docs/{plugins → deprecated}/dnswl.md +0 -0
- /package/{tests → test}/.eslintrc.yaml +0 -0
- /package/{tests → test}/config/auth_flat_file.ini +0 -0
- /package/{tests → test}/config/dhparams.pem +0 -0
- /package/{tests → test}/config/host_list +0 -0
- /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
- /package/{tests → test}/config/outbound_tls_key.pem +0 -0
- /package/{tests → test}/config/smtp_forward.ini +0 -0
- /package/{tests → test}/config/tls/ec.pem +0 -0
- /package/{tests → test}/config/tls/haraka.local.pem +0 -0
- /package/{tests → test}/config/tls/mismatched.pem +0 -0
- /package/{tests → test}/config/tls_cert.pem +0 -0
- /package/{tests → test}/config/tls_key.pem +0 -0
- /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
- /package/{tests → test}/installation/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/config/tls.ini +0 -0
- /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
- /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
- /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
- /package/{tests → test}/installation/plugins/inherits.js +0 -0
- /package/{tests → test}/installation/plugins/load_first.js +0 -0
- /package/{tests → test}/installation/plugins/plugin.js +0 -0
- /package/{tests → test}/installation/plugins/tls.js +0 -0
- /package/{tests → test}/loud/config/dhparams.pem +0 -0
- /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
- /package/{tests → test/loud}/config/tls.ini +0 -0
- /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
- /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- /package/{tests → test}/queue/multibyte +0 -0
- /package/{tests → test}/queue/plain +0 -0
- /package/{tests → test}/queue/zero-length +0 -0
- /package/{tests → test}/test-queue/delete-me +0 -0
package/tls_socket.js
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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
|
-
[
|
|
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.
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
199
|
-
|
|
163
|
+
const keyRe = new RegExp('([-]+BEGIN (?:\\w+ )?PRIVATE KEY[-]+[^-]*[-]+END (?:\\w+ )?PRIVATE KEY[-]+)', 'gm')
|
|
164
|
+
res.keys = string.match(keyRe)
|
|
200
165
|
|
|
201
|
-
|
|
166
|
+
const certRe = new RegExp('([-]+BEGIN CERTIFICATE[-]+[^-]*[-]+END CERTIFICATE[-]+)', 'gm')
|
|
167
|
+
res.chain = string.match(certRe)
|
|
202
168
|
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
const res = {};
|
|
212
|
-
if (!string) return res
|
|
180
|
+
res.expire = new Date(raw.match(/notAfter=(.* [A-Z]{3})/)[1])
|
|
213
181
|
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
232
|
+
log.debug('ocsp loaded');
|
|
264
233
|
ocspCache = new ocsp.Cache();
|
|
265
234
|
}
|
|
266
235
|
catch (ignore) {
|
|
267
|
-
log.
|
|
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
|
|
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
|
-
|
|
321
|
-
continue;
|
|
280
|
+
certsByHost.set([name, opt], this.cfg[name][opt])
|
|
322
281
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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.
|
|
361
|
-
|
|
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.
|
|
366
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
414
|
-
log.
|
|
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
|
|
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
|
-
|
|
424
|
-
if (iterErr) return done(iterErr);
|
|
396
|
+
const s = {} // certs by name (CN)
|
|
425
397
|
|
|
426
|
-
|
|
398
|
+
for (const fp in r) {
|
|
427
399
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
499
|
-
|
|
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
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
515
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
532
|
+
log.info(`Saved to ${fpResolved}`);
|
|
564
533
|
const content = this.config.get(filePath, 'binary');
|
|
565
534
|
|
|
566
|
-
|
|
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.
|
|
542
|
+
log.debug(`addOCSP: 'ocsp' not available`);
|
|
574
543
|
return;
|
|
575
544
|
}
|
|
576
545
|
|
|
577
546
|
if (server.listenerCount('OCSPRequest') > 0) {
|
|
578
|
-
log.
|
|
547
|
+
log.debug('OCSPRequest already listening');
|
|
579
548
|
return;
|
|
580
549
|
}
|
|
581
550
|
|
|
582
|
-
log.
|
|
551
|
+
log.debug('adding OCSPRequest listener');
|
|
583
552
|
server.on('OCSPRequest', (cert, issuer, ocr_cb) => {
|
|
584
|
-
log.
|
|
585
|
-
ocsp.getOCSPURI(cert, (err, uri) => {
|
|
586
|
-
log.
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
567
|
+
const options = {
|
|
568
|
+
url: uri,
|
|
569
|
+
ocsp: req.data
|
|
570
|
+
};
|
|
605
571
|
|
|
606
|
-
|
|
607
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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 (
|
|
699
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
718
|
+
log.debug('client TLS upgrade in progress, awaiting secured.');
|
|
769
719
|
}
|
|
770
720
|
|
|
771
721
|
return socket;
|