| 1 | 'use strict';
|
|---|
| 2 |
|
|---|
| 3 | const spawn = require('child_process').spawn;
|
|---|
| 4 | const packageData = require('../../package.json');
|
|---|
| 5 | const shared = require('../shared');
|
|---|
| 6 |
|
|---|
| 7 | /**
|
|---|
| 8 | * Generates a Transport object for Sendmail
|
|---|
| 9 | *
|
|---|
| 10 | * Possible options can be the following:
|
|---|
| 11 | *
|
|---|
| 12 | * * **path** optional path to sendmail binary
|
|---|
| 13 | * * **newline** either 'windows' or 'unix'
|
|---|
| 14 | * * **args** an array of arguments for the sendmail binary
|
|---|
| 15 | *
|
|---|
| 16 | * @constructor
|
|---|
| 17 | * @param {Object} optional config parameter for Sendmail
|
|---|
| 18 | */
|
|---|
| 19 | class SendmailTransport {
|
|---|
| 20 | constructor(options) {
|
|---|
| 21 | options = options || {};
|
|---|
| 22 |
|
|---|
| 23 | // use a reference to spawn for mocking purposes
|
|---|
| 24 | this._spawn = spawn;
|
|---|
| 25 |
|
|---|
| 26 | this.options = options || {};
|
|---|
| 27 |
|
|---|
| 28 | this.name = 'Sendmail';
|
|---|
| 29 | this.version = packageData.version;
|
|---|
| 30 |
|
|---|
| 31 | this.path = 'sendmail';
|
|---|
| 32 | this.args = false;
|
|---|
| 33 | this.winbreak = false;
|
|---|
| 34 |
|
|---|
| 35 | this.logger = shared.getLogger(this.options, {
|
|---|
| 36 | component: this.options.component || 'sendmail'
|
|---|
| 37 | });
|
|---|
| 38 |
|
|---|
| 39 | if (options) {
|
|---|
| 40 | if (typeof options === 'string') {
|
|---|
| 41 | this.path = options;
|
|---|
| 42 | } else if (typeof options === 'object') {
|
|---|
| 43 | if (options.path) {
|
|---|
| 44 | this.path = options.path;
|
|---|
| 45 | }
|
|---|
| 46 | if (Array.isArray(options.args)) {
|
|---|
| 47 | this.args = options.args;
|
|---|
| 48 | }
|
|---|
| 49 | this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
|
|---|
| 50 | }
|
|---|
| 51 | }
|
|---|
| 52 | }
|
|---|
| 53 |
|
|---|
| 54 | /**
|
|---|
| 55 | * <p>Compiles a mailcomposer message and forwards it to handler that sends it.</p>
|
|---|
| 56 | *
|
|---|
| 57 | * @param {Object} emailMessage MailComposer object
|
|---|
| 58 | * @param {Function} callback Callback function to run when the sending is completed
|
|---|
| 59 | */
|
|---|
| 60 | send(mail, done) {
|
|---|
| 61 | // Sendmail strips this header line by itself
|
|---|
| 62 | mail.message.keepBcc = true;
|
|---|
| 63 |
|
|---|
| 64 | let envelope = mail.data.envelope || mail.message.getEnvelope();
|
|---|
| 65 | let messageId = mail.message.messageId();
|
|---|
| 66 | let args;
|
|---|
| 67 | let sendmail;
|
|---|
| 68 | let returned;
|
|---|
| 69 |
|
|---|
| 70 | const hasInvalidAddresses = []
|
|---|
| 71 | .concat(envelope.from || [])
|
|---|
| 72 | .concat(envelope.to || [])
|
|---|
| 73 | .some(addr => /^-/.test(addr));
|
|---|
| 74 | if (hasInvalidAddresses) {
|
|---|
| 75 | return done(new Error('Can not send mail. Invalid envelope addresses.'));
|
|---|
| 76 | }
|
|---|
| 77 |
|
|---|
| 78 | if (this.args) {
|
|---|
| 79 | // force -i to keep single dots
|
|---|
| 80 | args = ['-i'].concat(this.args).concat(envelope.to);
|
|---|
| 81 | } else {
|
|---|
| 82 | args = ['-i'].concat(envelope.from ? ['-f', envelope.from] : []).concat(envelope.to);
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | let callback = err => {
|
|---|
| 86 | if (returned) {
|
|---|
| 87 | // ignore any additional responses, already done
|
|---|
| 88 | return;
|
|---|
| 89 | }
|
|---|
| 90 | returned = true;
|
|---|
| 91 | if (typeof done === 'function') {
|
|---|
| 92 | if (err) {
|
|---|
| 93 | return done(err);
|
|---|
| 94 | } else {
|
|---|
| 95 | return done(null, {
|
|---|
| 96 | envelope: mail.data.envelope || mail.message.getEnvelope(),
|
|---|
| 97 | messageId,
|
|---|
| 98 | response: 'Messages queued for delivery'
|
|---|
| 99 | });
|
|---|
| 100 | }
|
|---|
| 101 | }
|
|---|
| 102 | };
|
|---|
| 103 |
|
|---|
| 104 | try {
|
|---|
| 105 | sendmail = this._spawn(this.path, args);
|
|---|
| 106 | } catch (E) {
|
|---|
| 107 | this.logger.error(
|
|---|
| 108 | {
|
|---|
| 109 | err: E,
|
|---|
| 110 | tnx: 'spawn',
|
|---|
| 111 | messageId
|
|---|
| 112 | },
|
|---|
| 113 | 'Error occurred while spawning sendmail. %s',
|
|---|
| 114 | E.message
|
|---|
| 115 | );
|
|---|
| 116 | return callback(E);
|
|---|
| 117 | }
|
|---|
| 118 |
|
|---|
| 119 | if (sendmail) {
|
|---|
| 120 | sendmail.on('error', err => {
|
|---|
| 121 | this.logger.error(
|
|---|
| 122 | {
|
|---|
| 123 | err,
|
|---|
| 124 | tnx: 'spawn',
|
|---|
| 125 | messageId
|
|---|
| 126 | },
|
|---|
| 127 | 'Error occurred when sending message %s. %s',
|
|---|
| 128 | messageId,
|
|---|
| 129 | err.message
|
|---|
| 130 | );
|
|---|
| 131 | callback(err);
|
|---|
| 132 | });
|
|---|
| 133 |
|
|---|
| 134 | sendmail.once('exit', code => {
|
|---|
| 135 | if (!code) {
|
|---|
| 136 | return callback();
|
|---|
| 137 | }
|
|---|
| 138 | let err;
|
|---|
| 139 | if (code === 127) {
|
|---|
| 140 | err = new Error('Sendmail command not found, process exited with code ' + code);
|
|---|
| 141 | } else {
|
|---|
| 142 | err = new Error('Sendmail exited with code ' + code);
|
|---|
| 143 | }
|
|---|
| 144 |
|
|---|
| 145 | this.logger.error(
|
|---|
| 146 | {
|
|---|
| 147 | err,
|
|---|
| 148 | tnx: 'stdin',
|
|---|
| 149 | messageId
|
|---|
| 150 | },
|
|---|
| 151 | 'Error sending message %s to sendmail. %s',
|
|---|
| 152 | messageId,
|
|---|
| 153 | err.message
|
|---|
| 154 | );
|
|---|
| 155 | callback(err);
|
|---|
| 156 | });
|
|---|
| 157 | sendmail.once('close', callback);
|
|---|
| 158 |
|
|---|
| 159 | sendmail.stdin.on('error', err => {
|
|---|
| 160 | this.logger.error(
|
|---|
| 161 | {
|
|---|
| 162 | err,
|
|---|
| 163 | tnx: 'stdin',
|
|---|
| 164 | messageId
|
|---|
| 165 | },
|
|---|
| 166 | 'Error occurred when piping message %s to sendmail. %s',
|
|---|
| 167 | messageId,
|
|---|
| 168 | err.message
|
|---|
| 169 | );
|
|---|
| 170 | callback(err);
|
|---|
| 171 | });
|
|---|
| 172 |
|
|---|
| 173 | let recipients = [].concat(envelope.to || []);
|
|---|
| 174 | if (recipients.length > 3) {
|
|---|
| 175 | recipients.push('...and ' + recipients.splice(2).length + ' more');
|
|---|
| 176 | }
|
|---|
| 177 | this.logger.info(
|
|---|
| 178 | {
|
|---|
| 179 | tnx: 'send',
|
|---|
| 180 | messageId
|
|---|
| 181 | },
|
|---|
| 182 | 'Sending message %s to <%s>',
|
|---|
| 183 | messageId,
|
|---|
| 184 | recipients.join(', ')
|
|---|
| 185 | );
|
|---|
| 186 |
|
|---|
| 187 | let sourceStream = mail.message.createReadStream();
|
|---|
| 188 | sourceStream.once('error', err => {
|
|---|
| 189 | this.logger.error(
|
|---|
| 190 | {
|
|---|
| 191 | err,
|
|---|
| 192 | tnx: 'stdin',
|
|---|
| 193 | messageId
|
|---|
| 194 | },
|
|---|
| 195 | 'Error occurred when generating message %s. %s',
|
|---|
| 196 | messageId,
|
|---|
| 197 | err.message
|
|---|
| 198 | );
|
|---|
| 199 | sendmail.kill('SIGINT'); // do not deliver the message
|
|---|
| 200 | callback(err);
|
|---|
| 201 | });
|
|---|
| 202 |
|
|---|
| 203 | sourceStream.pipe(sendmail.stdin);
|
|---|
| 204 | } else {
|
|---|
| 205 | return callback(new Error('sendmail was not found'));
|
|---|
| 206 | }
|
|---|
| 207 | }
|
|---|
| 208 | }
|
|---|
| 209 |
|
|---|
| 210 | module.exports = SendmailTransport;
|
|---|