'use strict'; const { spawn } = require('child_process'); const packageData = require('../../package.json'); const shared = require('../shared'); const errors = require('../errors'); /** * Generates a Transport object for Sendmail * * Possible options can be the following: * * * **path** optional path to sendmail binary * * **newline** either 'windows' or 'unix' * * **args** an array of arguments for the sendmail binary * * @constructor * @param {Object} optional config parameter for Sendmail */ class SendmailTransport { constructor(options) { options = options || {}; // use a reference to spawn for mocking purposes this._spawn = spawn; this.options = options; this.name = 'Sendmail'; this.version = packageData.version; this.path = 'sendmail'; this.args = false; this.logger = shared.getLogger(this.options, { component: this.options.component || 'sendmail' }); if (typeof options === 'string') { this.path = options; } else if (typeof options === 'object') { if (options.path) { this.path = options.path; } if (Array.isArray(options.args)) { this.args = options.args; } } } /** *

Compiles a mailcomposer message and forwards it to handler that sends it.

* * @param {Object} emailMessage MailComposer object * @param {Function} callback Callback function to run when the sending is completed */ send(mail, done) { // Sendmail strips this header line by itself mail.message.keepBcc = true; const envelope = mail.data.envelope || mail.message.getEnvelope(); const messageId = mail.message.messageId(); let returned; const hasInvalidAddresses = [] .concat(envelope.from || []) .concat(envelope.to || []) .some(addr => /^-/.test(addr)); if (hasInvalidAddresses) { const err = new Error('Can not send mail. Invalid envelope addresses.'); err.code = errors.ESENDMAIL; return done(err); } // force -i to keep single dots const args = this.args ? ['-i'].concat(this.args).concat(envelope.to) : ['-i'].concat(envelope.from ? ['-f', envelope.from] : []).concat(envelope.to); const callback = err => { if (returned) { // ignore any additional responses, already done return; } returned = true; if (typeof done === 'function') { if (err) { return done(err); } return done(null, { envelope, messageId, response: 'Messages queued for delivery' }); } }; let sendmail; try { sendmail = this._spawn(this.path, args); } catch (E) { this.logger.error( { err: E, tnx: 'spawn', messageId }, 'Error occurred while spawning sendmail. %s', E.message ); return callback(E); } if (sendmail) { sendmail.on('error', err => { this.logger.error( { err, tnx: 'spawn', messageId }, 'Error occurred when sending message %s. %s', messageId, err.message ); callback(err); }); sendmail.once('exit', code => { if (!code) { return callback(); } const err = new Error( code === 127 ? 'Sendmail command not found, process exited with code ' + code : 'Sendmail exited with code ' + code ); err.code = errors.ESENDMAIL; this.logger.error( { err, tnx: 'stdin', messageId }, 'Error sending message %s to sendmail. %s', messageId, err.message ); callback(err); }); sendmail.once('close', callback); sendmail.stdin.on('error', err => { this.logger.error( { err, tnx: 'stdin', messageId }, 'Error occurred when piping message %s to sendmail. %s', messageId, err.message ); callback(err); }); const recipients = [].concat(envelope.to || []); if (recipients.length > 3) { recipients.push('...and ' + recipients.splice(2).length + ' more'); } this.logger.info( { tnx: 'send', messageId }, 'Sending message %s to <%s>', messageId, recipients.join(', ') ); const sourceStream = mail.message.createReadStream(); sourceStream.once('error', err => { this.logger.error( { err, tnx: 'stdin', messageId }, 'Error occurred when generating message %s. %s', messageId, err.message ); sendmail.kill('SIGINT'); // do not deliver the message callback(err); }); sourceStream.pipe(sendmail.stdin); } else { const err = new Error('sendmail was not found'); err.code = errors.ESENDMAIL; return callback(err); } } } module.exports = SendmailTransport;