haraka 0.0.33 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +7 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1898 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/LICENSE +22 -0
- package/Plugins.md +227 -0
- package/README.md +119 -4
- package/SECURITY.md +178 -0
- package/TODO +22 -0
- package/bin/haraka +593 -0
- package/bin/haraka_grep +32 -0
- package/config/aliases +2 -0
- package/config/auth_flat_file.ini +7 -0
- package/config/auth_vpopmaild.ini +9 -0
- package/config/connection.ini +79 -0
- package/config/delay_deny.ini +7 -0
- package/config/host_list +3 -0
- package/config/host_list_regex +6 -0
- package/config/http.ini +11 -0
- package/config/lmtp.ini +7 -0
- package/config/log.ini +11 -0
- package/config/outbound.bounce_message +18 -0
- package/config/outbound.bounce_message_html +36 -0
- package/config/outbound.bounce_message_image +106 -0
- package/config/outbound.ini +24 -0
- package/config/plugins +67 -0
- package/config/smtp.ini +37 -0
- package/config/smtp_bridge.ini +4 -0
- package/config/smtp_forward.ini +31 -0
- package/config/smtp_proxy.ini +27 -0
- package/config/tarpit.timeout +1 -0
- package/config/tls.ini +83 -0
- package/config/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1865 -0
- package/contrib/Haraka.cf +6 -0
- package/contrib/Haraka.pm +35 -0
- package/contrib/bad_smtp_server.pl +25 -0
- package/contrib/bsd-rc.d/haraka +63 -0
- package/contrib/debian-init.d/haraka +87 -0
- package/contrib/haraka.init +96 -0
- package/contrib/haraka.service +23 -0
- package/contrib/plugin2npm.sh +81 -0
- package/contrib/ubuntu-upstart/haraka.conf +27 -0
- package/docs/Body.md +1 -0
- package/docs/Config.md +1 -0
- package/docs/Connection.md +153 -0
- package/docs/CoreConfig.md +96 -0
- package/docs/CustomReturnCodes.md +3 -0
- package/docs/HAProxy.md +62 -0
- package/docs/Header.md +1 -0
- package/docs/Logging.md +129 -0
- package/docs/Outbound.md +210 -0
- package/docs/Plugins.md +372 -0
- package/docs/Results.md +7 -0
- package/docs/Transaction.md +135 -0
- package/docs/Tutorial.md +183 -0
- package/docs/deprecated/access.md +3 -0
- package/docs/deprecated/backscatterer.md +9 -0
- package/docs/deprecated/connect.rdns_access.md +53 -0
- package/docs/deprecated/data.headers.md +3 -0
- package/docs/deprecated/data.nomsgid.md +7 -0
- package/docs/deprecated/data.noreceived.md +11 -0
- package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
- package/docs/deprecated/dkim_sign.md +97 -0
- package/docs/deprecated/dkim_verify.md +28 -0
- package/docs/deprecated/dnsbl.md +80 -0
- package/docs/deprecated/dnswl.md +73 -0
- package/docs/deprecated/lookup_rdns.strict.md +67 -0
- package/docs/deprecated/mail_from.access.md +52 -0
- package/docs/deprecated/mail_from.blocklist.md +18 -0
- package/docs/deprecated/mail_from.nobounces.md +8 -0
- package/docs/deprecated/rcpt_to.access.md +53 -0
- package/docs/deprecated/rcpt_to.blocklist.md +18 -0
- package/docs/deprecated/rcpt_to.routes.md +3 -0
- package/docs/deprecated/rdns.regexp.md +30 -0
- package/docs/plugins/aliases.md +3 -0
- package/docs/plugins/auth/auth_bridge.md +34 -0
- package/docs/plugins/auth/auth_ldap.md +4 -0
- package/docs/plugins/auth/auth_proxy.md +36 -0
- package/docs/plugins/auth/auth_vpopmaild.md +33 -0
- package/docs/plugins/auth/flat_file.md +40 -0
- package/docs/plugins/block_me.md +18 -0
- package/docs/plugins/data.signatures.md +11 -0
- package/docs/plugins/delay_deny.md +23 -0
- package/docs/plugins/max_unrecognized_commands.md +6 -0
- package/docs/plugins/prevent_credential_leaks.md +22 -0
- package/docs/plugins/process_title.md +42 -0
- package/docs/plugins/queue/deliver.md +3 -0
- package/docs/plugins/queue/discard.md +32 -0
- package/docs/plugins/queue/lmtp.md +24 -0
- package/docs/plugins/queue/qmail-queue.md +16 -0
- package/docs/plugins/queue/quarantine.md +87 -0
- package/docs/plugins/queue/smtp_bridge.md +32 -0
- package/docs/plugins/queue/smtp_forward.md +127 -0
- package/docs/plugins/queue/smtp_proxy.md +68 -0
- package/docs/plugins/queue/test.md +7 -0
- package/docs/plugins/rcpt_to.in_host_list.md +34 -0
- package/docs/plugins/rcpt_to.max_count.md +3 -0
- package/docs/plugins/record_envelope_addresses.md +20 -0
- package/docs/plugins/relay.md +3 -0
- package/docs/plugins/reseed_rng.md +16 -0
- package/docs/plugins/status.md +41 -0
- package/docs/plugins/tarpit.md +50 -0
- package/docs/plugins/tls.md +235 -0
- package/docs/plugins/toobusy.md +27 -0
- package/docs/plugins/xclient.md +10 -0
- package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
- package/docs/tutorials/SettingUpOutbound.md +62 -0
- package/eslint.config.mjs +2 -0
- package/haraka.js +74 -0
- package/haraka.sh +2 -0
- package/http/html/404.html +58 -0
- package/http/html/index.html +47 -0
- package/http/package.json +21 -0
- package/line_socket.js +24 -0
- package/logger.js +322 -0
- package/outbound/client_pool.js +59 -0
- package/outbound/config.js +134 -0
- package/outbound/hmail.js +1504 -0
- package/outbound/index.js +349 -0
- package/outbound/qfile.js +93 -0
- package/outbound/queue.js +399 -0
- package/outbound/tls.js +85 -0
- package/outbound/todo.js +17 -0
- package/package.json +100 -4
- package/plugins/.eslintrc.yaml +3 -0
- package/plugins/auth/auth_base.js +261 -0
- package/plugins/auth/auth_bridge.js +20 -0
- package/plugins/auth/auth_proxy.js +227 -0
- package/plugins/auth/auth_vpopmaild.js +162 -0
- package/plugins/auth/flat_file.js +44 -0
- package/plugins/block_me.js +88 -0
- package/plugins/data.signatures.js +30 -0
- package/plugins/delay_deny.js +153 -0
- package/plugins/prevent_credential_leaks.js +61 -0
- package/plugins/process_title.js +197 -0
- package/plugins/profile.js +11 -0
- package/plugins/queue/deliver.js +12 -0
- package/plugins/queue/discard.js +27 -0
- package/plugins/queue/lmtp.js +45 -0
- package/plugins/queue/qmail-queue.js +93 -0
- package/plugins/queue/quarantine.js +133 -0
- package/plugins/queue/smtp_bridge.js +45 -0
- package/plugins/queue/smtp_forward.js +371 -0
- package/plugins/queue/smtp_proxy.js +142 -0
- package/plugins/queue/test.js +15 -0
- package/plugins/rcpt_to.host_list_base.js +65 -0
- package/plugins/rcpt_to.in_host_list.js +56 -0
- package/plugins/record_envelope_addresses.js +17 -0
- package/plugins/reseed_rng.js +7 -0
- package/plugins/status.js +274 -0
- package/plugins/tarpit.js +45 -0
- package/plugins/tls.js +164 -0
- package/plugins/toobusy.js +47 -0
- package/plugins/xclient.js +124 -0
- package/plugins.js +605 -0
- package/run_tests +11 -0
- package/server.js +827 -0
- package/smtp_client.js +504 -0
- package/test/.eslintrc.yaml +11 -0
- package/test/config/auth_flat_file.ini +5 -0
- package/test/config/block_me.recipient +1 -0
- package/test/config/block_me.senders +1 -0
- package/test/config/dhparams.pem +8 -0
- package/test/config/host_list +2 -0
- package/test/config/outbound_tls_cert.pem +1 -0
- package/test/config/outbound_tls_key.pem +1 -0
- package/test/config/plugins +7 -0
- package/test/config/smtp.ini +11 -0
- package/test/config/smtp_forward.ini +30 -0
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/config/tls/haraka.local.pem +51 -0
- package/test/config/tls.ini +45 -0
- package/test/config/tls_cert.pem +21 -0
- package/test/config/tls_key.pem +28 -0
- package/test/connection.js +820 -0
- package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
- package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
- package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
- package/test/fixtures/line_socket.js +21 -0
- package/test/fixtures/todo_qfile.txt +0 -0
- package/test/fixtures/util_hmailitem.js +156 -0
- package/test/installation/config/test-plugin-flat +1 -0
- package/test/installation/config/test-plugin.ini +10 -0
- package/test/installation/config/tls.ini +1 -0
- package/test/installation/node_modules/load_first/index.js +5 -0
- package/test/installation/node_modules/load_first/package.json +11 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
- package/test/installation/node_modules/test-plugin/package.json +5 -0
- package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
- package/test/installation/plugins/base_plugin.js +3 -0
- package/test/installation/plugins/folder_plugin/index.js +3 -0
- package/test/installation/plugins/folder_plugin/package.json +11 -0
- package/test/installation/plugins/inherits.js +7 -0
- package/test/installation/plugins/load_first.js +3 -0
- package/test/installation/plugins/plugin.js +1 -0
- package/test/installation/plugins/tls.js +3 -0
- package/test/logger.js +217 -0
- package/test/loud/config/dhparams.pem +0 -0
- package/test/loud/config/tls/goobered.pem +45 -0
- package/test/loud/config/tls.ini +43 -0
- package/test/mail_specimen/base64-root-part.txt +23 -0
- package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
- package/test/outbound/bounce_net_errors.js +133 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +210 -0
- package/test/outbound/index.js +385 -0
- package/test/outbound/qfile.js +124 -0
- package/test/outbound/queue.js +325 -0
- package/test/plugins/auth/auth_base.js +620 -0
- package/test/plugins/auth/auth_bridge.js +80 -0
- package/test/plugins/auth/auth_vpopmaild.js +81 -0
- package/test/plugins/auth/flat_file.js +123 -0
- package/test/plugins/block_me.js +141 -0
- package/test/plugins/data.signatures.js +111 -0
- package/test/plugins/delay_deny.js +262 -0
- package/test/plugins/prevent_credential_leaks.js +174 -0
- package/test/plugins/process_title.js +141 -0
- package/test/plugins/queue/deliver.js +98 -0
- package/test/plugins/queue/discard.js +78 -0
- package/test/plugins/queue/lmtp.js +137 -0
- package/test/plugins/queue/qmail-queue.js +98 -0
- package/test/plugins/queue/quarantine.js +80 -0
- package/test/plugins/queue/smtp_bridge.js +152 -0
- package/test/plugins/queue/smtp_forward.js +1023 -0
- package/test/plugins/queue/smtp_proxy.js +138 -0
- package/test/plugins/rcpt_to.host_list_base.js +102 -0
- package/test/plugins/rcpt_to.in_host_list.js +186 -0
- package/test/plugins/record_envelope_addresses.js +66 -0
- package/test/plugins/reseed_rng.js +34 -0
- package/test/plugins/status.js +207 -0
- package/test/plugins/tarpit.js +90 -0
- package/test/plugins/tls.js +86 -0
- package/test/plugins/toobusy.js +198 -0
- package/test/plugins/xclient.js +119 -0
- package/test/plugins.js +230 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- package/test/queue/zero-length +0 -0
- package/test/server.js +1012 -0
- package/test/smtp_client.js +1303 -0
- package/test/tls_socket.js +321 -0
- package/test/transaction.js +554 -0
- package/tls_socket.js +771 -0
- package/transaction.js +267 -0
package/tls_socket.js
ADDED
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
'use strict'
|
|
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')
|
|
10
|
+
|
|
11
|
+
// npm packages
|
|
12
|
+
exports.config = require('haraka-config') // exported for tests
|
|
13
|
+
const Notes = require('haraka-notes')
|
|
14
|
+
|
|
15
|
+
const log = require('./logger')
|
|
16
|
+
|
|
17
|
+
const certsByHost = new Notes()
|
|
18
|
+
const ctxByHost = {}
|
|
19
|
+
let ocsp
|
|
20
|
+
let ocspCache
|
|
21
|
+
|
|
22
|
+
// provides a common socket for attaching
|
|
23
|
+
// and detaching from either main socket, or crypto socket
|
|
24
|
+
class pluggableStream extends stream.Stream {
|
|
25
|
+
constructor(socket) {
|
|
26
|
+
super()
|
|
27
|
+
this.readable = this.writable = true
|
|
28
|
+
this._timeout = 0
|
|
29
|
+
this._keepalive = false
|
|
30
|
+
this._writeState = true
|
|
31
|
+
this._pending = []
|
|
32
|
+
this._pendingCallbacks = []
|
|
33
|
+
if (socket) this.attach(socket)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pause() {
|
|
37
|
+
if (this.targetsocket.pause) {
|
|
38
|
+
this.targetsocket.pause()
|
|
39
|
+
this.readable = false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
resume() {
|
|
44
|
+
if (this.targetsocket.resume) {
|
|
45
|
+
this.readable = true
|
|
46
|
+
this.targetsocket.resume()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
attach(socket) {
|
|
51
|
+
this.targetsocket = socket
|
|
52
|
+
this.targetsocket.on('data', (data) => {
|
|
53
|
+
this.emit('data', data)
|
|
54
|
+
})
|
|
55
|
+
this.targetsocket.on('connect', (a, b) => {
|
|
56
|
+
this.emit('connect', a, b)
|
|
57
|
+
})
|
|
58
|
+
this.targetsocket.on('secureConnect', (a, b) => {
|
|
59
|
+
this.emit('secureConnect', a, b)
|
|
60
|
+
this.emit('secure', a, b)
|
|
61
|
+
})
|
|
62
|
+
this.targetsocket.on('secure', (a, b) => {
|
|
63
|
+
this.emit('secure', a, b)
|
|
64
|
+
})
|
|
65
|
+
this.targetsocket.on('end', () => {
|
|
66
|
+
this.writable = this.targetsocket.writable
|
|
67
|
+
this.emit('end')
|
|
68
|
+
})
|
|
69
|
+
this.targetsocket.on('close', (had_error) => {
|
|
70
|
+
this.writable = this.targetsocket.writable
|
|
71
|
+
this.emit('close', had_error)
|
|
72
|
+
})
|
|
73
|
+
this.targetsocket.on('drain', () => {
|
|
74
|
+
this.emit('drain')
|
|
75
|
+
})
|
|
76
|
+
this.targetsocket.once('error', (exception) => {
|
|
77
|
+
this.writable = this.targetsocket.writable
|
|
78
|
+
exception.source = 'tls'
|
|
79
|
+
if (this.listenerCount('error') > 0) this.emit('error', exception)
|
|
80
|
+
})
|
|
81
|
+
this.targetsocket.on('timeout', () => {
|
|
82
|
+
this.emit('timeout')
|
|
83
|
+
})
|
|
84
|
+
if (this.targetsocket.remotePort) {
|
|
85
|
+
this.remotePort = this.targetsocket.remotePort
|
|
86
|
+
}
|
|
87
|
+
if (this.targetsocket.remoteAddress) {
|
|
88
|
+
this.remoteAddress = this.targetsocket.remoteAddress
|
|
89
|
+
}
|
|
90
|
+
if (this.targetsocket.localPort) {
|
|
91
|
+
this.localPort = this.targetsocket.localPort
|
|
92
|
+
}
|
|
93
|
+
if (this.targetsocket.localAddress) {
|
|
94
|
+
this.localAddress = this.targetsocket.localAddress
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
clean() {
|
|
99
|
+
if (this.targetsocket?.removeAllListeners) {
|
|
100
|
+
for (const name of ['data', 'secure', 'secureConnect', 'end', 'close', 'error', 'drain']) {
|
|
101
|
+
this.targetsocket.removeAllListeners(name)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
this.targetsocket = {}
|
|
105
|
+
this.targetsocket.write = () => {}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
write(data, encoding, callback) {
|
|
109
|
+
if (this.targetsocket.write) {
|
|
110
|
+
return this.targetsocket.write(data, encoding, callback)
|
|
111
|
+
}
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
end(data, encoding) {
|
|
116
|
+
if (this.targetsocket.end) {
|
|
117
|
+
return this.targetsocket.end(data, encoding)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
destroySoon() {
|
|
122
|
+
if (this.targetsocket.destroySoon) {
|
|
123
|
+
return this.targetsocket.destroySoon()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
destroy() {
|
|
128
|
+
if (this.targetsocket.destroy) {
|
|
129
|
+
return this.targetsocket.destroy()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setKeepAlive(bool) {
|
|
134
|
+
this._keepalive = bool
|
|
135
|
+
return this.targetsocket.setKeepAlive(bool)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setNoDelay(/* true||false */) {}
|
|
139
|
+
|
|
140
|
+
unref() {
|
|
141
|
+
return this.targetsocket.unref()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setTimeout(timeout) {
|
|
145
|
+
this._timeout = timeout
|
|
146
|
+
return this.targetsocket.setTimeout(timeout)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
isEncrypted() {
|
|
150
|
+
return this.targetsocket.encrypted
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
isSecure() {
|
|
154
|
+
return this.targetsocket.encrypted && this.targetsocket.authorized
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
exports.parse_x509 = async (string) => {
|
|
159
|
+
const res = {}
|
|
160
|
+
if (!string) return res
|
|
161
|
+
|
|
162
|
+
const keyRe = /([-]+BEGIN (?:\w+ )?PRIVATE KEY[-]+[^-]*[-]+END (?:\w+ )?PRIVATE KEY[-]+)/gm
|
|
163
|
+
res.keys = string.match(keyRe)
|
|
164
|
+
|
|
165
|
+
const certRe = /([-]+BEGIN CERTIFICATE[-]+[^-]*[-]+END CERTIFICATE[-]+)/gm
|
|
166
|
+
res.chain = string.match(certRe)
|
|
167
|
+
|
|
168
|
+
if (res.chain?.length) {
|
|
169
|
+
// it's cleaner to call openssl with each of -enddate, -subject, etc, but it costs
|
|
170
|
+
// 40-50ms per spawn with node v21 on a M1 MBP
|
|
171
|
+
const raw = await openssl(res.chain[0], 'x509', '-noout', '-enddate', '-subject', '-ext', 'subjectAltName')
|
|
172
|
+
if (!raw) return res
|
|
173
|
+
|
|
174
|
+
res.expire = new Date(raw.match(/notAfter=(.* [A-Z]{3})/)[1])
|
|
175
|
+
|
|
176
|
+
const match = /CN\s*=\s*([^/\s,]+)/.exec(raw)
|
|
177
|
+
if (match && match[1]) res.names = [match[1]]
|
|
178
|
+
|
|
179
|
+
for (let name of Array.from(raw.matchAll(/DNS:([^\s,]+)/gm), (m) => m[0])) {
|
|
180
|
+
name = name.replace('DNS:', '')
|
|
181
|
+
if (!res.names.includes(name)) res.names.push(name)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return res
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
exports.load_tls_ini = (opts) => {
|
|
189
|
+
log.info('loading tls.ini')
|
|
190
|
+
|
|
191
|
+
const cfg = exports.config.get(
|
|
192
|
+
'tls.ini',
|
|
193
|
+
{
|
|
194
|
+
booleans: [
|
|
195
|
+
'-redis.disable_for_failed_hosts',
|
|
196
|
+
|
|
197
|
+
// wildcards match in any section and are not initialized
|
|
198
|
+
'*.requestCert',
|
|
199
|
+
'*.rejectUnauthorized',
|
|
200
|
+
'*.honorCipherOrder',
|
|
201
|
+
'*.enableOCSPStapling',
|
|
202
|
+
'*.requestOCSP',
|
|
203
|
+
|
|
204
|
+
// explicitely declared booleans are initialized
|
|
205
|
+
'+main.requestCert',
|
|
206
|
+
'-main.rejectUnauthorized',
|
|
207
|
+
'+main.honorCipherOrder',
|
|
208
|
+
'-main.requestOCSP',
|
|
209
|
+
'-main.mutual_tls',
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
() => {
|
|
213
|
+
this.load_tls_ini()
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if (cfg.no_tls_hosts === undefined) cfg.no_tls_hosts = {}
|
|
218
|
+
if (cfg.mutual_auth_hosts === undefined) cfg.mutual_auth_hosts = {}
|
|
219
|
+
if (cfg.mutual_auth_hosts_exclude === undefined) cfg.mutual_auth_hosts_exclude = {}
|
|
220
|
+
|
|
221
|
+
if (cfg.main.enableOCSPStapling !== undefined) {
|
|
222
|
+
log.error('deprecated setting enableOCSPStapling in tls.ini')
|
|
223
|
+
cfg.main.requestOCSP = cfg.main.enableOCSPStapling
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (ocsp === undefined && cfg.main.requestOCSP) {
|
|
227
|
+
try {
|
|
228
|
+
ocsp = require('@haraka/ocsp')
|
|
229
|
+
log.debug('ocsp loaded')
|
|
230
|
+
ocspCache = new ocsp.Cache()
|
|
231
|
+
} catch (ignore) {
|
|
232
|
+
log.notice('OCSP Stapling not available.')
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (cfg.main.requireAuthorized === undefined) {
|
|
237
|
+
cfg.main.requireAuthorized = []
|
|
238
|
+
} else if (!Array.isArray(cfg.main.requireAuthorized)) {
|
|
239
|
+
cfg.main.requireAuthorized = [cfg.main.requireAuthorized]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!Array.isArray(cfg.main.no_starttls_ports)) cfg.main.no_starttls_ports = []
|
|
243
|
+
|
|
244
|
+
this.cfg = cfg
|
|
245
|
+
|
|
246
|
+
if (!opts || opts.role === 'server') {
|
|
247
|
+
this.applySocketOpts('*')
|
|
248
|
+
this.load_default_opts()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return cfg
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Build a client tls_options, merges a consumers own [tls] section
|
|
255
|
+
// over tls.ini [main].
|
|
256
|
+
exports.load_plugin_tls_options = (plugin_tls_cfg = {}) => {
|
|
257
|
+
const tls_cfg = exports.load_tls_ini({ role: 'client' })
|
|
258
|
+
const cfg = JSON.parse(JSON.stringify(plugin_tls_cfg))
|
|
259
|
+
|
|
260
|
+
// Inheritance from tls.ini [main] deliberately omits no_tls_hosts: the
|
|
261
|
+
// [main].no_tls_hosts list is documented as inbound-only; outbound and
|
|
262
|
+
// queue plugins should opt in explicitly via their own section.
|
|
263
|
+
const inheritable_opts = [
|
|
264
|
+
'key',
|
|
265
|
+
'cert',
|
|
266
|
+
'ciphers',
|
|
267
|
+
'minVersion',
|
|
268
|
+
'dhparam',
|
|
269
|
+
'requestCert',
|
|
270
|
+
'honorCipherOrder',
|
|
271
|
+
'rejectUnauthorized',
|
|
272
|
+
'force_tls_hosts',
|
|
273
|
+
]
|
|
274
|
+
for (const opt of inheritable_opts) {
|
|
275
|
+
if (cfg[opt] !== undefined) continue // set in plugin [tls]
|
|
276
|
+
if (tls_cfg.main[opt] === undefined) continue // unset in tls.ini [main]
|
|
277
|
+
cfg[opt] = tls_cfg.main[opt]
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Resolve key/cert/dhparam file references to buffers. Drop empty results
|
|
281
|
+
// so we never pass null to tls.connect.
|
|
282
|
+
for (const k of ['key', 'cert', 'dhparam']) {
|
|
283
|
+
if (!cfg[k]) {
|
|
284
|
+
delete cfg[k]
|
|
285
|
+
continue
|
|
286
|
+
}
|
|
287
|
+
const ref = Array.isArray(cfg[k]) ? cfg[k][0] : cfg[k]
|
|
288
|
+
const bin = exports.config.get(ref, 'binary')
|
|
289
|
+
if (bin) cfg[k] = bin
|
|
290
|
+
else delete cfg[k]
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (const k of ['no_tls_hosts', 'force_tls_hosts']) {
|
|
294
|
+
if (!cfg[k]) {
|
|
295
|
+
cfg[k] = []
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
if (!Array.isArray(cfg[k])) cfg[k] = [cfg[k]]
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return cfg
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
exports.applySocketOpts = (name) => {
|
|
305
|
+
// https://nodejs.org/api/tls.html#tls_new_tls_tlssocket_socket_options
|
|
306
|
+
const TLSSocketOptions = [
|
|
307
|
+
// 'server' // manually added
|
|
308
|
+
'isServer',
|
|
309
|
+
'requestCert',
|
|
310
|
+
'rejectUnauthorized',
|
|
311
|
+
'NPNProtocols',
|
|
312
|
+
'ALPNProtocols',
|
|
313
|
+
'session',
|
|
314
|
+
'requestOCSP',
|
|
315
|
+
'secureContext',
|
|
316
|
+
'SNICallback',
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
// https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
|
|
320
|
+
const createSecureContextOptions = [
|
|
321
|
+
'key',
|
|
322
|
+
'cert',
|
|
323
|
+
'dhparam',
|
|
324
|
+
'pfx',
|
|
325
|
+
'passphrase',
|
|
326
|
+
'ca',
|
|
327
|
+
'crl',
|
|
328
|
+
'ciphers',
|
|
329
|
+
'minVersion',
|
|
330
|
+
'honorCipherOrder',
|
|
331
|
+
'ecdhCurve',
|
|
332
|
+
'secureProtocol',
|
|
333
|
+
'secureOptions',
|
|
334
|
+
'sessionIdContext',
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
for (const opt of [...TLSSocketOptions, ...createSecureContextOptions]) {
|
|
338
|
+
if (this.cfg[name] && this.cfg[name][opt] !== undefined) {
|
|
339
|
+
// if the setting exists in tls.ini [name]
|
|
340
|
+
certsByHost.set([name, opt], this.cfg[name][opt])
|
|
341
|
+
} else if (this.cfg.main[opt] !== undefined) {
|
|
342
|
+
// save settings in tls.ini [main] to each CN
|
|
343
|
+
certsByHost.set([name, opt], this.cfg.main[opt])
|
|
344
|
+
} else {
|
|
345
|
+
// defaults
|
|
346
|
+
switch (opt) {
|
|
347
|
+
case 'sessionIdContext':
|
|
348
|
+
certsByHost.set([name, opt], 'haraka')
|
|
349
|
+
break
|
|
350
|
+
case 'isServer':
|
|
351
|
+
certsByHost.set([name, opt], true)
|
|
352
|
+
break
|
|
353
|
+
case 'key':
|
|
354
|
+
certsByHost.set([name, opt], 'tls_key.pem')
|
|
355
|
+
break
|
|
356
|
+
case 'cert':
|
|
357
|
+
certsByHost.set([name, opt], 'tls_cert.pem')
|
|
358
|
+
break
|
|
359
|
+
case 'dhparam':
|
|
360
|
+
certsByHost.set([name, opt], 'dhparams.pem')
|
|
361
|
+
break
|
|
362
|
+
case 'SNICallback':
|
|
363
|
+
certsByHost.set([name, opt], exports.SNICallback)
|
|
364
|
+
break
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
exports.load_default_opts = () => {
|
|
371
|
+
const cfg = certsByHost['*']
|
|
372
|
+
|
|
373
|
+
if (cfg.dhparam && typeof cfg.dhparam === 'string') {
|
|
374
|
+
log.debug(`loading dhparams from ${cfg.dhparam}`)
|
|
375
|
+
certsByHost.set('*.dhparam', this.config.get(cfg.dhparam, 'binary'))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (cfg.ca && typeof cfg.ca === 'string') {
|
|
379
|
+
log.info(`loading CA certs from ${cfg.ca}`)
|
|
380
|
+
certsByHost.set('*.ca', this.config.get(cfg.ca, 'binary'))
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// make non-array key/cert option into Arrays with one entry
|
|
384
|
+
if (!Array.isArray(cfg.key)) cfg.key = [cfg.key]
|
|
385
|
+
if (!Array.isArray(cfg.cert)) cfg.cert = [cfg.cert]
|
|
386
|
+
|
|
387
|
+
if (cfg.key.length !== cfg.cert.length) {
|
|
388
|
+
log.error(`number of keys (${cfg.key.length}) not equal to certs (${cfg.cert.length}).`)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// if key file has already been loaded, it'll be a Buffer.
|
|
392
|
+
if (typeof cfg.key[0] === 'string') {
|
|
393
|
+
// turn key/cert file names into actual key/cert binary data
|
|
394
|
+
const asArray = cfg.key.map((keyFileName) => {
|
|
395
|
+
if (!keyFileName) return
|
|
396
|
+
const key = this.config.get(keyFileName, 'binary')
|
|
397
|
+
if (!key) {
|
|
398
|
+
log.error(`tls key ${path.join(this.config.root_path, keyFileName)} could not be loaded.`)
|
|
399
|
+
}
|
|
400
|
+
return key
|
|
401
|
+
})
|
|
402
|
+
certsByHost.set('*.key', asArray)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (typeof cfg.cert[0] === 'string') {
|
|
406
|
+
const asArray = cfg.cert.map((certFileName) => {
|
|
407
|
+
if (!certFileName) return
|
|
408
|
+
const cert = this.config.get(certFileName, 'binary')
|
|
409
|
+
if (!cert) {
|
|
410
|
+
log.error(`tls cert ${path.join(this.config.root_path, certFileName)} could not be loaded.`)
|
|
411
|
+
}
|
|
412
|
+
return cert
|
|
413
|
+
})
|
|
414
|
+
certsByHost.set('*.cert', asArray)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (cfg.cert[0] && cfg.key[0]) {
|
|
418
|
+
this.tls_valid = true
|
|
419
|
+
|
|
420
|
+
// now that all opts are applied, generate TLS context
|
|
421
|
+
this.ensureDhparams(() => {
|
|
422
|
+
ctxByHost['*'] = tls.createSecureContext(cfg)
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
exports.SNICallback = function (servername, sniDone) {
|
|
428
|
+
log.debug(`SNI servername: ${servername}`)
|
|
429
|
+
|
|
430
|
+
sniDone(null, ctxByHost[servername] || ctxByHost['*'])
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
exports.get_certs_dir = async (tlsDir) => {
|
|
434
|
+
const r = {}
|
|
435
|
+
const watcher = async () => {
|
|
436
|
+
exports.get_certs_dir(tlsDir)
|
|
437
|
+
}
|
|
438
|
+
const dirOpts = { type: 'binary', watchCb: watcher }
|
|
439
|
+
|
|
440
|
+
const files = await this.config.getDir(tlsDir, dirOpts)
|
|
441
|
+
for (const file of files) {
|
|
442
|
+
try {
|
|
443
|
+
r[file.path] = await exports.parse_x509(file.data.toString())
|
|
444
|
+
} catch (err) {
|
|
445
|
+
log.debug(err.message)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
log.debug(`found ${Object.keys(r).length} files in config/tls`)
|
|
450
|
+
if (Object.keys(r).length === 0) return
|
|
451
|
+
|
|
452
|
+
const s = {} // certs by name (CN)
|
|
453
|
+
|
|
454
|
+
for (const fp in r) {
|
|
455
|
+
if (r[fp].expire && r[fp].expire < new Date()) {
|
|
456
|
+
log.error(`${fp} expired on ${r[fp].expire}`)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// a file with a key and no cert, get name from file
|
|
460
|
+
if (!r[fp].names) r[fp].names = [path.parse(fp).name]
|
|
461
|
+
|
|
462
|
+
for (let name of r[fp].names) {
|
|
463
|
+
if (name[0] === '_') name = name.replace('_', '*') // windows
|
|
464
|
+
if (s[name] === undefined) s[name] = {}
|
|
465
|
+
if (!s[name].key && r[fp].keys) s[name].key = r[fp].keys[0]
|
|
466
|
+
if (!s[name].cert && r[fp].chain) {
|
|
467
|
+
s[name].cert = r[fp].chain[0]
|
|
468
|
+
s[name].file = fp
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
for (const cn in s) {
|
|
474
|
+
if (!s[cn].cert || !s[cn].key) {
|
|
475
|
+
delete s[cn]
|
|
476
|
+
continue
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
this.applySocketOpts(cn) // from tls.ini
|
|
480
|
+
certsByHost.set([cn, 'cert'], Buffer.from(s[cn].cert))
|
|
481
|
+
certsByHost.set([cn, 'key'], Buffer.from(s[cn].key))
|
|
482
|
+
certsByHost.set([cn, 'dhparam'], certsByHost['*'].dhparam, true)
|
|
483
|
+
|
|
484
|
+
// all opts are applied, generate TLS context
|
|
485
|
+
try {
|
|
486
|
+
ctxByHost[cn] = tls.createSecureContext(certsByHost.get([cn]))
|
|
487
|
+
} catch (err) {
|
|
488
|
+
log.error(`CN '${cn}' loading got: ${err.message}`)
|
|
489
|
+
delete ctxByHost[cn]
|
|
490
|
+
delete certsByHost[cn]
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
log.info(`found ${Object.keys(s).length} TLS certs in config/tls`)
|
|
495
|
+
|
|
496
|
+
return certsByHost // used only by tests
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function openssl(crt, ...params) {
|
|
500
|
+
return new Promise((resolve) => {
|
|
501
|
+
let crtTxt = ''
|
|
502
|
+
let errTxt = ''
|
|
503
|
+
|
|
504
|
+
const o = spawn('openssl', params, { timeout: 2000 })
|
|
505
|
+
o.stdout.on('data', (data) => {
|
|
506
|
+
crtTxt += data
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
o.stderr.on('data', (data) => {
|
|
510
|
+
errTxt += data
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
o.on('close', (code) => {
|
|
514
|
+
if (code !== 0) {
|
|
515
|
+
log.error(`openssl ${params.join(' ')} failed with code ${code}: ${errTxt.trim()}`)
|
|
516
|
+
}
|
|
517
|
+
resolve(crtTxt)
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
o.stdin.write(crt)
|
|
521
|
+
o.stdin.write('\n')
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
exports.getSocketOpts = async (name) => {
|
|
526
|
+
// startup time, load the config/tls dir
|
|
527
|
+
if (!certsByHost['*']) this.load_tls_ini()
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
await this.get_certs_dir('tls')
|
|
531
|
+
} catch (err) {
|
|
532
|
+
if (err.code !== 'ENOENT') {
|
|
533
|
+
log.error(err.message)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return certsByHost[name] || certsByHost['*']
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function pipe(cleartext, socket) {
|
|
541
|
+
cleartext.socket = socket
|
|
542
|
+
|
|
543
|
+
function onError() {}
|
|
544
|
+
|
|
545
|
+
function onClose() {
|
|
546
|
+
socket.removeListener('error', onError)
|
|
547
|
+
socket.removeListener('close', onClose)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
socket.on('error', onError)
|
|
551
|
+
socket.on('close', onClose)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
exports.ensureDhparams = (done) => {
|
|
555
|
+
// empty/missing dhparams file
|
|
556
|
+
if (certsByHost['*'].dhparam) {
|
|
557
|
+
return done(null, certsByHost['*'].dhparam)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (cluster.isWorker) return // only once, on the master process
|
|
561
|
+
|
|
562
|
+
const filePath = this.cfg.main.dhparam || 'dhparams.pem'
|
|
563
|
+
const fpResolved = path.resolve(exports.config.root_path, filePath)
|
|
564
|
+
|
|
565
|
+
log.info(`Generating a 2048 bit dhparams file at ${fpResolved}`)
|
|
566
|
+
|
|
567
|
+
const o = spawn('openssl', ['dhparam', '-out', fpResolved, '2048'], { timeout: 30000 })
|
|
568
|
+
o.stdout.on('data', (data) => {
|
|
569
|
+
// normally empty output
|
|
570
|
+
log.debug(data)
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
o.stderr.on('data', () => {
|
|
574
|
+
// this is the status gibberish `openssl dhparam` spews as it works
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
o.on('close', (code) => {
|
|
578
|
+
if (code !== 0) {
|
|
579
|
+
return done(`Error code: ${code}`)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
log.info(`Saved to ${fpResolved}`)
|
|
583
|
+
const content = this.config.get(filePath, 'binary')
|
|
584
|
+
|
|
585
|
+
certsByHost.set('*.dhparam', content)
|
|
586
|
+
done(null, certsByHost['*'].dhparam)
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
exports.addOCSP = (server) => {
|
|
591
|
+
if (!ocsp) {
|
|
592
|
+
log.debug(`addOCSP: 'ocsp' not available`)
|
|
593
|
+
return
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (server.listenerCount('OCSPRequest') > 0) {
|
|
597
|
+
log.debug('OCSPRequest already listening')
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
log.debug('adding OCSPRequest listener')
|
|
602
|
+
server.on('OCSPRequest', (cert, issuer, ocr_cb) => {
|
|
603
|
+
log.debug(`OCSPRequest: ${cert}`)
|
|
604
|
+
ocsp.getOCSPURI(cert, async (err, uri) => {
|
|
605
|
+
log.debug(`OCSP Request, URI: ${uri}, err=${err}`)
|
|
606
|
+
if (err) return ocr_cb(err)
|
|
607
|
+
if (uri === null) return ocr_cb() // not working OCSP server
|
|
608
|
+
|
|
609
|
+
const req = ocsp.request.generate(cert, issuer)
|
|
610
|
+
const cached = await ocspCache.probe(req.id)
|
|
611
|
+
|
|
612
|
+
if (cached) {
|
|
613
|
+
log.debug(`OCSP cache: ${util.inspect(cached)}`)
|
|
614
|
+
return ocr_cb(null, cached.response)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const options = {
|
|
618
|
+
url: uri,
|
|
619
|
+
ocsp: req.data,
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
log.debug(`OCSP req:${util.inspect(req)}`)
|
|
623
|
+
ocspCache.request(req.id, options, ocr_cb)
|
|
624
|
+
})
|
|
625
|
+
})
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
exports.shutdown = () => {
|
|
629
|
+
if (ocsp) cleanOcspCache()
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function cleanOcspCache() {
|
|
633
|
+
log.debug(`Cleaning ocspCache. How many keys? ${Object.keys(ocspCache.cache).length}`)
|
|
634
|
+
for (const key of Object.keys(ocspCache.cache)) {
|
|
635
|
+
clearTimeout(ocspCache.cache[key].timer)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
exports.certsByHost = certsByHost
|
|
640
|
+
exports.ocsp = ocsp
|
|
641
|
+
|
|
642
|
+
exports.get_rejectUnauthorized = (rejectUnauthorized, port, port_list) => {
|
|
643
|
+
// console.log(`rejectUnauthorized: ${rejectUnauthorized}, port ${port}, list: ${port_list}`)
|
|
644
|
+
|
|
645
|
+
if (rejectUnauthorized) return true
|
|
646
|
+
|
|
647
|
+
return !!port_list.includes(port)
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function createServer(cb) {
|
|
651
|
+
const server = net.createServer((cryptoSocket) => {
|
|
652
|
+
const socket = new pluggableStream(cryptoSocket)
|
|
653
|
+
|
|
654
|
+
exports.addOCSP(server)
|
|
655
|
+
|
|
656
|
+
socket.upgrade = (cb2) => {
|
|
657
|
+
log.debug('Upgrading to TLS')
|
|
658
|
+
|
|
659
|
+
socket.clean()
|
|
660
|
+
|
|
661
|
+
cryptoSocket.removeAllListeners('data')
|
|
662
|
+
|
|
663
|
+
const options = { ...certsByHost['*'] }
|
|
664
|
+
options.server = server // TLSSocket needs server for SNI to work
|
|
665
|
+
|
|
666
|
+
options.rejectUnauthorized = exports.get_rejectUnauthorized(
|
|
667
|
+
options.rejectUnauthorized,
|
|
668
|
+
cryptoSocket.localPort,
|
|
669
|
+
exports.cfg.main.requireAuthorized,
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
const cleartext = new tls.TLSSocket(cryptoSocket, options)
|
|
673
|
+
|
|
674
|
+
pipe(cleartext, cryptoSocket)
|
|
675
|
+
|
|
676
|
+
cleartext
|
|
677
|
+
.on('error', (exception) => {
|
|
678
|
+
exception.source = 'tls'
|
|
679
|
+
socket.emit('error', exception)
|
|
680
|
+
})
|
|
681
|
+
.on('secure', () => {
|
|
682
|
+
log.debug('TLS secured.')
|
|
683
|
+
socket.emit('secure')
|
|
684
|
+
const cipher = cleartext.getCipher()
|
|
685
|
+
cipher.version = cleartext.getProtocol()
|
|
686
|
+
if (cb2)
|
|
687
|
+
cb2(cleartext.authorized, cleartext.authorizationError, cleartext.getPeerCertificate(), cipher)
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
socket.cleartext = cleartext
|
|
691
|
+
|
|
692
|
+
if (socket._timeout) {
|
|
693
|
+
cleartext.setTimeout(socket._timeout)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
cleartext.setKeepAlive(socket._keepalive)
|
|
697
|
+
|
|
698
|
+
socket.attach(socket.cleartext)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
cb(socket)
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
return server
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function getCertFor(host) {
|
|
708
|
+
if (host && certsByHost[host]) return certsByHost[host]
|
|
709
|
+
return certsByHost['*'] // the default TLS cert
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function connect(conn_options = {}) {
|
|
713
|
+
// called by outbound/client_pool, smtp_client, plugins/spamassassin,avg,clamd,
|
|
714
|
+
// plugins/auth/auth_proxy
|
|
715
|
+
|
|
716
|
+
const cryptoSocket = net.connect(conn_options)
|
|
717
|
+
const socket = new pluggableStream(cryptoSocket)
|
|
718
|
+
|
|
719
|
+
socket.upgrade = (options, cb2) => {
|
|
720
|
+
socket.clean()
|
|
721
|
+
cryptoSocket.removeAllListeners('data')
|
|
722
|
+
|
|
723
|
+
if (exports.tls_valid) {
|
|
724
|
+
const host = conn_options.host
|
|
725
|
+
if (exports.cfg === undefined) exports.load_tls_ini()
|
|
726
|
+
if (exports.cfg.mutual_auth_hosts[host]) {
|
|
727
|
+
options = { ...options, ...getCertFor(exports.cfg.mutual_auth_hosts[host]) }
|
|
728
|
+
} else if (exports.cfg.mutual_auth_hosts_exclude[host]) {
|
|
729
|
+
// send no client cert
|
|
730
|
+
} else if (exports.cfg.main.mutual_tls) {
|
|
731
|
+
options = { ...options, ...getCertFor(host) }
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
options.socket = cryptoSocket
|
|
735
|
+
|
|
736
|
+
const cleartext = tls.connect(options)
|
|
737
|
+
|
|
738
|
+
pipe(cleartext, cryptoSocket)
|
|
739
|
+
|
|
740
|
+
cleartext.on('error', (err) => {
|
|
741
|
+
err.source = 'tls'
|
|
742
|
+
socket.emit('error', err)
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
cleartext.once('secureConnect', () => {
|
|
746
|
+
log.debug('client TLS secured.')
|
|
747
|
+
const cipher = cleartext.getCipher()
|
|
748
|
+
cipher.version = cleartext.getProtocol()
|
|
749
|
+
if (cb2) cb2(cleartext.authorized, cleartext.authorizationError, cleartext.getPeerCertificate(), cipher)
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
socket.cleartext = cleartext
|
|
753
|
+
|
|
754
|
+
if (socket._timeout) {
|
|
755
|
+
cleartext.setTimeout(socket._timeout)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
cleartext.setKeepAlive(socket._keepalive)
|
|
759
|
+
|
|
760
|
+
socket.attach(socket.cleartext)
|
|
761
|
+
|
|
762
|
+
log.debug('client TLS upgrade in progress, awaiting secured.')
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return socket
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
exports.connect = connect
|
|
769
|
+
exports.createConnection = connect
|
|
770
|
+
exports.Server = createServer
|
|
771
|
+
exports.createServer = createServer
|