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

haraka / Haraka / 27447031766

12 Jun 2026 10:36PM UTC coverage: 72.577% (-0.2%) from 72.753%
27447031766

Pull #3601

github

web-flow
Merge c03f85603 into 10de82021
Pull Request #3601: fix(conn): flag soft queue denials in results

1682 of 2207 branches covered (76.21%)

3 of 3 new or added lines in 1 file covered. (100.0%)

136 existing lines in 6 files now uncovered.

7609 of 10484 relevant lines covered (72.58%)

25.93 hits per line

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

59.76
/plugins/tls.js
1
'use strict'
6✔
2
// TLS is built into Haraka. This plugin conditionally advertises STARTTLS.
6✔
3
// see 'haraka -h tls' for help
6✔
4

6✔
5
/* global server */
6✔
6

6✔
7
const tls_socket = require('../tls_socket')
6✔
8

6✔
9
// exported so tests can override config dir
6✔
10
exports.net_utils = require('haraka-net-utils')
6✔
11

6✔
12
exports.register = function () {
6✔
13
    tls_socket.load_tls_ini()
6✔
14

6✔
15
    if (tls_socket.cfg.redis.disable_for_failed_hosts) this.logdebug('Will disable STARTTLS for failing TLS hosts')
6!
16

×
17
    this.register_hook('capabilities', 'advertise_starttls')
6✔
18
    this.register_hook('unrecognized_command', 'upgrade_connection')
6✔
19
}
6✔
20

6✔
21
exports.shutdown = () => {
6✔
22
    if (tls_socket.shutdown) tls_socket.shutdown()
6✔
23
}
×
24

×
25
exports.advertise_starttls = function (next, connection) {
6✔
26
    // if no TLS setup incomplete/invalid, don't advertise
5✔
27
    if (!tls_socket.tls_valid) {
5✔
28
        this.logerror('no valid TLS config')
5!
29
        return next()
×
30
    }
×
31

×
32
    /* Caution: do not advertise STARTTLS if already TLS upgraded */
5✔
33
    if (connection.tls.enabled) return next()
5!
34

×
35
    if (this.net_utils.ip_in_list(tls_socket.cfg.no_tls_hosts, connection.remote.ip)) {
5✔
36
        return next()
5!
37
    }
×
38

×
39
    function enable_tls() {
5✔
40
        connection.capabilities.push('STARTTLS')
5✔
41
        connection.tls.advertised = true
5✔
42
        next()
5✔
43
    }
5✔
44

5✔
45
    // check if local port is excluded from starttls advertisement
5✔
46
    if (tls_socket.cfg.main.no_starttls_ports.includes(connection.local.port)) return next()
5!
47

×
48
    // exclude port 587 from NO-GO
5✔
49
    if (connection.local.port === 587) return enable_tls()
5!
50

×
51
    if (!tls_socket.cfg.redis || !server.notes.redis) {
5✔
52
        return enable_tls()
5✔
53
    }
5✔
54

5✔
55
    const { redis } = server.notes
5!
56
    const dbkey = `no_tls|${connection.remote.ip}`
×
57

×
58
    redis
×
59
        .get(dbkey)
×
60
        .then((dbr) => {
×
61
            if (!dbr) return enable_tls()
×
62
            connection.results.add(this, { msg: 'no_tls' })
×
63
            next(CONT, 'STARTTLS disabled because previous attempt failed')
×
64
        })
×
65
        .catch((err) => {
×
66
            connection.results.add(this, { err })
×
67
            enable_tls()
×
68
        })
×
69
}
×
70

×
71
exports.set_notls = function (connection) {
6✔
72
    if (!tls_socket.cfg.redis.disable_for_failed_hosts) return
1✔
73
    if (!server.notes.redis) return
1!
74

×
75
    const expiry = tls_socket.cfg.redis.disable_inbound_expiry || 3600
×
76

×
77
    this.lognotice(connection, `STARTTLS failed. Marking ${connection.remote.ip} as non-TLS host for ${expiry} seconds`)
1✔
78

1✔
79
    server.notes.redis.setEx(`no_tls|${connection.remote.ip}`, expiry, new Date().toISOString())
1✔
80
}
1✔
81

