Guest User

Untitled

a guest
Nov 25th, 2018
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 47.61 KB | None | 0 0
  1. 'use strict';
  2.  
  3. var packageInfo = require('../package.json');
  4. var EventEmitter = require('events').EventEmitter;
  5. var util = require('util');
  6. var net = require('net');
  7. var tls = require('tls');
  8. var os = require('os');
  9. var crypto = require('crypto');
  10. var DataStream = require('./data-stream');
  11. var PassThrough = require('stream').PassThrough;
  12. var shared = require('nodemailer-shared');
  13. var ntlm = require('httpntlm/ntlm');
  14.  
  15. // default timeout values in ms
  16. var CONNECTION_TIMEOUT = 2 * 60 * 1000; // how much to wait for the connection to be established
  17. var SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity before disconnecting the client
  18. var GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
  19.  
  20. module.exports = SMTPConnection;
  21.  
  22. /**
  23. * Generates a SMTP connection object
  24. *
  25. * Optional options object takes the following possible properties:
  26. *
  27. * * **port** - is the port to connect to (defaults to 25 or 465)
  28. * * **host** - is the hostname or IP address to connect to (defaults to 'localhost')
  29. * * **secure** - use SSL
  30. * * **ignoreTLS** - ignore server support for STARTTLS
  31. * * **requireTLS** - forces the client to use STARTTLS
  32. * * **name** - the name of the client server
  33. * * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
  34. * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 10000)
  35. * * **connectionTimeout** - how many milliseconds to wait for the connection to establish
  36. * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 1 hour)
  37. * * **lmtp** - if true, uses LMTP instead of SMTP protocol
  38. * * **logger** - bunyan compatible logger interface
  39. * * **debug** - if true pass SMTP traffic to the logger
  40. * * **tls** - options for createCredentials
  41. * * **socket** - existing socket to use instead of creating a new one (see: http://nodejs.org/api/net.html#net_class_net_socket)
  42. * * **secured** - boolean indicates that the provided socket has already been upgraded to tls
  43. *
  44. * @constructor
  45. * @namespace SMTP Client module
  46. * @param {Object} [options] Option properties
  47. */
  48. function SMTPConnection(options) {
  49. EventEmitter.call(this);
  50.  
  51. this.id = crypto.randomBytes(8).toString('base64').replace(/\W/g, '');
  52. this.stage = 'init';
  53.  
  54. this.options = options || {};
  55.  
  56. this.secureConnection = !!this.options.secure;
  57. this.alreadySecured = !!this.options.secured;
  58.  
  59. this.port = this.options.port || (this.secureConnection ? 465 : 25);
  60. this.host = this.options.host || 'localhost';
  61.  
  62. if (typeof this.options.secure === 'undefined' && this.port === 465) {
  63. // if secure option is not set but port is 465, then default to secure
  64. this.secureConnection = true;
  65. }
  66.  
  67. this.name = this.options.name || this._getHostname();
  68.  
  69. this.logger = shared.getLogger(this.options);
  70.  
  71. /**
  72. * Expose version nr, just for the reference
  73. * @type {String}
  74. */
  75. this.version = packageInfo.version;
  76.  
  77. /**
  78. * If true, then the user is authenticated
  79. * @type {Boolean}
  80. */
  81. this.authenticated = false;
  82.  
  83. /**
  84. * If set to true, this instance is no longer active
  85. * @private
  86. */
  87. this.destroyed = false;
  88.  
  89. /**
  90. * Defines if the current connection is secure or not. If not,
  91. * STARTTLS can be used if available
  92. * @private
  93. */
  94. this.secure = !!this.secureConnection;
  95.  
  96. /**
  97. * Store incomplete messages coming from the server
  98. * @private
  99. */
  100. this._remainder = '';
  101.  
  102. /**
  103. * Unprocessed responses from the server
  104. * @type {Array}
  105. */
  106. this._responseQueue = [];
  107.  
  108. /**
  109. * The socket connecting to the server
  110. * @publick
  111. */
  112. this._socket = false;
  113.  
  114. /**
  115. * Lists supported auth mechanisms
  116. * @private
  117. */
  118. this._supportedAuth = [];
  119.  
  120. /**
  121. * Includes current envelope (from, to)
  122. * @private
  123. */
  124. this._envelope = false;
  125.  
  126. /**
  127. * Lists supported extensions
  128. * @private
  129. */
  130. this._supportedExtensions = [];
  131.  
  132. /**
  133. * Defines the maximum allowed size for a single message
  134. * @private
  135. */
  136. this._maxAllowedSize = 0;
  137.  
  138. /**
  139. * Function queue to run if a data chunk comes from the server
  140. * @private
  141. */
  142. this._responseActions = [];
  143. this._recipientQueue = [];
  144.  
  145. /**
  146. * Timeout variable for waiting the greeting
  147. * @private
  148. */
  149. this._greetingTimeout = false;
  150.  
  151. /**
  152. * Timeout variable for waiting the connection to start
  153. * @private
  154. */
  155. this._connectionTimeout = false;
  156.  
  157. /**
  158. * If the socket is deemed already closed
  159. * @private
  160. */
  161. this._destroyed = false;
  162.  
  163. /**
  164. * If the socket is already being closed
  165. * @private
  166. */
  167. this._closing = false;
  168. }
  169. util.inherits(SMTPConnection, EventEmitter);
  170.  
  171. /**
  172. * Creates a connection to a SMTP server and sets up connection
  173. * listener
  174. */
  175. SMTPConnection.prototype.connect = function (connectCallback) {
  176. if (typeof connectCallback === 'function') {
  177. this.once('connect', function () {
  178. this.logger.debug('[%s] SMTP handshake finished', this.id);
  179. connectCallback();
  180. }.bind(this));
  181. }
  182.  
  183. var opts = {
  184. port: this.port,
  185. host: this.host
  186. };
  187.  
  188. if (this.options.localAddress) {
  189. opts.localAddress = this.options.localAddress;
  190. }
  191.  
  192. if (this.options.connection) {
  193. // connection is already opened
  194. this._socket = this.options.connection;
  195. if (this.secureConnection && !this.alreadySecured) {
  196. setImmediate(this._upgradeConnection.bind(this, function (err) {
  197. if (err) {
  198. this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
  199. return;
  200. }
  201. this._onConnect();
  202. }.bind(this)));
  203. } else {
  204. setImmediate(this._onConnect.bind(this));
  205. }
  206. } else if (this.options.socket) {
  207. // socket object is set up but not yet connected
  208. this._socket = this.options.socket;
  209. try {
  210. this._socket.connect(this.port, this.host, this._onConnect.bind(this));
  211. } catch (E) {
  212. return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
  213. }
  214. } else if (this.secureConnection) {
  215. // connect using tls
  216. if (this.options.tls) {
  217. Object.keys(this.options.tls).forEach(function (key) {
  218. opts[key] = this.options.tls[key];
  219. }.bind(this));
  220. }
  221. try {
  222. this._socket = tls.connect(this.port, this.host, opts, this._onConnect.bind(this));
  223. } catch (E) {
  224. return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
  225. }
  226. } else {
  227. // connect using plaintext
  228. try {
  229. this._socket = net.connect(opts, this._onConnect.bind(this));
  230. } catch (E) {
  231. return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
  232. }
  233. }
  234.  
  235. this._connectionTimeout = setTimeout(function () {
  236. this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
  237. }.bind(this), this.options.connectionTimeout || CONNECTION_TIMEOUT);
  238.  
  239. this._socket.on('error', function (err) {
  240. this._onError(err, 'ECONNECTION', false, 'CONN');
  241. }.bind(this));
  242. };
  243.  
  244. /**
  245. * Sends QUIT
  246. */
  247. SMTPConnection.prototype.quit = function () {
  248. this._sendCommand('QUIT');
  249. this._responseActions.push(this.close);
  250. };
  251.  
  252. /**
  253. * Closes the connection to the server
  254. */
  255. SMTPConnection.prototype.close = function () {
  256. clearTimeout(this._connectionTimeout);
  257. clearTimeout(this._greetingTimeout);
  258. this._responseActions = [];
  259.  
  260. // allow to run this function only once
  261. if (this._closing) {
  262. return;
  263. }
  264. this._closing = true;
  265.  
  266. var closeMethod = 'end';
  267.  
  268. if (this.stage === 'init') {
  269. // Close the socket immediately when connection timed out
  270. closeMethod = 'destroy';
  271. }
  272.  
  273. this.logger.debug('[%s] Closing connection to the server using "%s"', this.id, closeMethod);
  274.  
  275. var socket = this._socket && this._socket.socket || this._socket;
  276.  
  277. if (socket && !socket.destroyed) {
  278. try {
  279. this._socket[closeMethod]();
  280. } catch (E) {
  281. // just ignore
  282. }
  283. }
  284.  
  285. this._destroy();
  286. };
  287.  
  288. /**
  289. * Authenticate user
  290. */
  291. SMTPConnection.prototype.login = function (authData, callback) {
  292. this._auth = authData || {};
  293. this._user = this._auth.xoauth2 && this._auth.xoauth2.options && this._auth.xoauth2.options.user || this._auth.user || '';
  294.  
  295. this._authMethod = false;
  296. if (this.options.authMethod) {
  297. this._authMethod = this.options.authMethod.toUpperCase().trim();
  298. } else if (this._auth.xoauth2 && this._supportedAuth.indexOf('XOAUTH2') >= 0) {
  299. this._authMethod = 'XOAUTH2';
  300. } else if (this._auth.domain && this._supportedAuth.indexOf('NTLM') >= 0) {
  301. this._authMethod = 'NTLM';
  302. } else {
  303. // use first supported
  304. this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
  305. }
  306.  
  307. switch (this._authMethod) {
  308. case 'XOAUTH2':
  309. this._handleXOauth2Token(false, callback);
  310. return;
  311. case 'LOGIN':
  312. this._responseActions.push(function (str) {
  313. this._actionAUTH_LOGIN_USER(str, callback);
  314. }.bind(this));
  315. this._sendCommand('AUTH LOGIN');
  316. return;
  317. case 'PLAIN':
  318. this._responseActions.push(function (str) {
  319. this._actionAUTHComplete(str, callback);
  320. }.bind(this));
  321. this._sendCommand('AUTH PLAIN ' + new Buffer(
  322. //this._auth.user+'\u0000'+
  323. '\u0000' + // skip authorization identity as it causes problems with some servers
  324. this._auth.user + '\u0000' +
  325. this._auth.pass, 'utf-8').toString('base64'));
  326. return;
  327. case 'CRAM-MD5':
  328. this._responseActions.push(function (str) {
  329. this._actionAUTH_CRAM_MD5(str, callback);
  330. }.bind(this));
  331. this._sendCommand('AUTH CRAM-MD5');
  332. return;
  333. case 'NTLM':
  334. this._responseActions.push(function (str) {
  335. this._actionAUTH_NTLM_TYPE1(str, callback);
  336. }.bind(this));
  337. this._sendCommand('AUTH ' + ntlm.createType1Message({
  338. domain: this._auth.domain || '',
  339. workstation: this._auth.workstation || ''
  340. }));
  341. return;
  342. }
  343.  
  344. return callback(this._formatError('Unknown authentication method "' + this._authMethod + '"', 'EAUTH', false, 'API'));
  345. };
  346.  
  347. /**
  348. * Sends a message
  349. *
  350. * @param {Object} envelope Envelope object, {from: addr, to: [addr]}
  351. * @param {Object} message String, Buffer or a Stream
  352. * @param {Function} callback Callback to return once sending is completed
  353. */
  354. SMTPConnection.prototype.send = function (envelope, message, done) {
  355. if (!message) {
  356. return done(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
  357. }
  358.  
  359. // reject larger messages than allowed
  360. if (this._maxAllowedSize && envelope.size > this._maxAllowedSize) {
  361. return setImmediate(function () {
  362. done(this._formatError('Message size larger than allowed ' + this._maxAllowedSize, 'EMESSAGE', false, 'MAIL FROM'));
  363. }.bind(this));
  364. }
  365.  
  366. // ensure that callback is only called once
  367. var returned = false;
  368. var callback = function () {
  369. if (returned) {
  370. return;
  371. }
  372. returned = true;
  373.  
  374. done.apply(null, Array.prototype.slice.call(arguments));
  375. };
  376.  
  377. if (typeof message.on === 'function') {
  378. message.on('error', function (err) {
  379. return callback(this._formatError(err, 'ESTREAM', false, 'API'));
  380. }.bind(this));
  381. }
  382.  
  383. this._setEnvelope(envelope, function (err, info) {
  384. if (err) {
  385. return callback(err);
  386. }
  387. var stream = this._createSendStream(function (err, str) {
  388. if (err) {
  389. return callback(err);
  390. }
  391. info.response = str;
  392. return callback(null, info);
  393. });
  394. if (typeof message.pipe === 'function') {
  395. message.pipe(stream);
  396. } else {
  397. stream.write(message);
  398. stream.end();
  399. }
  400.  
  401. }.bind(this));
  402. };
  403.  
  404. /**
  405. * Resets connection state
  406. *
  407. * @param {Function} callback Callback to return once connection is reset
  408. */
  409. SMTPConnection.prototype.reset = function (callback) {
  410. this._sendCommand('RSET');
  411. this._responseActions.push(function (str) {
  412. if (str.charAt(0) !== '2') {
  413. return callback(this._formatError('Could not reset session state:\n' + str, 'EPROTOCOL', str, 'RSET'));
  414. }
  415. this._envelope = false;
  416. return callback(null, true);
  417. }.bind(this));
  418. };
  419.  
  420. /**
  421. * Connection listener that is run when the connection to
  422. * the server is opened
  423. *
  424. * @event
  425. */
  426. SMTPConnection.prototype._onConnect = function () {
  427. clearTimeout(this._connectionTimeout);
  428.  
  429. this.logger.info('[%s] %s established to %s:%s', this.id, this.secure ? 'Secure connection' : 'Connection', this._socket.remoteAddress, this._socket.remotePort);
  430.  
  431. if (this._destroyed) {
  432. // Connection was established after we already had canceled it
  433. this.close();
  434. return;
  435. }
  436.  
  437. this.stage = 'connected';
  438.  
  439. // clear existing listeners for the socket
  440. this._socket.removeAllListeners('data');
  441. this._socket.removeAllListeners('timeout');
  442. this._socket.removeAllListeners('close');
  443. this._socket.removeAllListeners('end');
  444.  
  445. this._socket.on('data', this._onData.bind(this));
  446. this._socket.once('close', this._onClose.bind(this));
  447. this._socket.once('end', this._onEnd.bind(this));
  448.  
  449. this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
  450. this._socket.on('timeout', this._onTimeout.bind(this));
  451.  
  452. this._greetingTimeout = setTimeout(function () {
  453. // if still waiting for greeting, give up
  454. if (this._socket && !this._destroyed && this._responseActions[0] === this._actionGreeting) {
  455. this._onError('Greeting never received', 'ETIMEDOUT', false, 'CONN');
  456. }
  457. }.bind(this), this.options.greetingTimeout || GREETING_TIMEOUT);
  458.  
  459. this._responseActions.push(this._actionGreeting);
  460.  
  461. // we have a 'data' listener set up so resume socket if it was paused
  462. this._socket.resume();
  463. };
  464.  
  465. /**
  466. * 'data' listener for data coming from the server
  467. *
  468. * @event
  469. * @param {Buffer} chunk Data chunk coming from the server
  470. */
  471. SMTPConnection.prototype._onData = function (chunk) {
  472. if (this._destroyed || !chunk || !chunk.length) {
  473. return;
  474. }
  475.  
  476. var data = (chunk || '').toString('binary');
  477. var lines = (this._remainder + data).split(/\r?\n/);
  478. var lastline;
  479.  
  480. this._remainder = lines.pop();
  481.  
  482. for (var i = 0, len = lines.length; i < len; i++) {
  483. if (this._responseQueue.length) {
  484. lastline = this._responseQueue[this._responseQueue.length - 1];
  485. if (/^\d+\-/.test(lastline.split('\n').pop())) {
  486. this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
  487. continue;
  488. }
  489. }
  490. this._responseQueue.push(lines[i]);
  491. }
  492.  
  493. this._processResponse();
  494. };
  495.  
  496. /**
  497. * 'error' listener for the socket
  498. *
  499. * @event
  500. * @param {Error} err Error object
  501. * @param {String} type Error name
  502. */
  503. SMTPConnection.prototype._onError = function (err, type, data, command) {
  504. clearTimeout(this._connectionTimeout);
  505. clearTimeout(this._greetingTimeout);
  506.  
  507. if (this._destroyed) {
  508. // just ignore, already closed
  509. // this might happen when a socket is canceled because of reached timeout
  510. // but the socket timeout error itself receives only after
  511. return;
  512. }
  513.  
  514. err = this._formatError(err, type, data, command);
  515.  
  516. this.logger.error('[%s] %s', this.id, err.message);
  517.  
  518. this.emit('error', err);
  519. this.close();
  520. };
  521.  
  522. SMTPConnection.prototype._formatError = function (message, type, response, command) {
  523. var err;
  524.  
  525. if (/Error\]$/i.test(Object.prototype.toString.call(message))) {
  526. err = message;
  527. } else {
  528. err = new Error(message);
  529. }
  530.  
  531. if (type && type !== 'Error') {
  532. err.code = type;
  533. }
  534.  
  535. if (response) {
  536. err.response = response;
  537. err.message += ': ' + response;
  538. }
  539.  
  540. var responseCode = typeof response === 'string' && Number((response.match(/^\d+/) || [])[0]) || false;
  541. if (responseCode) {
  542. err.responseCode = responseCode;
  543. }
  544.  
  545. if (command) {
  546. err.command = command;
  547. }
  548.  
  549. return err;
  550. };
  551.  
  552. /**
  553. * 'close' listener for the socket
  554. *
  555. * @event
  556. */
  557. SMTPConnection.prototype._onClose = function () {
  558. this.logger.info('[%s] Connection closed', this.id);
  559.  
  560. if ([this._actionGreeting, this.close].indexOf(this._responseActions[0]) < 0 && !this._destroyed) {
  561. return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', false, 'CONN');
  562. }
  563.  
  564. this._destroy();
  565. };
  566.  
  567. /**
  568. * 'end' listener for the socket
  569. *
  570. * @event
  571. */
  572. SMTPConnection.prototype._onEnd = function () {
  573. this._destroy();
  574. };
  575.  
  576. /**
  577. * 'timeout' listener for the socket
  578. *
  579. * @event
  580. */
  581. SMTPConnection.prototype._onTimeout = function () {
  582. return this._onError(new Error('Timeout'), 'ETIMEDOUT', false, 'CONN');
  583. };
  584.  
  585. /**
  586. * Destroys the client, emits 'end'
  587. */
  588. SMTPConnection.prototype._destroy = function () {
  589. if (this._destroyed) {
  590. return;
  591. }
  592. this._destroyed = true;
  593. this.emit('end');
  594. };
  595.  
  596. /**
  597. * Upgrades the connection to TLS
  598. *
  599. * @param {Function} callback Callback function to run when the connection
  600. * has been secured
  601. */
  602. SMTPConnection.prototype._upgradeConnection = function (callback) {
  603. // do not remove all listeners or it breaks node v0.10 as there's
  604. // apparently a 'finish' event set that would be cleared as well
  605.  
  606. // we can safely keep 'error', 'end', 'close' etc. events
  607. this._socket.removeAllListeners('data'); // incoming data is going to be gibberish from this point onwards
  608. this._socket.removeAllListeners('timeout'); // timeout will be re-set for the new socket object
  609.  
  610. var socketPlain = this._socket;
  611. var opts = {
  612. socket: this._socket,
  613. host: this.host
  614. };
  615.  
  616. Object.keys(this.options.tls || {}).forEach(function (key) {
  617. opts[key] = this.options.tls[key];
  618. }.bind(this));
  619.  
  620. this._socket = tls.connect(opts, function () {
  621. this.secure = true;
  622. this._socket.on('data', this._onData.bind(this));
  623.  
  624. socketPlain.removeAllListeners('close');
  625. socketPlain.removeAllListeners('end');
  626.  
  627. return callback(null, true);
  628. }.bind(this));
  629.  
  630. this._socket.on('error', this._onError.bind(this));
  631. this._socket.once('close', this._onClose.bind(this));
  632. this._socket.once('end', this._onEnd.bind(this));
  633.  
  634. this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT); // 10 min.
  635. this._socket.on('timeout', this._onTimeout.bind(this));
  636.  
  637. // resume in case the socket was paused
  638. socketPlain.resume();
  639. };
  640.  
  641. /**
  642. * Processes queued responses from the server
  643. *
  644. * @param {Boolean} force If true, ignores _processing flag
  645. */
  646. SMTPConnection.prototype._processResponse = function () {
  647. if (!this._responseQueue.length) {
  648. return false;
  649. }
  650.  
  651. var str = (this._responseQueue.shift() || '').toString();
  652.  
  653. if (/^\d+\-/.test(str.split('\n').pop())) {
  654. // keep waiting for the final part of multiline response
  655. return;
  656. }
  657.  
  658. if (this.options.debug) {
  659. this.logger.debug('[%s] S: %s', this.id, str.replace(/\r?\n$/, ''));
  660. }
  661.  
  662. if (!str.trim()) { // skip unexpected empty lines
  663. setImmediate(this._processResponse.bind(this, true));
  664. }
  665.  
  666. var action = this._responseActions.shift();
  667.  
  668. if (typeof action === 'function') {
  669. action.call(this, str);
  670. setImmediate(this._processResponse.bind(this, true));
  671. } else {
  672. return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str, 'CONN');
  673. }
  674. };
  675.  
  676. /**
  677. * Send a command to the server, append \r\n
  678. *
  679. * @param {String} str String to be sent to the server
  680. */
  681. SMTPConnection.prototype._sendCommand = function (str) {
  682. if (this._destroyed) {
  683. // Connection already closed, can't send any more data
  684. return;
  685. }
  686.  
  687. if (this._socket.destroyed) {
  688. return this.close();
  689. }
  690.  
  691. if (this.options.debug) {
  692. this.logger.debug('[%s] C: %s', this.id, (str || '').toString().replace(/\r?\n$/, ''));
  693. }
  694.  
  695. this._socket.write(new Buffer(str + '\r\n', 'utf-8'));
  696. };
  697.  
  698. /**
  699. * Initiates a new message by submitting envelope data, starting with
  700. * MAIL FROM: command
  701. *
  702. * @param {Object} envelope Envelope object in the form of
  703. * {from:'...', to:['...']}
  704. * or
  705. * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
  706. */
  707. SMTPConnection.prototype._setEnvelope = function (envelope, callback) {
  708. var args = [];
  709. var useSmtpUtf8 = false;
  710.  
  711. this._envelope = envelope || {};
  712. this._envelope.from = (this._envelope.from && this._envelope.from.address || this._envelope.from || '').toString().trim();
  713.  
  714. this._envelope.to = [].concat(this._envelope.to || []).map(function (to) {
  715. return (to && to.address || to || '').toString().trim();
  716. });
  717.  
  718. if (!this._envelope.to.length) {
  719. return callback(this._formatError('No recipients defined', 'EENVELOPE', false, 'API'));
  720. }
  721.  
  722. if (this._envelope.from && /[\r\n<>]/.test(this._envelope.from)) {
  723. return callback(this._formatError('Invalid sender ' + JSON.stringify(this._envelope.from), 'EENVELOPE', false, 'API'));
  724. }
  725.  
  726. // check if the sender address uses only ASCII characters,
  727. // otherwise require usage of SMTPUTF8 extension
  728. if (/[\x80-\uFFFF]/.test(this._envelope.from)) {
  729. useSmtpUtf8 = true;
  730. }
  731.  
  732. for (var i = 0, len = this._envelope.to.length; i < len; i++) {
  733. if (!this._envelope.to[i] || /[\r\n<>]/.test(this._envelope.to[i])) {
  734. return callback(this._formatError('Invalid recipient ' + JSON.stringify(this._envelope.to[i]), 'EENVELOPE', false, 'API'));
  735. }
  736.  
  737. // check if the recipients addresses use only ASCII characters,
  738. // otherwise require usage of SMTPUTF8 extension
  739. if (/[\x80-\uFFFF]/.test(this._envelope.to[i])) {
  740. useSmtpUtf8 = true;
  741. }
  742. }
  743.  
  744. // clone the recipients array for latter manipulation
  745. this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
  746. this._envelope.rejected = [];
  747. this._envelope.rejectedErrors = [];
  748. this._envelope.accepted = [];
  749.  
  750. if (this._envelope.dsn) {
  751. try {
  752. this._envelope.dsn = this._setDsnEnvelope(this._envelope.dsn);
  753. } catch (err) {
  754. return callback(this._formatError('Invalid dsn ' + err.message, 'EENVELOPE', false, 'API'));
  755. }
  756. }
  757.  
  758. this._responseActions.push(function (str) {
  759. this._actionMAIL(str, callback);
  760. }.bind(this));
  761.  
  762. // If the server supports SMTPUTF8 and the envelope includes an internationalized
  763. // email address then append SMTPUTF8 keyword to the MAIL FROM command
  764. if (useSmtpUtf8 && this._supportedExtensions.indexOf('SMTPUTF8') >= 0) {
  765. args.push('SMTPUTF8');
  766. this._usingSmtpUtf8 = true;
  767. }
  768.  
  769. // If the server supports 8BITMIME and the message might contain non-ascii bytes
  770. // then append the 8BITMIME keyword to the MAIL FROM command
  771. if (this._envelope.use8BitMime && this._supportedExtensions.indexOf('8BITMIME') >= 0) {
  772. args.push('BODY=8BITMIME');
  773. this._using8BitMime = true;
  774. }
  775.  
  776. if (this._envelope.size && this._supportedExtensions.indexOf('SIZE') >= 0) {
  777. args.push('SIZE=' + this._envelope.size);
  778. }
  779.  
  780. // If the server supports DSN and the envelope includes an DSN prop
  781. // then append DSN params to the MAIL FROM command
  782. if (this._envelope.dsn && this._supportedExtensions.indexOf('DSN') >= 0) {
  783. if (this._envelope.dsn.ret) {
  784. args.push('RET=' + this._envelope.dsn.ret);
  785. }
  786. if (this._envelope.dsn.envid) {
  787. args.push('ENVID=' + this._envelope.dsn.envid);
  788. }
  789. }
  790.  
  791. this._sendCommand('MAIL FROM:<' + (this._envelope.from) + '>' + (args.length ? ' ' + args.join(' ') : ''));
  792. };
  793.  
  794. SMTPConnection.prototype._setDsnEnvelope = function (params) {
  795. var ret = params.ret ? params.ret.toString().toUpperCase() : null;
  796. if (ret && ['FULL', 'HDRS'].indexOf(ret) < 0) {
  797. throw new Error('ret: ' + JSON.stringify(ret));
  798. }
  799. var envid = params.envid ? params.envid.toString() : null;
  800. var notify = params.notify ? params.notify : null;
  801. if (notify) {
  802. if (typeof notify === 'string') {
  803. notify = notify.split(',');
  804. }
  805. notify = notify.map(function (n) {
  806. return n.trim().toUpperCase();
  807. });
  808. var validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
  809. var invaliNotify = notify.filter(function (n) {
  810. return validNotify.indexOf(n) === -1;
  811. });
  812. if (invaliNotify.length || (notify.length > 1 && notify.indexOf('NEVER') >= 0)) {
  813. throw new Error('notify: ' + JSON.stringify(notify.join(',')));
  814. }
  815. notify = notify.join(',');
  816. }
  817. var orcpt = params.orcpt ? params.orcpt.toString() : null;
  818. return {
  819. ret: ret,
  820. envid: envid,
  821. notify: notify,
  822. orcpt: orcpt
  823. };
  824. };
  825.  
  826. SMTPConnection.prototype._getDsnRcptToArgs = function () {
  827. var args = [];
  828. // If the server supports DSN and the envelope includes an DSN prop
  829. // then append DSN params to the RCPT TO command
  830. if (this._envelope.dsn && this._supportedExtensions.indexOf('DSN') >= 0) {
  831. if (this._envelope.dsn.notify) {
  832. args.push('NOTIFY=' + this._envelope.dsn.notify);
  833. }
  834. if (this._envelope.dsn.orcpt) {
  835. args.push('ORCPT=' + this._envelope.dsn.orcpt);
  836. }
  837. }
  838. return (args.length ? ' ' + args.join(' ') : '');
  839. };
  840.  
  841. SMTPConnection.prototype._createSendStream = function (callback) {
  842. var dataStream = new DataStream();
  843. var logStream;
  844.  
  845. if (this.options.lmtp) {
  846. this._envelope.accepted.forEach(function (recipient, i) {
  847. var final = i === this._envelope.accepted.length - 1;
  848. this._responseActions.push(function (str) {
  849. this._actionLMTPStream(recipient, final, str, callback);
  850. }.bind(this));
  851. }.bind(this));
  852. } else {
  853. this._responseActions.push(function (str) {
  854. this._actionSMTPStream(str, callback);
  855. }.bind(this));
  856. }
  857.  
  858. dataStream.pipe(this._socket, {
  859. end: false
  860. });
  861.  
  862. if (this.options.debug) {
  863. logStream = new PassThrough();
  864. logStream.on('readable', function () {
  865. var chunk;
  866. while ((chunk = logStream.read())) {
  867. this.logger.debug('[%s] C: %s', this.id, chunk.toString('binary').replace(/\r?\n$/, ''));
  868. }
  869. }.bind(this));
  870. dataStream.pipe(logStream);
  871. }
  872.  
  873. dataStream.once('end', function () {
  874. this.logger.info('[%s] C: <%s bytes encoded mime message (source size %s bytes)>', this.id, dataStream.outByteCount, dataStream.inByteCount);
  875. }.bind(this));
  876.  
  877. return dataStream;
  878. };
  879.  
  880. /** ACTIONS **/
  881.  
  882. /**
  883. * Will be run after the connection is created and the server sends
  884. * a greeting. If the incoming message starts with 220 initiate
  885. * SMTP session by sending EHLO command
  886. *
  887. * @param {String} str Message from the server
  888. */
  889. SMTPConnection.prototype._actionGreeting = function (str) {
  890. clearTimeout(this._greetingTimeout);
  891.  
  892. if (str.substr(0, 3) !== '220') {
  893. this._onError(new Error('Invalid greeting from server:\n' + str), 'EPROTOCOL', str, 'CONN');
  894. return;
  895. }
  896.  
  897. if (this.options.lmtp) {
  898. this._responseActions.push(this._actionLHLO);
  899. this._sendCommand('LHLO ' + this.name);
  900. } else {
  901. this._responseActions.push(this._actionEHLO);
  902. this._sendCommand('EHLO ' + this.name);
  903. }
  904. };
  905.  
  906. /**
  907. * Handles server response for LHLO command. If it yielded in
  908. * error, emit 'error', otherwise treat this as an EHLO response
  909. *
  910. * @param {String} str Message from the server
  911. */
  912. SMTPConnection.prototype._actionLHLO = function (str) {
  913. if (str.charAt(0) !== '2') {
  914. this._onError(new Error('Invalid response for LHLO:\n' + str), 'EPROTOCOL', str, 'LHLO');
  915. return;
  916. }
  917.  
  918. this._actionEHLO(str);
  919. };
  920.  
  921. /**
  922. * Handles server response for EHLO command. If it yielded in
  923. * error, try HELO instead, otherwise initiate TLS negotiation
  924. * if STARTTLS is supported by the server or move into the
  925. * authentication phase.
  926. *
  927. * @param {String} str Message from the server
  928. */
  929. SMTPConnection.prototype._actionEHLO = function (str) {
  930. var match;
  931.  
  932. if (str.substr(0, 3) === '421') {
  933. this._onError(new Error('Server terminates connection:\n' + str), 'ECONNECTION', str, 'EHLO');
  934. return;
  935. }
  936.  
  937. if (str.charAt(0) !== '2') {
  938. if (this.options.requireTLS) {
  939. this._onError(new Error('EHLO failed but HELO does not support required STARTTLS:\n' + str), 'ECONNECTION', str, 'EHLO');
  940. return;
  941. }
  942.  
  943. // Try HELO instead
  944. this._responseActions.push(this._actionHELO);
  945. this._sendCommand('HELO ' + this.name);
  946. return;
  947. }
  948.  
  949. // Detect if the server supports STARTTLS
  950. if (!this.secure && !this.options.ignoreTLS && (/[ \-]STARTTLS\b/mi.test(str) || this.options.requireTLS)) {
  951. this._sendCommand('STARTTLS');
  952. this._responseActions.push(this._actionSTARTTLS);
  953. return;
  954. }
  955.  
  956. // Detect if the server supports SMTPUTF8
  957. if (/[ \-]SMTPUTF8\b/mi.test(str)) {
  958. this._supportedExtensions.push('SMTPUTF8');
  959. }
  960.  
  961. // Detect if the server supports DSN
  962. if (/[ \-]DSN\b/mi.test(str)) {
  963. this._supportedExtensions.push('DSN');
  964. }
  965.  
  966. // Detect if the server supports 8BITMIME
  967. if (/[ \-]8BITMIME\b/mi.test(str)) {
  968. this._supportedExtensions.push('8BITMIME');
  969. }
  970.  
  971. // Detect if the server supports PIPELINING
  972. if (/[ \-]PIPELINING\b/mi.test(str)) {
  973. this._supportedExtensions.push('PIPELINING');
  974. }
  975.  
  976. // Detect if the server supports PLAIN auth
  977. if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i.test(str)) {
  978. this._supportedAuth.push('PLAIN');
  979. }
  980.  
  981. // Detect if the server supports LOGIN auth
  982. if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i.test(str)) {
  983. this._supportedAuth.push('LOGIN');
  984. }
  985.  
  986. // Detect if the server supports CRAM-MD5 auth
  987. if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i.test(str)) {
  988. this._supportedAuth.push('CRAM-MD5');
  989. }
  990.  
  991. // Detect if the server supports XOAUTH2 auth
  992. if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i.test(str)) {
  993. this._supportedAuth.push('XOAUTH2');
  994. }
  995.  
  996. // Detect if the server supports SIZE extensions (and the max allowed size)
  997. if ((match = str.match(/[ \-]SIZE(?:\s+(\d+))?/mi))) {
  998. this._supportedExtensions.push('SIZE');
  999. this._maxAllowedSize = Number(match[1]) || 0;
  1000. }
  1001.  
  1002. this.emit('connect');
  1003. };
  1004.  
  1005. /**
  1006. * Handles server response for HELO command. If it yielded in
  1007. * error, emit 'error', otherwise move into the authentication phase.
  1008. *
  1009. * @param {String} str Message from the server
  1010. */
  1011. SMTPConnection.prototype._actionHELO = function (str) {
  1012. if (str.charAt(0) !== '2') {
  1013. this._onError(new Error('Invalid response for EHLO/HELO:\n' + str), 'EPROTOCOL', str, 'HELO');
  1014. return;
  1015. }
  1016.  
  1017. this.emit('connect');
  1018. };
  1019.  
  1020. /**
  1021. * Handles server response for STARTTLS command. If there's an error
  1022. * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
  1023. * succeedes restart the EHLO
  1024. *
  1025. * @param {String} str Message from the server
  1026. */
  1027. SMTPConnection.prototype._actionSTARTTLS = function (str) {
  1028. if (str.charAt(0) !== '2') {
  1029. if (this.options.opportunisticTLS) {
  1030. this.logger.info('[%s] Failed STARTTLS upgrade, continuing unencrypted', this.id);
  1031. return this.emit('connect');
  1032. }
  1033. this._onError(new Error('Error upgrading connection with STARTTLS'), 'ETLS', str, 'STARTTLS');
  1034. return;
  1035. }
  1036.  
  1037. this._upgradeConnection(function (err, secured) {
  1038. if (err) {
  1039. this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'STARTTLS');
  1040. return;
  1041. }
  1042.  
  1043. this.logger.info('[%s] Connection upgraded with STARTTLS', this.id);
  1044.  
  1045. if (secured) {
  1046. // restart session
  1047. this._responseActions.push(this._actionEHLO);
  1048. this._sendCommand('EHLO ' + this.name);
  1049. } else {
  1050. this.emit('connect');
  1051. }
  1052. }.bind(this));
  1053. };
  1054.  
  1055. /**
  1056. * Handle the response for AUTH LOGIN command. We are expecting
  1057. * '334 VXNlcm5hbWU6' (base64 for 'Username:'). Data to be sent as
  1058. * response needs to be base64 encoded username.
  1059. *
  1060. * @param {String} str Message from the server
  1061. */
  1062. SMTPConnection.prototype._actionAUTH_LOGIN_USER = function (str, callback) {
  1063. if (str !== '334 VXNlcm5hbWU6') {
  1064. callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str, 'AUTH LOGIN'));
  1065. return;
  1066. }
  1067.  
  1068. this._responseActions.push(function (str) {
  1069. this._actionAUTH_LOGIN_PASS(str, callback);
  1070. }.bind(this));
  1071.  
  1072. this._sendCommand(new Buffer(this._auth.user + '', 'utf-8').toString('base64'));
  1073. };
  1074.  
  1075. /**
  1076. * Handle the response for AUTH NTLM, which should be a
  1077. * '334 <challenge string>'. See http://davenport.sourceforge.net/ntlm.html
  1078. * We already sent the Type1 message, the challenge is a Type2 message, we
  1079. * need to respond with a Type3 message.
  1080. *
  1081. * @param {String} str Message from the server
  1082. */
  1083. SMTPConnection.prototype._actionAUTH_NTLM_TYPE1 = function (str, callback) {
  1084. var challengeMatch = str.match(/^334\s+(.+)$/);
  1085. var challengeString = '';
  1086.  
  1087. if (!challengeMatch) {
  1088. return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH NTLM'));
  1089. } else {
  1090. challengeString = challengeMatch[1];
  1091. }
  1092.  
  1093. if (!/^NTLM/i.test(challengeString)) {
  1094. challengeString = 'NTLM ' + challengeString;
  1095. }
  1096.  
  1097. var type2Message = ntlm.parseType2Message(challengeString, callback);
  1098. if (!type2Message) {
  1099. return;
  1100. }
  1101.  
  1102. var type3Message = ntlm.createType3Message(type2Message, {
  1103. domain: this._auth.domain || '',
  1104. workstation: this._auth.workstation || '',
  1105. username: this._auth.user,
  1106. password: this._auth.pass
  1107. });
  1108.  
  1109. type3Message = type3Message.substring(5); // remove the "NTLM " prefix
  1110.  
  1111. this._responseActions.push(function (str) {
  1112. this._actionAUTH_NTLM_TYPE3(str, callback);
  1113. }.bind(this));
  1114.  
  1115. this._sendCommand(type3Message);
  1116. };
  1117.  
  1118. /**
  1119. * Handle the response for AUTH CRAM-MD5 command. We are expecting
  1120. * '334 <challenge string>'. Data to be sent as response needs to be
  1121. * base64 decoded challenge string, MD5 hashed using the password as
  1122. * a HMAC key, prefixed by the username and a space, and finally all
  1123. * base64 encoded again.
  1124. *
  1125. * @param {String} str Message from the server
  1126. */
  1127. SMTPConnection.prototype._actionAUTH_CRAM_MD5 = function (str, callback) {
  1128. var challengeMatch = str.match(/^334\s+(.+)$/);
  1129. var challengeString = '';
  1130.  
  1131. if (!challengeMatch) {
  1132. return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
  1133. } else {
  1134. challengeString = challengeMatch[1];
  1135. }
  1136.  
  1137. // Decode from base64
  1138. var base64decoded = new Buffer(challengeString, 'base64').toString('ascii'),
  1139. hmac_md5 = crypto.createHmac('md5', this._auth.pass);
  1140.  
  1141. hmac_md5.update(base64decoded);
  1142.  
  1143. var hex_hmac = hmac_md5.digest('hex'),
  1144. prepended = this._auth.user + ' ' + hex_hmac;
  1145.  
  1146. this._responseActions.push(function (str) {
  1147. this._actionAUTH_CRAM_MD5_PASS(str, callback);
  1148. }.bind(this));
  1149.  
  1150.  
  1151. this._sendCommand(new Buffer(prepended).toString('base64'));
  1152. };
  1153.  
  1154. /**
  1155. * Handles the response to CRAM-MD5 authentication, if there's no error,
  1156. * the user can be considered logged in. Start waiting for a message to send
  1157. *
  1158. * @param {String} str Message from the server
  1159. */
  1160. SMTPConnection.prototype._actionAUTH_CRAM_MD5_PASS = function (str, callback) {
  1161. if (!str.match(/^235\s+/)) {
  1162. return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH CRAM-MD5'));
  1163. }
  1164.  
  1165. this.logger.info('[%s] User %s authenticated', this.id, JSON.stringify(this._user));
  1166. this.authenticated = true;
  1167. callback(null, true);
  1168. };
  1169.  
  1170. /**
  1171. * Handles the TYPE3 response for NTLM authentication, if there's no error,
  1172. * the user can be considered logged in. Start waiting for a message to send
  1173. *
  1174. * @param {String} str Message from the server
  1175. */
  1176. SMTPConnection.prototype._actionAUTH_NTLM_TYPE3 = function (str, callback) {
  1177. if (!str.match(/^235\s+/)) {
  1178. return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH NTLM'));
  1179. }
  1180.  
  1181. this.logger.info('[%s] User %s authenticated', this.id, JSON.stringify(this._user));
  1182. this.authenticated = true;
  1183. callback(null, true);
  1184. };
  1185.  
  1186. /**
  1187. * Handle the response for AUTH LOGIN command. We are expecting
  1188. * '334 UGFzc3dvcmQ6' (base64 for 'Password:'). Data to be sent as
  1189. * response needs to be base64 encoded password.
  1190. *
  1191. * @param {String} str Message from the server
  1192. */
  1193. SMTPConnection.prototype._actionAUTH_LOGIN_PASS = function (str, callback) {
  1194. if (str !== '334 UGFzc3dvcmQ6') {
  1195. return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str, 'AUTH LOGIN'));
  1196. }
  1197.  
  1198. this._responseActions.push(function (str) {
  1199. this._actionAUTHComplete(str, callback);
  1200. }.bind(this));
  1201.  
  1202. this._sendCommand(new Buffer(this._auth.pass + '', 'utf-8').toString('base64'));
  1203. };
  1204.  
  1205. /**
  1206. * Handles the response for authentication, if there's no error,
  1207. * the user can be considered logged in. Start waiting for a message to send
  1208. *
  1209. * @param {String} str Message from the server
  1210. */
  1211. SMTPConnection.prototype._actionAUTHComplete = function (str, isRetry, callback) {
  1212. if (!callback && typeof isRetry === 'function') {
  1213. callback = isRetry;
  1214. isRetry = undefined;
  1215. }
  1216.  
  1217. if (str.substr(0, 3) === '334') {
  1218. this._responseActions.push(function (str) {
  1219. if (isRetry || !this._auth.xoauth2 || typeof this._auth.xoauth2 !== 'object') {
  1220. this._actionAUTHComplete(str, true, callback);
  1221. } else {
  1222. setTimeout(this._handleXOauth2Token.bind(this, true, callback), Math.random() * 4000 + 1000);
  1223. }
  1224. }.bind(this));
  1225. this._sendCommand('');
  1226. return;
  1227. }
  1228.  
  1229. if (str.charAt(0) !== '2') {
  1230. this.logger.info('[%s] User %s failed to authenticate', this.id, JSON.stringify(this._user));
  1231. return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
  1232. }
  1233.  
  1234. this.logger.info('[%s] User %s authenticated', this.id, JSON.stringify(this._user));
  1235. this.authenticated = true;
  1236. callback(null, true);
  1237. };
  1238.  
  1239. /**
  1240. * Handle response for a MAIL FROM: command
  1241. *
  1242. * @param {String} str Message from the server
  1243. */
  1244. SMTPConnection.prototype._actionMAIL = function (str, callback) {
  1245. var message, curRecipient;
  1246. if (Number(str.charAt(0)) !== 2) {
  1247. if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
  1248. message = 'Internationalized mailbox name not allowed';
  1249. } else {
  1250. message = 'Mail command failed';
  1251. }
  1252. return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
  1253. }
  1254.  
  1255. if (!this._envelope.rcptQueue.length) {
  1256. return callback(this._formatError('Can\'t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
  1257. } else {
  1258. this._recipientQueue = [];
  1259.  
  1260. if (this._supportedExtensions.indexOf('PIPELINING') >= 0) {
  1261. while (this._envelope.rcptQueue.length) {
  1262. curRecipient = this._envelope.rcptQueue.shift();
  1263. this._recipientQueue.push(curRecipient);
  1264. this._responseActions.push(function (str) {
  1265. this._actionRCPT(str, callback);
  1266. }.bind(this));
  1267. this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
  1268. }
  1269. } else {
  1270. curRecipient = this._envelope.rcptQueue.shift();
  1271. this._recipientQueue.push(curRecipient);
  1272. this._responseActions.push(function (str) {
  1273. this._actionRCPT(str, callback);
  1274. }.bind(this));
  1275. this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
  1276. }
  1277. }
  1278. };
  1279.  
  1280. /**
  1281. * Handle response for a RCPT TO: command
  1282. *
  1283. * @param {String} str Message from the server
  1284. */
  1285. SMTPConnection.prototype._actionRCPT = function (str, callback) {
  1286. var message, err, curRecipient = this._recipientQueue.shift();
  1287. if (Number(str.charAt(0)) !== 2) {
  1288. // this is a soft error
  1289. if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
  1290. message = 'Internationalized mailbox name not allowed';
  1291. } else {
  1292. message = 'Recipient command failed';
  1293. }
  1294. this._envelope.rejected.push(curRecipient);
  1295. // store error for the failed recipient
  1296. err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
  1297. err.recipient = curRecipient;
  1298. this._envelope.rejectedErrors.push(err);
  1299. } else {
  1300. this._envelope.accepted.push(curRecipient);
  1301. }
  1302.  
  1303. if (!this._envelope.rcptQueue.length && !this._recipientQueue.length) {
  1304. if (this._envelope.rejected.length < this._envelope.to.length) {
  1305. this._responseActions.push(function (str) {
  1306. this._actionDATA(str, callback);
  1307. }.bind(this));
  1308. this._sendCommand('DATA');
  1309. } else {
  1310. err = this._formatError('Can\'t send mail - all recipients were rejected', 'EENVELOPE', str, 'RCPT TO');
  1311. err.rejected = this._envelope.rejected;
  1312. err.rejectedErrors = this._envelope.rejectedErrors;
  1313. return callback(err);
  1314. }
  1315. } else if (this._envelope.rcptQueue.length) {
  1316. curRecipient = this._envelope.rcptQueue.shift();
  1317. this._recipientQueue.push(curRecipient);
  1318. this._responseActions.push(function (str) {
  1319. this._actionRCPT(str, callback);
  1320. }.bind(this));
  1321. this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
  1322. }
  1323. };
  1324.  
  1325. /**
  1326. * Handle response for a DATA command
  1327. *
  1328. * @param {String} str Message from the server
  1329. */
  1330. SMTPConnection.prototype._actionDATA = function (str, callback) {
  1331. // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
  1332. // some servers might use 250 instead, so lets check for 2 or 3 as the first digit
  1333. if ([2, 3].indexOf(Number(str.charAt(0))) < 0) {
  1334. return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
  1335. }
  1336.  
  1337. var response = {
  1338. accepted: this._envelope.accepted,
  1339. rejected: this._envelope.rejected
  1340. };
  1341.  
  1342. if (this._envelope.rejectedErrors.length) {
  1343. response.rejectedErrors = this._envelope.rejectedErrors;
  1344. }
  1345.  
  1346. callback(null, response);
  1347. };
  1348.  
  1349. /**
  1350. * Handle response for a DATA stream when using SMTP
  1351. * We expect a single response that defines if the sending succeeded or failed
  1352. *
  1353. * @param {String} str Message from the server
  1354. */
  1355. SMTPConnection.prototype._actionSMTPStream = function (str, callback) {
  1356. if (Number(str.charAt(0)) !== 2) {
  1357. // Message failed
  1358. return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
  1359. } else {
  1360. // Message sent succesfully
  1361. return callback(null, str);
  1362. }
  1363. };
  1364.  
  1365. /**
  1366. * Handle response for a DATA stream
  1367. * We expect a separate response for every recipient. All recipients can either
  1368. * succeed or fail separately
  1369. *
  1370. * @param {String} recipient The recipient this response applies to
  1371. * @param {Boolean} final Is this the final recipient?
  1372. * @param {String} str Message from the server
  1373. */
  1374. SMTPConnection.prototype._actionLMTPStream = function (recipient, final, str, callback) {
  1375. var err;
  1376. if (Number(str.charAt(0)) !== 2) {
  1377. // Message failed
  1378. err = this._formatError('Message failed for recipient ' + recipient, 'EMESSAGE', str, 'DATA');
  1379. err.recipient = recipient;
  1380. this._envelope.rejected.push(recipient);
  1381. this._envelope.rejectedErrors.push(err);
  1382. for (var i = 0, len = this._envelope.accepted.length; i < len; i++) {
  1383. if (this._envelope.accepted[i] === recipient) {
  1384. this._envelope.accepted.splice(i, 1);
  1385. }
  1386. }
  1387. }
  1388. if (final) {
  1389. return callback(null, str);
  1390. }
  1391. };
  1392.  
  1393. SMTPConnection.prototype._handleXOauth2Token = function (isRetry, callback) {
  1394. this._responseActions.push(function (str) {
  1395. this._actionAUTHComplete(str, isRetry, callback);
  1396. }.bind(this));
  1397.  
  1398. if (this._auth.xoauth2 && typeof this._auth.xoauth2 === 'object') {
  1399. this._auth.xoauth2[isRetry ? 'generateToken' : 'getToken'](function (err, token) {
  1400. if (err) {
  1401. this.logger.info('[%s] User %s failed to authenticate', this.id, JSON.stringify(this._user));
  1402. return callback(this._formatError(err, 'EAUTH', false, 'AUTH XOAUTH2'));
  1403. }
  1404. this._sendCommand('AUTH XOAUTH2 ' + token);
  1405. }.bind(this));
  1406. } else {
  1407. this._sendCommand('AUTH XOAUTH2 ' + this._buildXOAuth2Token(this._auth.user, this._auth.xoauth2));
  1408. }
  1409. };
  1410.  
  1411. /**
  1412. * Builds a login token for XOAUTH2 authentication command
  1413. *
  1414. * @param {String} user E-mail address of the user
  1415. * @param {String} token Valid access token for the user
  1416. * @return {String} Base64 formatted login token
  1417. */
  1418. SMTPConnection.prototype._buildXOAuth2Token = function (user, token) {
  1419. var authData = [
  1420. 'user=' + (user || ''),
  1421. 'auth=Bearer ' + token,
  1422. '',
  1423. ''
  1424. ];
  1425. return new Buffer(authData.join('\x01')).toString('base64');
  1426. };
  1427.  
  1428. SMTPConnection.prototype._getHostname = function () {
  1429. // defaul hostname is machine hostname or [IP]
  1430. var defaultHostname = os.hostname() || '';
  1431.  
  1432. // ignore if not FQDN
  1433. if (defaultHostname.indexOf('.') < 0) {
  1434. defaultHostname = '[127.0.0.1]';
  1435. }
  1436.  
  1437. // IP should be enclosed in []
  1438. if (defaultHostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
  1439. defaultHostname = '[' + defaultHostname + ']';
  1440. }
  1441.  
  1442. return defaultHostname;
  1443. };
Add Comment
Please, Sign In to add comment