index.js 6.4 KB

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