msimerson/Haraka

View on GitHub
plugins/queue/qmail-queue.js

Summary

Maintainability
B
4 hrs
Test Coverage
// Queue to qmail-queue

const childproc = require('child_process');
const fs        = require('fs');

exports.register = function () {

    this.queue_exec = this.config.get('qmail-queue.path') || '/var/qmail/bin/qmail-queue';
    if (!fs.existsSync(this.queue_exec)) {
        throw new Error(`Cannot find qmail-queue binary (${this.queue_exec})`);
    }

    this.load_qmail_queue_ini();

    if (this.cfg.main.enable_outbound) {
        this.register_hook('queue_outbound', 'hook_queue');
    }
}

exports.load_qmail_queue_ini = function () {

    this.cfg = this.config.get('qmail-queue.ini', {
        booleans: [
            '+main.enable_outbound',
        ],
    },
    () => {
        this.load_qmail_queue_ini();
    });
}

exports.hook_queue = function (next, connection) {
    const plugin = this;

    const txn = connection?.transaction;
    if (!txn) return next();

    const q_wants = txn.notes.get('queue.wants');
    if (q_wants && q_wants !== 'qmail-queue') return next();

    const qmail_queue = childproc.spawn(
        this.queue_exec, // process name
        [],              // arguments
        { stdio: ['pipe', 'pipe', process.stderr] }
    );

    qmail_queue.on('exit', function finished (code) {
        if (code !== 0) {
            connection.logerror(plugin, `Unable to queue message to qmail-queue: ${code}`);
            next();
        }
        else {
            next(OK, "Queued!");
        }
    });

    connection.transaction.message_stream.pipe(qmail_queue.stdin, { line_endings: '\n' });

    qmail_queue.stdin.on('close', () => {
        if (!connection?.transaction) {
            plugin.logerror("Transaction went away while delivering mail to qmail-queue");
            try {
                qmail_queue.stdout.end();
            }
            catch (err) {
                if (err.code !== 'ENOTCONN') {
                    // Ignore ENOTCONN and re throw anything else
                    throw err
                }
            }

            connection.results.add(plugin, { err: 'dead sender' });
            return;
        }
        plugin.loginfo("Message Stream sent to qmail. Now sending envelope");
        // now send envelope
        // Hope this will be big enough...
        const buf = Buffer.alloc(4096);
        let p = 0;
        buf[p++] = 70;
        const mail_from = connection.transaction.mail_from.address();
        for (let i = 0; i < mail_from.length; i++) {
            buf[p++] = mail_from.charCodeAt(i);
        }
        buf[p++] = 0;
        connection.transaction.rcpt_to.forEach(rcpt => {
            buf[p++] = 84;
            const rcpt_to = rcpt.address();
            for (let j = 0; j < rcpt_to.length; j++) {
                buf[p++] = rcpt_to.charCodeAt(j);
            }
            buf[p++] = 0;
        });
        buf[p++] = 0;
        qmail_queue.stdout.on('error', err => {}); // stdout throws an error on close
        qmail_queue.stdout.end(buf);
    });
}