1✔
82
exports.upgrade_connection = function (next, connection, params) {
6✔
83
    if (!connection.tls.advertised) return next()
5!
84

×
85
    /* Watch for STARTTLS directive from client. */
5✔
86
    if (params[0].toUpperCase() !== 'STARTTLS') return next()
5✔
87

4!
88
    // RFC 3207 ยง4: discard any plaintext the client pipelined after
5✔
89
    // STARTTLS. Otherwise connection.respond() restores state to CMD and
1✔
90
    // re-enters _process_data(), parsing and executing those buffered
1✔
91
    // cleartext commands before the TLS handshake completes (STARTTLS
1✔
92
    // command injection). Mirrors the buffer-nuke already used for
1✔
93
    // SSL-over-plaintext detection in connection.process_line().
1✔
94
    connection.current_data = null
1✔
95

1✔
96
    /* Respond to STARTTLS command. */
1✔
97
    connection.respond(220, 'Go ahead.')
1✔
98

1✔
99
    const plugin = this
1✔
100
    let called_next = false
1✔
101
    // adjust plugin.timeout like so: echo '45' > config/tls.timeout
1✔
102
    const timeout = plugin.timeout - 1
1✔
103

1✔
104
    function nextOnce(disconnected) {
1✔
105
        if (called_next) return
1✔
106
        called_next = true
1!
107
        clearTimeout(connection.notes.tls_timer)
1✔
108
        if (!disconnected) connection.lognotice(plugin, 'timeout setting up TLS')
1!
109
        plugin.set_notls(connection)
1✔
110
        return next(DENYSOFTDISCONNECT)
1✔
111
    }
1✔
112

1✔
113
    if (timeout && timeout > 0) {
1✔
114
        connection.notes.tls_timer = setTimeout(nextOnce, timeout * 1000)
5!
115
    }
1✔
116

1✔
117
    connection.notes.cleanUpDisconnect = nextOnce
1✔
118

1✔
119
    /* Upgrade the connection to TLS. */
1✔
120
    connection.client.upgrade((verified, verifyError, cert, cipher) => {
1✔
121
        if (called_next) return
×
122
        clearTimeout(connection.notes.tls_timer)
×
123
        called_next = true
×
124
        connection.reset_transaction(() => {
×
125
            connection.setTLS({
×
126
                cipher,
×
127
                verified,
×
128
                verifyError,
×
129
                peerCertificate: cert,
×
130
            })
×
131

×
132
            connection.results.add(plugin, connection.tls)
×
133
            plugin.emit_upgrade_msg(connection, verified, verifyError, cert, cipher)
×
134
            next(OK)
×
135
        })
×
136
    })
×
137
}
×
138

×
139
exports.hook_disconnect = (next, connection) => {
6✔
140
    if (connection.notes.cleanUpDisconnect) {
1✔
141
        connection.notes.cleanUpDisconnect(true)
1✔
142
    }
1✔
143
    next()
1✔
144
}
1✔
145

1✔
146
exports.emit_upgrade_msg = function (conn, verified, verifyError, cert, cipher) {
6✔
UNCOV
147
    let msg = 'secured:'
×
UNCOV
148
    if (cipher) {
×
UNCOV
149
        msg += ` cipher=${cipher.name} version=${cipher.version}`
×
150
    }
×
UNCOV
151
    msg += ` verified=${verified}`
×
UNCOV
152
    if (verifyError) msg += ` error="${verifyError}"`
✔
UNCOV
153
    if (cert) {
×
UNCOV
154
        if (cert.subject) {
×
UNCOV
155
            msg += ` cn="${cert.subject.CN}" organization="${cert.subject.O}"`
×
UNCOV
156
        }
×
UNCOV
157
        if (cert.issuer) msg += ` issuer="${cert.issuer.O}"`
×
UNCOV
158
        if (cert.valid_to) msg += ` expires="${cert.valid_to}"`
×
UNCOV
159
        if (cert.fingerprint) msg += ` fingerprint=${cert.fingerprint}`
×
160
    }
×
161

×
UNCOV
162
    conn.loginfo(this, msg)
×
UNCOV
163
    return msg
×
UNCOV
164
}
×
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