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