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

haraka / Haraka / 26143288784

20 May 2026 05:27AM UTC coverage: 73.431% (+1.9%) from 71.554%
26143288784

push

github

web-flow
Release 3.1.7 (#3572)

- feat(smtp_forward,smtp_proxy): honor `tls.ini` `[main]` and plugin
`[tls]`
  section for backend STARTTLS (matches docs). Behavior change: installs
that set `[main] rejectUnauthorized=true` in `tls.ini` will now see it
applied
to the forward/proxy paths. Untouched installs match the previous
behavior.
- fix(auth_proxy): try opportunistic STARTTLS w/o a key/cert,
#matchTheDocs
- feat(tls_socket): new `load_plugin_tls_options(plugin_tls_cfg)` helper
that
merges a plugin's `[tls]` section over `tls.ini` `[main]` for client
STARTTLS
- refactor: `outbound/tls.js#load_config()` delegates to
`load_plugin_tls_options()`
- change: update DSN.addr_bad_dest_system(...) to DSN.addr_null_mx(...)
- fix(tls): buffer discard on STARTTLS (RFC 3207 §4)
- fix(server): run the graceful restart/shutdown work queue
- fix(xclient): parse DESTPORT to int so the 587/465 auth check applies
- fix(smtp_client):
  - no_tls_hosts works correctly by referencing the correct path
  - unsupported AUTH no longer throws out of the event loop
- fix(smtputf8): all code paths use it, no more smtp_utf8
- fix(conn): reject control chars in HELO name (RFC 5321 §4.1.1.1)
- fix: sanitize AUTH usernames before storing
- fix: strip CR/LF from all strings passed into `auth_results()`
- fix(smtp_client,auth_proxy): redact AUTH credentials in protocol logs
- fix(prevent_credential_leaks): properly handle usernames w/o an `@`
- fix(queue/qmail-queue): size envelope dynamically; UTF-8 safe
- deps(some): bump patch versions to latest
- change: replace forEach with es6 style for...of #3569
- tests: add a few #3568
- doc(Plugins): add publish year to each plugin #3567
- deps(all): switch from ^ to ~ version ranges #3565

1699 of 2190 branches covered (77.58%)

162 of 170 new or added lines in 13 files covered. (95.29%)

2 existing lines in 2 files now uncovered.

7943 of 10817 relevant lines covered (73.43%)

23.24 hits per line

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

42.96
/plugins/queue/smtp_proxy.js
1
'use strict'
10✔
2
// Proxy to an SMTP server
10✔
3
// Opens the connection to the ongoing SMTP server at MAIL FROM time
10✔
4
// and passes back any errors seen on the ongoing server to the
10✔
5
// originating server.
10✔
6

10✔
7
const smtp_client_mod = require('../../smtp_client')
10✔
8
const tls_socket = require('../../tls_socket')
10✔
9

10✔
10
exports.register = function () {
10✔
11
    this.load_smtp_proxy_ini()
×
12

×
13
    if (this.cfg.main.enable_outbound) {
×
14
        this.register_hook('queue_outbound', 'hook_queue')
×
15
    }
×
16
}
×
17

×
18
exports.load_smtp_proxy_ini = function () {
10✔
19
    this.cfg = this.config.get(
13✔
20
        'smtp_proxy.ini',
13✔
21
        {
13✔
22
            booleans: [
13✔
23
                '-main.enable_tls',
13✔
24
                '+main.enable_outbound',
13✔
25
                '+tls.requestCert',
13✔
26
                '+tls.honorCipherOrder',
13✔
27
                '-tls.rejectUnauthorized',
13✔
28
            ],
13✔
29
        },
13✔
30
        () => {
13✔
31
            this.load_smtp_proxy_ini()
13✔
32
        },
×
33
    )
×
34

×
35
    // Build backend TLS options from tls.ini [main] + this plugin's [tls] section.
13✔
36
    // Re-derived on every (re)load so SIGHUP picks up edits.
13✔
37
    this.tls_options = tls_socket.load_plugin_tls_options(this.cfg.tls || {})
13✔
38

13✔
39
    if (this.cfg.main.enable_outbound) {
13!
40
        this.lognotice('outbound enabled, will default to disabled in Haraka v3 (see #1472)')
13✔
41
    }
13✔
42
}
13✔
43

13✔
44
exports.hook_mail = function (next, connection, params) {
10✔
45
    const c = this.cfg.main
×
46
    connection.loginfo(
×
47
        this,
×
48
        `forwarding to ${c.forwarding_host_pool ? 'configured forwarding_host_pool' : `${c.host}:${c.port}`}`,
×
49
    )
×
50
    smtp_client_mod.get_client_plugin(this, connection, c, (err, smtp_client) => {
×
51
        connection.notes.smtp_client = smtp_client
×
52
        smtp_client.next = next
×
53

×
54
        smtp_client.on('mail', smtp_client.call_next)
×
55
        smtp_client.on('rcpt', smtp_client.call_next)
×
56
        smtp_client.on('data', smtp_client.call_next)
×
57

×
58
        smtp_client.on('dot', () => {
×
59
            if (smtp_client.is_dead_sender(this, connection)) {
×
60
                delete connection.notes.smtp_client
×
61
                return
×
62
            }
×
63

×
64
            smtp_client.call_next(OK, smtp_client.response)
×
65
            smtp_client.release()
×
66
            delete connection.notes.smtp_client
×
67
        })
×
68

×
69
        smtp_client.on('error', () => {
×
70
            delete connection.notes.smtp_client
×
71
        })
×
72

×
73
        smtp_client.on('bad_code', (code, msg) => {
×
74
            smtp_client.call_next(code.match(/^4/) ? DENYSOFT : DENY, smtp_client.response.slice())
×
75

×
76
            if (smtp_client.command !== 'rcpt') {
×
77
                // errors are OK for rcpt, but nothing else
×
78
                // this can also happen if the destination server
×
79
                // times out, but that is okay.
×
80
                connection.loginfo(this, 'message denied, proxying failed')
×
81
                smtp_client.release()
×
82
                delete connection.notes.smtp_client
×
83
            }
×
84
        })
×
85
    })
×
86
}
×
87

×
88
exports.hook_rcpt_ok = (next, connection, recipient) => {
10✔
89
    const { smtp_client } = connection.notes
×
90
    if (!smtp_client) return next()
×
91
    if (smtp_client.is_dead_sender(this, connection)) {
×
92
        delete connection.notes.smtp_client
×
93
        return
×
94
    }
×
95
    smtp_client.next = next
×
NEW
96
    smtp_client.send_command('RCPT', `TO:${recipient.format(!smtp_client.smtputf8)}`)
×
97
}
×
98

×
99
exports.hook_data = (next, connection) => {
10✔
100
    const { smtp_client } = connection.notes
×
101
    if (!smtp_client) return next()
×
102

×
103
    if (smtp_client.is_dead_sender(this, connection)) {
×
104
        delete connection.notes.smtp_client
×
105
        return
×
106
    }
×
107
    smtp_client.next = next
×
108
    smtp_client.send_command('DATA')
×
109
}
×
110

×
111
exports.hook_queue = function (next, connection) {
10✔
112
    if (!connection?.transaction || !connection?.notes) return next()
2✔
113

1✔
114
    const { smtp_client } = connection.notes
1✔
115
    if (!smtp_client) return next()
1✔
116

1✔
117
    if (smtp_client.is_dead_sender(this, connection)) {
2!
118
        delete connection.notes.smtp_client
×
119
        return
×
120
    }
×
121
    smtp_client.next = next
×
122
    smtp_client.start_data(connection.transaction.message_stream)
×
123
}
×
124

×
125
exports.hook_rset = (next, connection) => {
10✔
126
    const { smtp_client } = connection.notes
3✔
127
    if (!smtp_client) return next()
3✔
128
    smtp_client.release()
3✔
129
    delete connection.notes.smtp_client
1✔
130
    next()
1✔
131
}
1✔
132

1✔
133
exports.hook_quit = exports.hook_rset
10✔
134

10✔
135
exports.hook_disconnect = (next, connection) => {
10✔
136
    const { smtp_client } = connection.notes
2✔
137
    if (!smtp_client) return next()
2✔
138
    smtp_client.release()
1✔
139
    delete connection.notes.smtp_client
1✔
140
    smtp_client.call_next()
1✔
141
    next()
1✔
142
}
1✔
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