• 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

47.31
/plugins/queue/qmail-queue.js
1
// Queue to qmail-queue
8✔
2

8✔
3
const childproc = require('node:child_process')
8✔
4
const fs = require('node:fs')
8✔
5

8✔
6
exports.register = function () {
8✔
7
    this.queue_exec = this.config.get('qmail-queue.path') || '/var/qmail/bin/qmail-queue'
1✔
8
    if (!fs.existsSync(this.queue_exec)) {
1✔
9
        throw new Error(`Cannot find qmail-queue binary (${this.queue_exec})`)
1✔
10
    }
1✔
11

1✔
12
    this.load_qmail_queue_ini()
1!
13

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

×
19
exports.load_qmail_queue_ini = function () {
8✔
20
    this.cfg = this.config.get(
4✔
21
        'qmail-queue.ini',
4✔
22
        {
4✔
23
            booleans: ['+main.enable_outbound'],
4✔
24
        },
4✔
25
        () => {
4✔
26
            this.load_qmail_queue_ini()
4✔
27
        },
×
28
    )
×
29
}
×
30

×
31
// qmail-queue envelope: F<sender>\0 (T<rcpt>\0)* \0
8✔
32
// Built dynamically, sized to exactly the bytes needed.
8✔
33
//   doesn't emit zero padding after the terminating NUL.
8✔
34
//   encodes non-ASCII (SMTPUTF8) addresses correctly
8✔
35
exports.build_envelope = function (transaction) {
8✔
36
    const NUL = Buffer.from([0])
3✔
37
    const parts = [Buffer.from('F'), Buffer.from(transaction.mail_from.address), NUL]
3✔
38
    for (const rcpt of transaction.rcpt_to) {
3✔
39
        parts.push(Buffer.from('T'), Buffer.from(rcpt.address), NUL)
3✔
40
    }
503✔
41
    parts.push(NUL)
3✔
42
    return Buffer.concat(parts)
3✔
43
}
3✔
44

3✔
45
exports.hook_queue = function (next, connection) {
8✔
46
    const plugin = this
3✔
47

3✔
48
    const txn = connection?.transaction
3✔
49
    if (!txn) return next()
3✔
50

1✔
51
    const q_wants = txn.notes.get('queue.wants')
3✔
52
    if (q_wants && q_wants !== 'qmail-queue') return next()
3✔
53

2✔
54
    const qmail_queue = childproc.spawn(
3!
55
        this.queue_exec, // process name
×
56
        [], // arguments
×
57
        { stdio: ['pipe', 'pipe', process.stderr] },
×
58
    )
×
59

×
60
    qmail_queue.on('exit', function finished(code) {
×
61
        if (code !== 0) {
×
62
            connection.logerror(plugin, `Unable to queue message to qmail-queue: ${code}`)
×
63
            next()
×
64
        } else {
×
65
            next(OK, 'Queued!')
×
66
        }
×
67
    })
×
68

×
69
    connection.transaction.message_stream.pipe(qmail_queue.stdin, {
×
70
        line_endings: '\n',
×
71
    })
×
72

×
73
    qmail_queue.stdin.on('close', () => {
×
74
        if (!connection?.transaction) {
×
75
            plugin.logerror('Transaction went away while delivering mail to qmail-queue')
×
76
            try {
×
77
                qmail_queue.stdout.end()
×
78
            } catch (err) {
×
79
                if (err.code !== 'ENOTCONN') {
×
80
                    // Ignore ENOTCONN and re throw anything else
×
81
                    throw err
×
82
                }
×
83
            }
×
84

×
85
            connection.results.add(plugin, { err: 'dead sender' })
×
86
            return
×
87
        }
×
88
        plugin.loginfo('Message Stream sent to qmail. Now sending envelope')
×
NEW
89
        const buf = plugin.build_envelope(connection.transaction)
×
90
        qmail_queue.stdout.on('error', (err) => {}) // stdout throws an error on close
×
91
        qmail_queue.stdout.end(buf)
×
92
    })
×
93
}
×
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