haraka 0.0.33 → 3.3.0
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/.claude/settings.local.json +28 -0
- package/.githooks/pre-commit +41 -0
- package/.prettierignore +6 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1894 -0
- package/CLAUDE.md +40 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/GEMINI.md +38 -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/address.js +53 -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/dhparams.pem +8 -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/me +1 -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/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/config/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1863 -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 +61 -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/coverage/coverage-final.json +2 -0
- package/coverage/coverage-summary.json +33 -0
- package/coverage/tmp/coverage-79131-1779241025146-0.json +1 -0
- package/coverage/tmp/coverage-79132-1779240999690-0.json +1 -0
- package/coverage/tmp/coverage-79172-1779241000095-0.json +1 -0
- package/coverage/tmp/coverage-79210-1779241000156-0.json +1 -0
- package/coverage/tmp/coverage-79211-1779241000209-0.json +1 -0
- package/coverage/tmp/coverage-79212-1779241000266-0.json +1 -0
- package/coverage/tmp/coverage-79213-1779241000441-0.json +1 -0
- package/coverage/tmp/coverage-79214-1779241000626-0.json +1 -0
- package/coverage/tmp/coverage-79215-1779241000795-0.json +1 -0
- package/coverage/tmp/coverage-79216-1779241000965-0.json +1 -0
- package/coverage/tmp/coverage-79218-1779241001013-0.json +1 -0
- package/coverage/tmp/coverage-79219-1779241001179-0.json +1 -0
- package/coverage/tmp/coverage-79220-1779241006249-0.json +1 -0
- package/coverage/tmp/coverage-79227-1779241011453-0.json +1 -0
- package/coverage/tmp/coverage-79229-1779241011537-0.json +1 -0
- package/coverage/tmp/coverage-79230-1779241011647-0.json +1 -0
- package/coverage/tmp/coverage-79231-1779241011765-0.json +1 -0
- package/coverage/tmp/coverage-79232-1779241011841-0.json +1 -0
- package/coverage/tmp/coverage-79233-1779241011909-0.json +1 -0
- package/coverage/tmp/coverage-79234-1779241011984-0.json +1 -0
- package/coverage/tmp/coverage-79235-1779241012055-0.json +1 -0
- package/coverage/tmp/coverage-79236-1779241012230-0.json +1 -0
- package/coverage/tmp/coverage-79237-1779241012300-0.json +1 -0
- package/coverage/tmp/coverage-79238-1779241012368-0.json +1 -0
- package/coverage/tmp/coverage-79239-1779241012438-0.json +1 -0
- package/coverage/tmp/coverage-79240-1779241012511-0.json +1 -0
- package/coverage/tmp/coverage-79241-1779241012582-0.json +1 -0
- package/coverage/tmp/coverage-79242-1779241012652-0.json +1 -0
- package/coverage/tmp/coverage-79243-1779241012814-0.json +1 -0
- package/coverage/tmp/coverage-79244-1779241012931-0.json +1 -0
- package/coverage/tmp/coverage-79245-1779241013007-0.json +1 -0
- package/coverage/tmp/coverage-79246-1779241013106-0.json +1 -0
- package/coverage/tmp/coverage-79247-1779241013178-0.json +1 -0
- package/coverage/tmp/coverage-79248-1779241013244-0.json +1 -0
- package/coverage/tmp/coverage-79249-1779241013409-0.json +1 -0
- package/coverage/tmp/coverage-79250-1779241013697-0.json +1 -0
- package/coverage/tmp/coverage-79251-1779241013847-0.json +1 -0
- package/coverage/tmp/coverage-79252-1779241014288-0.json +1 -0
- package/coverage/tmp/coverage-79253-1779241014378-0.json +1 -0
- package/coverage/tmp/coverage-79254-1779241014428-0.json +1 -0
- package/coverage/tmp/coverage-79255-1779241021774-0.json +1 -0
- package/coverage/tmp/coverage-80382-1779241021949-0.json +1 -0
- package/coverage/tmp/coverage-80383-1779241025019-0.json +1 -0
- package/coverage/tmp/coverage-80384-1779241025133-0.json +1 -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 +99 -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 +604 -0
- package/queue/1772642154987_1775581346001_4_82235_TGwgfd_2_mattbook-m3.home.simerson.net +0 -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 +817 -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 +21 -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
|