• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

haraka / Haraka / 25021562108

27 Apr 2026 09:52PM UTC coverage: 72.481% (+3.8%) from 68.637%
25021562108

push

github

web-flow
outbound: yield before delivery attempts (#3552)

- dep(ocsp): replaced with local updated fork

1528 of 2027 branches covered (75.38%)

20 of 21 new or added lines in 3 files covered. (95.24%)

5 existing lines in 1 file now uncovered.

6977 of 9626 relevant lines covered (72.48%)

23.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.8
/tls_socket.js
1
'use strict'
12✔
2

12✔
3
const cluster = require('node:cluster')
12✔
4
const net = require('node:net')
12✔
5
const path = require('node:path')
12✔
6
const { spawn } = require('node:child_process')
12✔
7
const stream = require('node:stream')
12✔
8
const tls = require('node:tls')
12✔
9
const util = require('node:util')
12✔
10

12✔
11
// npm packages
12✔
12
exports.config = require('haraka-config') // exported for tests
12✔
13
const Notes = require('haraka-notes')
12✔
14

12✔
15
const log = require('./logger')
12✔
16

12✔
17
const certsByHost = new Notes()
12✔
18
const ctxByHost = {}
12✔
19
let ocsp
12✔
20
let ocspCache
12✔
21

12✔
22
// provides a common socket for attaching
12✔
23
// and detaching from either main socket, or crypto socket
12✔
24
class pluggableStream extends stream.Stream {
12✔
25
    constructor(socket) {
12✔
26
        super()
11✔
27
        this.readable = this.writable = true
11✔
28
        this._timeout = 0
11✔
29
        this._keepalive = false
11✔
30
        this._writeState = true
11✔
31
        this._pending = []
11✔
32
        this._pendingCallbacks = []
11✔
33
        if (socket) this.attach(socket)
11✔
34
    }
11✔
35

12✔
36
    pause() {
12✔
37
        if (this.targetsocket.pause) {
6✔
38
            this.targetsocket.pause()
6✔
39
            this.readable = false
6✔
40
        }
6✔
41
    }
6✔
42

12✔
43
    resume() {
12✔
44
        if (this.targetsocket.resume) {
6✔
45
            this.readable = true
6✔
46
            this.targetsocket.resume()
6✔
47
        }
6✔
48
    }
6✔
49

12✔
50
    attach(socket) {
12✔
51
        this.targetsocket = socket
14✔
52
        this.targetsocket.on('data', (data) => {
14✔
53
            this.emit('data', data)
53✔
54
        })
14✔
55
        this.targetsocket.on('connect', (a, b) => {
14✔
56
            this.emit('connect', a, b)
6✔
57
        })
14✔
58
        this.targetsocket.on('secureConnect', (a, b) => {
14✔
59
            this.emit('secureConnect', a, b)
2✔
60
            this.emit('secure', a, b)
2✔
61
        })
14✔
62
        this.targetsocket.on('secure', (a, b) => {
14✔
63
            this.emit('secure', a, b)
1✔
64
        })
14✔
65
        this.targetsocket.on('end', () => {
14✔
66
            this.writable = this.targetsocket.writable
5✔
67
            this.emit('end')
5✔
68
        })
14✔
69
        this.targetsocket.on('close', (had_error) => {
14✔
70
            this.writable = this.targetsocket.writable
10✔
71
            this.emit('close', had_error)
10✔
72
        })
14✔
73
        this.targetsocket.on('drain', () => {
14✔
74
            this.emit('drain')
×
75
        })
14✔
76
        this.targetsocket.once('error', (exception) => {
14✔
77
            this.writable = this.targetsocket.writable
2✔
78
            exception.source = 'tls'
2✔
79
            this.emit('error', exception)
2✔
80
        })
14✔
81
        this.targetsocket.on('timeout', () => {
14✔
82
            this.emit('timeout')
×
83
        })
14✔
84
        if (this.targetsocket.remotePort) {
14✔
85
            this.remotePort = this.targetsocket.remotePort
7✔
86
        }
7✔
87
        if (this.targetsocket.remoteAddress) {
14✔
88
            this.remoteAddress = this.targetsocket.remoteAddress
7✔
89
        }
7✔
90
        if (this.targetsocket.localPort) {
14✔
91
            this.localPort = this.targetsocket.localPort
7✔
92
        }
7✔
93
        if (this.targetsocket.localAddress) {
14✔
94
            this.localAddress = this.targetsocket.localAddress
7✔
95
        }
7✔
96
    }
14✔
97

12✔
98
    clean(data) {
12✔
99
        if (this.targetsocket?.removeAllListeners) {
3✔
100
            for (const name of ['data', 'secure', 'secureConnect', 'end', 'close', 'error', 'drain']) {
3✔
101
                this.targetsocket.removeAllListeners(name)
21✔
102
            }
21✔
103
        }
3✔
104
        this.targetsocket = {}
3✔
105
        this.targetsocket.write = () => {}
3✔
106
    }
3✔
107

12✔
108
    write(data, encoding, callback) {
12✔
109
        if (this.targetsocket.write) {
66✔
110
            return this.targetsocket.write(data, encoding, callback)
66✔
111
        }
66✔
112
        return false
×
113
    }
66✔
114

12✔
115
    end(data, encoding) {
12✔
116
        if (this.targetsocket.end) {
5✔
117
            return this.targetsocket.end(data, encoding)
5✔
118
        }
5✔
119
    }
5✔
120

12✔
121
    destroySoon() {
12✔
122
        if (this.targetsocket.destroySoon) {
×
123
            return this.targetsocket.destroySoon()
×
124
        }
×
125
    }
×
126

12✔
127
    destroy() {
12✔
128
        if (this.targetsocket.destroy) {
4✔
129
            return this.targetsocket.destroy()
4✔
130
        }
4✔
131
    }
4✔
132

12✔
133
    setKeepAlive(bool) {
12✔
134
        this._keepalive = bool
5✔
135
        return this.targetsocket.setKeepAlive(bool)
5✔
136
    }
5✔
137

12✔
138
    setNoDelay(/* true||false */) {}
12✔
139

12✔
140
    unref() {
12✔
141
        return this.targetsocket.unref()
×
142
    }
×
143

12✔
144
    setTimeout(timeout) {
12✔
145
        this._timeout = timeout
14✔
146
        return this.targetsocket.setTimeout(timeout)
14✔
147
    }
14✔
148

12✔
149
    isEncrypted() {
12✔
150
        return this.targetsocket.encrypted
×
151
    }
×
152

12✔
153
    isSecure() {
12✔
154
        return this.targetsocket.encrypted && this.targetsocket.authorized
×
155
    }
×
156
}
12✔
157

12✔
158
exports.parse_x509 = async (string) => {
12✔
159
    const res = {}
50✔
160
    if (!string) return res
50✔
161

48✔
162
    const keyRe = /([-]+BEGIN (?:\w+ )?PRIVATE KEY[-]+[^-]*[-]+END (?:\w+ )?PRIVATE KEY[-]+)/gm
48✔
163
    res.keys = string.match(keyRe)
48✔
164

48✔
165
    const certRe = /([-]+BEGIN CERTIFICATE[-]+[^-]*[-]+END CERTIFICATE[-]+)/gm
48✔
166
    res.chain = string.match(certRe)
48✔
167

48✔
168
    if (res.chain?.length) {
50✔
169
        // it's cleaner to call openssl with each of -enddate, -subject, etc, but it costs
32✔
170
        // 40-50ms per spawn with node v21 on a M1 MBP
32✔
171
        const raw = await openssl(res.chain[0], 'x509', '-noout', '-enddate', '-subject', '-ext', 'subjectAltName')
32✔
172
        if (!raw) return res
32!
173

32✔
174
        res.expire = new Date(raw.match(/notAfter=(.* [A-Z]{3})/)[1])
32✔
175

32✔
176
        const match = /CN\s*=\s*([^/\s,]+)/.exec(raw)
32✔
177
        if (match && match[1]) res.names = [match[1]]
32✔
178

32✔
179
        for (let name of Array.from(raw.matchAll(/DNS:([^\s,]+)/gm), (m) => m[0])) {
32!
180
            name = name.replace('DNS:', '')
×
181
            if (!res.names.includes(name)) res.names.push(name)
×
182
        }
×
183
    }
32✔
184

48✔
185
    return res
48✔
186
}
50✔
187

12✔
188
exports.load_tls_ini = (opts) => {
12✔
189
    log.info('loading tls.ini')
10✔
190

10✔
191
    const cfg = exports.config.get(
10✔
192
        'tls.ini',
10✔
193
        {
10✔
194
            booleans: [
10✔
195
                '-redis.disable_for_failed_hosts',
10✔
196

10✔
197
                // wildcards match in any section and are not initialized
10✔
198
                '*.requestCert',
10✔
199
                '*.rejectUnauthorized',
10✔
200
                '*.honorCipherOrder',
10✔
201
                '*.enableOCSPStapling',
10✔
202
                '*.requestOCSP',
10✔
203

10✔
204
                // explicitely declared booleans are initialized
10✔
205
                '+main.requestCert',
10✔
206
                '-main.rejectUnauthorized',
10✔
207
                '+main.honorCipherOrder',
10✔
208
                '-main.requestOCSP',
10✔
209
                '-main.mutual_tls',
10✔
210
            ],
10✔
211
        },
10✔
212
        () => {
10✔
213
            this.load_tls_ini()
×
214
        },
10✔
215
    )
10✔
216

10✔
217
    if (cfg.no_tls_hosts === undefined) cfg.no_tls_hosts = {}
10!
218
    if (cfg.mutual_auth_hosts === undefined) cfg.mutual_auth_hosts = {}
10✔
219
    if (cfg.mutual_auth_hosts_exclude === undefined) cfg.mutual_auth_hosts_exclude = {}
10✔
220

10✔
221
    if (cfg.main.enableOCSPStapling !== undefined) {
10!
222
        log.error('deprecated setting enableOCSPStapling in tls.ini')
×
223
        cfg.main.requestOCSP = cfg.main.enableOCSPStapling
×
224
    }
×
225

10✔
226
    if (ocsp === undefined && cfg.main.requestOCSP) {
10!
227
        try {
×
NEW
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

10✔
236
    if (cfg.main.requireAuthorized === undefined) {
10✔
237
        cfg.main.requireAuthorized = []
2✔
238
    } else if (!Array.isArray(cfg.main.requireAuthorized)) {
10!
239
        cfg.main.requireAuthorized = [cfg.main.requireAuthorized]
×
240
    }
×
241

10✔
242
    if (!Array.isArray(cfg.main.no_starttls_ports)) cfg.main.no_starttls_ports = []
10✔
243

10✔
244
    this.cfg = cfg
10✔
245

10✔
246
    if (!opts || opts.role === 'server') {
10✔
247
        this.applySocketOpts('*')
9✔
248
        this.load_default_opts()
9✔
249
    }
9✔
250

10✔
251
    return cfg
10✔
252
}
10✔
253

12✔
254
exports.applySocketOpts = (name) => {
12✔
255
    // https://nodejs.org/api/tls.html#tls_new_tls_tlssocket_socket_options
41✔
256
    const TLSSocketOptions = [
41✔
257
        // 'server'        // manually added
41✔
258
        'isServer',
41✔
259
        'requestCert',
41✔
260
        'rejectUnauthorized',
41✔
261
        'NPNProtocols',
41✔
262
        'ALPNProtocols',
41✔
263
        'session',
41✔
264
        'requestOCSP',
41✔
265
        'secureContext',
41✔
266
        'SNICallback',
41✔
267
    ]
41✔
268

41✔
269
    // https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
41✔
270
    const createSecureContextOptions = [
41✔
271
        'key',
41✔
272
        'cert',
41✔
273
        'dhparam',
41✔
274
        'pfx',
41✔
275
        'passphrase',
41✔
276
        'ca',
41✔
277
        'crl',
41✔
278
        'ciphers',
41✔
279
        'minVersion',
41✔
280
        'honorCipherOrder',
41✔
281
        'ecdhCurve',
41✔
282
        'secureProtocol',
41✔
283
        'secureOptions',
41✔
284
        'sessionIdContext',
41✔
285
    ]
41✔
286

41✔
287
    for (const opt of [...TLSSocketOptions, ...createSecureContextOptions]) {
41✔
288
        if (this.cfg[name] && this.cfg[name][opt] !== undefined) {
943!
289
            // if the setting exists in tls.ini [name]
×
290
            certsByHost.set([name, opt], this.cfg[name][opt])
×
291
        } else if (this.cfg.main[opt] !== undefined) {
943✔
292
            // save settings in tls.ini [main] to each CN
359✔
293
            certsByHost.set([name, opt], this.cfg.main[opt])
359✔
294
        } else {
943✔
295
            // defaults
584✔
296
            switch (opt) {
584✔
297
                case 'sessionIdContext':
584✔
298
                    certsByHost.set([name, opt], 'haraka')
41✔
299
                    break
41✔
300
                case 'isServer':
584✔
301
                    certsByHost.set([name, opt], true)
41✔
302
                    break
41✔
303
                case 'key':
584✔
304
                    certsByHost.set([name, opt], 'tls_key.pem')
2✔
305
                    break
2✔
306
                case 'cert':
584✔
307
                    certsByHost.set([name, opt], 'tls_cert.pem')
2✔
308
                    break
2✔
309
                case 'dhparam':
584✔
310
                    certsByHost.set([name, opt], 'dhparams.pem')
2✔
311
                    break
2✔
312
                case 'SNICallback':
584✔
313
                    certsByHost.set([name, opt], exports.SNICallback)
41✔
314
                    break
41✔
315
            }
584✔
316
        }
584✔
317
    }
943✔
318
}
41✔
319

12✔
320
exports.load_default_opts = () => {
12✔
321
    const cfg = certsByHost['*']
9✔
322

9✔
323
    if (cfg.dhparam && typeof cfg.dhparam === 'string') {
9✔
324
        log.debug(`loading dhparams from ${cfg.dhparam}`)
9✔
325
        certsByHost.set('*.dhparam', this.config.get(cfg.dhparam, 'binary'))
9✔
326
    }
9✔
327

9✔
328
    if (cfg.ca && typeof cfg.ca === 'string') {
9!
329
        log.info(`loading CA certs from ${cfg.ca}`)
×
330
        certsByHost.set('*.ca', this.config.get(cfg.ca, 'binary'))
×
331
    }
×
332

9✔
333
    // make non-array key/cert option into Arrays with one entry
9✔
334
    if (!Array.isArray(cfg.key)) cfg.key = [cfg.key]
9✔
335
    if (!Array.isArray(cfg.cert)) cfg.cert = [cfg.cert]
9✔
336

9✔
337
    if (cfg.key.length !== cfg.cert.length) {
9!
338
        log.error(`number of keys (${cfg.key.length}) not equal to certs (${cfg.cert.length}).`)
×
339
    }
×
340

9✔
341
    // if key file has already been loaded, it'll be a Buffer.
9✔
342
    if (typeof cfg.key[0] === 'string') {
9✔
343
        // turn key/cert file names into actual key/cert binary data
9✔
344
        const asArray = cfg.key.map((keyFileName) => {
9✔
345
            if (!keyFileName) return
9!
346
            const key = this.config.get(keyFileName, 'binary')
9✔
347
            if (!key) {
9✔
348
                log.error(`tls key ${path.join(this.config.root_path, keyFileName)} could not be loaded.`)
2✔
349
            }
2✔
350
            return key
9✔
351
        })
9✔
352
        certsByHost.set('*.key', asArray)
9✔
353
    }
9✔
354

9✔
355
    if (typeof cfg.cert[0] === 'string') {
9✔
356
        const asArray = cfg.cert.map((certFileName) => {
9✔
357
            if (!certFileName) return
9!
358
            const cert = this.config.get(certFileName, 'binary')
9✔
359
            if (!cert) {
9✔
360
                log.error(`tls cert ${path.join(this.config.root_path, certFileName)} could not be loaded.`)
2✔
361
            }
2✔
362
            return cert
9✔
363
        })
9✔
364
        certsByHost.set('*.cert', asArray)
9✔
365
    }
9✔
366

9✔
367
    if (cfg.cert[0] && cfg.key[0]) {
9✔
368
        this.tls_valid = true
7✔
369

7✔
370
        // now that all opts are applied, generate TLS context
7✔
371
        this.ensureDhparams(() => {
7✔
372
            ctxByHost['*'] = tls.createSecureContext(cfg)
7✔
373
        })
7✔
374
    }
7✔
375
}
9✔
376

12✔
377
exports.SNICallback = function (servername, sniDone) {
12✔
378
    log.debug(`SNI servername: ${servername}`)
×
379

×
380
    sniDone(null, ctxByHost[servername] || ctxByHost['*'])
×
381
}
×
382

12✔
383
exports.get_certs_dir = async (tlsDir) => {
12✔
384
    const r = {}
16✔
385
    const watcher = async () => {
16✔
386
        exports.get_certs_dir(tlsDir)
×
387
    }
×
388
    const dirOpts = { type: 'binary', watchCb: watcher }
16✔
389

16✔
390
    const files = await this.config.getDir(tlsDir, dirOpts)
16✔
391
    for (const file of files) {
16✔
392
        try {
48✔
393
            r[file.path] = await exports.parse_x509(file.data.toString())
48✔
394
        } catch (err) {
48!
395
            log.debug(err.message)
×
396
        }
×
397
    }
48✔
398

16✔
399
    log.debug(`found ${Object.keys(r).length} files in config/tls`)
16✔
400
    if (Object.keys(r).length === 0) return
16!
401

16✔
402
    const s = {} // certs by name (CN)
16✔
403

16✔
404
    for (const fp in r) {
16✔
405
        if (r[fp].expire && r[fp].expire < new Date()) {
48!
406
            log.error(`${fp} expired on ${r[fp].expire}`)
×
407
        }
×
408

48✔
409
        // a file with a key and no cert, get name from file
48✔
410
        if (!r[fp].names) r[fp].names = [path.parse(fp).name]
48✔
411

48✔
412
        for (let name of r[fp].names) {
48✔
413
            if (name[0] === '_') name = name.replace('_', '*') // windows
48✔
414
            if (s[name] === undefined) s[name] = {}
48✔
415
            if (!s[name].key && r[fp].keys) s[name].key = r[fp].keys[0]
48✔
416
            if (!s[name].cert && r[fp].chain) {
48✔
417
                s[name].cert = r[fp].chain[0]
32✔
418
                s[name].file = fp
32✔
419
            }
32✔
420
        }
48✔
421
    }
48✔
422

16✔
423
    for (const cn in s) {
16✔
424
        if (!s[cn].cert || !s[cn].key) {
32!
425
            delete s[cn]
×
426
            continue
×
427
        }
×
428

32✔
429
        this.applySocketOpts(cn) // from tls.ini
32✔
430
        certsByHost.set([cn, 'cert'], Buffer.from(s[cn].cert))
32✔
431
        certsByHost.set([cn, 'key'], Buffer.from(s[cn].key))
32✔
432
        certsByHost.set([cn, 'dhparam'], certsByHost['*'].dhparam, true)
32✔
433

32✔
434
        // all opts are applied, generate TLS context
32✔
435
        try {
32✔
436
            ctxByHost[cn] = tls.createSecureContext(certsByHost.get([cn]))
32✔
437
        } catch (err) {
32!
438
            log.error(`CN '${cn}' loading got: ${err.message}`)
×
439
            delete ctxByHost[cn]
×
440
            delete certsByHost[cn]
×
441
        }
×
442
    }
32✔
443

16✔
444
    log.info(`found ${Object.keys(s).length} TLS certs in config/tls`)
16✔
445

16✔
446
    return certsByHost // used only by tests
16✔
447
}
16✔
448

12✔
449
function openssl(crt, ...params) {
32✔
450
    return new Promise((resolve) => {
32✔
451
        let crtTxt = ''
32✔
452
        let errTxt = ''
32✔
453

32✔
454
        const o = spawn('openssl', params, { timeout: 2000 })
32✔
455
        o.stdout.on('data', (data) => {
32✔
456
            crtTxt += data
32✔
457
        })
32✔
458

32✔
459
        o.stderr.on('data', (data) => {
32✔
460
            errTxt += data
32✔
461
        })
32✔
462

32✔
463
        o.on('close', (code) => {
32✔
464
            if (code !== 0) {
32!
465
                log.error(`openssl ${params.join(' ')} failed with code ${code}: ${errTxt.trim()}`)
×
466
            }
×
467
            resolve(crtTxt)
32✔
468
        })
32✔
469

32✔
470
        o.stdin.write(crt)
32✔
471
        o.stdin.write('\n')
32✔
472
    })
32✔
473
}
32✔
474

12✔
475
exports.getSocketOpts = async (name) => {
12✔
476
    // startup time, load the config/tls dir
17✔
477
    if (!certsByHost['*']) this.load_tls_ini()
17✔
478

17✔
479
    try {
17✔
480
        await this.get_certs_dir('tls')
17✔
481
    } catch (err) {
17✔
482
        if (err.code !== 'ENOENT') {
1!
483
            log.error(err.message)
×
484
        }
×
485
    }
1✔
486

17✔
487
    return certsByHost[name] || certsByHost['*']
17!
488
}
17✔
489

12✔
490
function pipe(cleartext, socket) {
3✔
491
    cleartext.socket = socket
3✔
492

3✔
493
    function onError(e) {}
3✔
494

3✔
495
    function onClose() {
3✔
496
        socket.removeListener('error', onError)
2✔
497
        socket.removeListener('close', onClose)
2✔
498
    }
2✔
499

3✔
500
    socket.on('error', onError)
3✔
501
    socket.on('close', onClose)
3✔
502
}
3✔
503

12✔
504
exports.ensureDhparams = (done) => {
12✔
505
    // empty/missing dhparams file
7✔
506
    if (certsByHost['*'].dhparam) {
7✔
507
        return done(null, certsByHost['*'].dhparam)
7✔
508
    }
7✔
509

×
510
    if (cluster.isWorker) return // only once, on the master process
×
511

×
512
    const filePath = this.cfg.main.dhparam || 'dhparams.pem'
×
513
    const fpResolved = path.resolve(exports.config.root_path, filePath)
7✔
514

7✔
515
    log.info(`Generating a 2048 bit dhparams file at ${fpResolved}`)
7✔
516

7✔
517
    const o = spawn('openssl', ['dhparam', '-out', fpResolved, '2048'], { timeout: 30000 })
7✔
518
    o.stdout.on('data', (data) => {
7✔
519
        // normally empty output
×
520
        log.debug(data)
×
521
    })
7✔
522

7✔
523
    o.stderr.on('data', (data) => {
7✔
524
        // this is the status gibberish `openssl dhparam` spews as it works
×
525
    })
7✔
526

7✔
527
    o.on('close', (code) => {
7✔
528
        if (code !== 0) {
×
529
            return done(`Error code: ${code}`)
×
530
        }
×
531

×
532
        log.info(`Saved to ${fpResolved}`)
×
533
        const content = this.config.get(filePath, 'binary')
×
534

×
535
        certsByHost.set('*.dhparam', content)
×
536
        done(null, certsByHost['*'].dhparam)
×
537
    })
7✔
538
}
7✔
539

12✔
540
exports.addOCSP = (server) => {
12✔
541
    if (!ocsp) {
7✔
542
        log.debug(`addOCSP: 'ocsp' not available`)
7✔
543
        return
7✔
544
    }
7✔
545

×
546
    if (server.listenerCount('OCSPRequest') > 0) {
×
547
        log.debug('OCSPRequest already listening')
×
548
        return
×
549
    }
×
550

×
551
    log.debug('adding OCSPRequest listener')
×
552
    server.on('OCSPRequest', (cert, issuer, ocr_cb) => {
×
553
        log.debug(`OCSPRequest: ${cert}`)
×
554
        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() // not working OCSP server
×
558

×
559
            const req = ocsp.request.generate(cert, issuer)
×
560
            const cached = await ocspCache.probe(req.id)
×
561

×
562
            if (cached) {
×
563
                log.debug(`OCSP cache: ${util.inspect(cached)}`)
×
564
                return ocr_cb(null, cached.response)
×
565
            }
×
566

×
567
            const options = {
×
568
                url: uri,
×
569
                ocsp: req.data,
×
570
            }
×
571

×
572
            log.debug(`OCSP req:${util.inspect(req)}`)
×
573
            ocspCache.request(req.id, options, ocr_cb)
×
574
        })
×
575
    })
×
576
}
7✔
577

12✔
578
exports.shutdown = () => {
12✔
579
    if (ocsp) cleanOcspCache()
×
580
}
×
581

12✔
582
function cleanOcspCache() {
×
583
    log.debug(`Cleaning ocspCache. How many keys? ${Object.keys(ocspCache.cache).length}`)
×
584
    for (const key of Object.keys(ocspCache.cache)) {
×
585
        clearTimeout(ocspCache.cache[key].timer)
×
586
    }
×
587
}
×
588

12✔
589
exports.certsByHost = certsByHost
12✔
590
exports.ocsp = ocsp
12✔
591

12✔
592
exports.get_rejectUnauthorized = (rejectUnauthorized, port, port_list) => {
12✔
593
    // console.log(`rejectUnauthorized: ${rejectUnauthorized}, port ${port}, list: ${port_list}`)
6✔
594

6✔
595
    if (rejectUnauthorized) return true
6✔
596

5✔
597
    return !!port_list.includes(port)
5✔
598
}
6✔
599

12✔
600
function createServer(cb) {
6✔
601
    const server = net.createServer((cryptoSocket) => {
6✔
602
        const socket = new pluggableStream(cryptoSocket)
5✔
603

5✔
604
        exports.addOCSP(server)
5✔
605

5✔
606
        socket.upgrade = (cb2) => {
5✔
607
            log.debug('Upgrading to TLS')
1✔
608

1✔
609
            socket.clean()
1✔
610

1✔
611
            cryptoSocket.removeAllListeners('data')
1✔
612

1✔
613
            const options = { ...certsByHost['*'] }
1✔
614
            options.server = server // TLSSocket needs server for SNI to work
1✔
615

1✔
616
            options.rejectUnauthorized = exports.get_rejectUnauthorized(
1✔
617
                options.rejectUnauthorized,
1✔
618
                cryptoSocket.localPort,
1✔
619
                exports.cfg.main.requireAuthorized,
1✔
620
            )
1✔
621

1✔
622
            const cleartext = new tls.TLSSocket(cryptoSocket, options)
1✔
623

1✔
624
            pipe(cleartext, cryptoSocket)
1✔
625

1✔
626
            cleartext
1✔
627
                .on('error', (exception) => {
1✔
628
                    exception.source = 'tls'
1✔
629
                    socket.emit('error', exception)
1✔
630
                })
1✔
631
                .on('secure', () => {
1✔
632
                    log.debug('TLS secured.')
×
633
                    socket.emit('secure')
×
634
                    const cipher = cleartext.getCipher()
×
635
                    cipher.version = cleartext.getProtocol()
×
636
                    if (cb2)
×
637
                        cb2(cleartext.authorized, cleartext.authorizationError, cleartext.getPeerCertificate(), cipher)
×
638
                })
1✔
639

1✔
640
            socket.cleartext = cleartext
1✔
641

1✔
642
            if (socket._timeout) {
1✔
643
                cleartext.setTimeout(socket._timeout)
1✔
644
            }
1✔
645

1✔
646
            cleartext.setKeepAlive(socket._keepalive)
1✔
647

1✔
648
            socket.attach(socket.cleartext)
1✔
649
        }
1✔
650

5✔
651
        cb(socket)
5✔
652
    })
6✔
653

6✔
654
    return server
6✔
655
}
6✔
656

12✔
657
function getCertFor(host) {
1✔
658
    if (host && certsByHost[host]) return certsByHost[host]
1✔
659
    return certsByHost['*'] // the default TLS cert
×
660
}
1✔
661

12✔
662
function connect(conn_options = {}) {
6✔
663
    // called by outbound/client_pool, smtp_client, plugins/spamassassin,avg,clamd,
6✔
664
    // plugins/auth/auth_proxy
6✔
665

6✔
666
    const cryptoSocket = net.connect(conn_options)
6✔
667
    const socket = new pluggableStream(cryptoSocket)
6✔
668

6✔
669
    socket.upgrade = (options, cb2) => {
6✔
670
        socket.clean()
2✔
671
        cryptoSocket.removeAllListeners('data')
2✔
672

2✔
673
        if (exports.tls_valid) {
2✔
674
            const host = conn_options.host
1✔
675
            if (exports.cfg === undefined) exports.load_tls_ini()
1!
676
            if (exports.cfg.mutual_auth_hosts[host]) {
1✔
677
                options = { ...options, ...getCertFor(exports.cfg.mutual_auth_hosts[host]) }
1✔
678
            } else if (exports.cfg.mutual_auth_hosts_exclude[host]) {
1!
679
                // send no client cert
×
680
            } else if (exports.cfg.main.mutual_tls) {
×
681
                options = { ...options, ...getCertFor(host) }
×
682
            }
×
683
        }
1✔
684
        options.socket = cryptoSocket
2✔
685

2✔
686
        const cleartext = tls.connect(options)
2✔
687

2✔
688
        pipe(cleartext, cryptoSocket)
2✔
689

2✔
690
        cleartext.on('error', (err) => {
2✔
691
            err.source = 'tls'
1✔
692
            socket.emit('error', err)
1✔
693
        })
2✔
694

2✔
695
        cleartext.once('secureConnect', () => {
2✔
696
            log.debug('client TLS secured.')
2✔
697
            const cipher = cleartext.getCipher()
2✔
698
            cipher.version = cleartext.getProtocol()
2✔
699
            if (cb2) cb2(cleartext.authorized, cleartext.authorizationError, cleartext.getPeerCertificate(), cipher)
2✔
700
        })
2✔
701

2✔
702
        socket.cleartext = cleartext
2✔
703

2✔
704
        if (socket._timeout) {
2✔
705
            cleartext.setTimeout(socket._timeout)
1✔
706
        }
1✔
707

2✔
708
        cleartext.setKeepAlive(socket._keepalive)
2✔
709

2✔
710
        socket.attach(socket.cleartext)
2✔
711

2✔
712
        log.debug('client TLS upgrade in progress, awaiting secured.')
2✔
713
    }
2✔
714

6✔
715
    return socket
6✔
716
}
6✔
717

12✔
718
exports.connect = connect
12✔
719
exports.createConnection = connect
12✔
720
exports.Server = createServer
12✔
721
exports.createServer = createServer
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc