'use strict'; const spawn = require('child_process').spawn; const packageData = require('../../package.json'); const shared = require('../shared'); /** * 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.winbreak = false; this.logger = shared.getLogger(this.options, { component: this.options.component || 'sendmail' }); if (options) { 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; } this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase()); } } } /** *
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; let envelope = mail.data.envelope || mail.message.getEnvelope(); let messageId = mail.message.messageId(); let args; let sendmail; let returned; const hasInvalidAddresses = [] .concat(envelope.from || []) .concat(envelope.to || []) .some(addr => /^-/.test(addr)); if (hasInvalidAddresses) { return done(new Error('Can not send mail. Invalid envelope addresses.')); } if (this.args) { // force -i to keep single dots args = ['-i'].concat(this.args).concat(envelope.to); } else { args = ['-i'].concat(envelope.from ? ['-f', envelope.from] : []).concat(envelope.to); } let callback = err => { if (returned) { // ignore any additional responses, already done return; } returned = true; if (typeof done === 'function') { if (err) { return done(err); } else { return done(null, { envelope: mail.data.envelope || mail.message.getEnvelope(), messageId, response: 'Messages queued for delivery' }); } } }; 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(); } let err; if (code === 127) { err = new Error('Sendmail command not found, process exited with code ' + code); } else { err = new Error('Sendmail exited with code ' + code); } 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); }); let 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(', ') ); let 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 { return callback(new Error('sendmail was not found')); } } } module.exports = SendmailTransport;