Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

smtp-connection

Package Overview
Dependencies
Maintainers
1
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

smtp-connection - npm Package Compare versions

Comparing version 2.5.0 to 2.6.0

7

CHANGELOG.md
# Changelog
## v2.6.0 2016-07-06
* Added support for DSN
* Added new option use8BitMime to indicate that the message might include non-ascii bytes
* Added new info property rejectedErrors that includes errors for failed recipients
* Updated errors to indicate where the error happened (SMTP command, API, CONN)
## v2.5.0 2016-05-11

@@ -4,0 +11,0 @@

2

Gruntfile.js

@@ -8,3 +8,3 @@ 'use strict';

eslint: {
all: ['lib/*.js', 'test/*.js', 'Gruntfile.js', '.eslintrc.js']
all: ['lib/*.js', 'test/*.js', 'Gruntfile.js']
},

@@ -11,0 +11,0 @@

@@ -180,3 +180,3 @@ 'use strict';

if (err) {
this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS');
this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
return;

@@ -195,3 +195,3 @@ }

} catch (E) {
return setImmediate(this._onError.bind(this, E, 'ECONNECTION'));
return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
}

@@ -208,3 +208,3 @@ } else if (this.secureConnection) {

} catch (E) {
return setImmediate(this._onError.bind(this, E, 'ECONNECTION'));
return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
}

@@ -216,3 +216,3 @@ } else {

} catch (E) {
return setImmediate(this._onError.bind(this, E, 'ECONNECTION'));
return setImmediate(this._onError.bind(this, E, 'ECONNECTION', false, 'CONN'));
}

@@ -222,6 +222,8 @@ }

this._connectionTimeout = setTimeout(function () {
this._onError('Connection timeout', 'ETIMEDOUT');
this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
}.bind(this), this.options.connectionTimeout || 60 * 1000);
this._socket.on('error', this._onError.bind(this));
this._socket.on('error', function (err) {
this._onError(err, 'ECONNECTION', false, 'CONN');
}.bind(this));
};

@@ -279,15 +281,15 @@

var authMethod;
this._authMethod = false;
if (this.options.authMethod) {
authMethod = this.options.authMethod.toUpperCase().trim();
this._authMethod = this.options.authMethod.toUpperCase().trim();
} else if (this._auth.xoauth2 && this._supportedAuth.indexOf('XOAUTH2') >= 0) {
authMethod = 'XOAUTH2';
this._authMethod = 'XOAUTH2';
} else if (this._auth.domain && this._supportedAuth.indexOf('NTLM') >= 0) {
authMethod = 'NTLM';
this._authMethod = 'NTLM';
} else {
// use first supported
authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
}
switch (authMethod) {
switch (this._authMethod) {
case 'XOAUTH2':

@@ -329,3 +331,3 @@ this._handleXOauth2Token(false, callback);

return callback(this._formatError('Unknown authentication method "' + authMethod + '"', 'EAUTH'));
return callback(this._formatError('Unknown authentication method "' + this._authMethod + '"', 'EAUTH', false, 'API'));
};

@@ -342,3 +344,3 @@

if (!message) {
return done(this._formatError('Empty message', 'EMESSAGE'));
return done(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
}

@@ -359,3 +361,3 @@

message.on('error', function (err) {
return callback(this._formatError(err, 'ESTREAM'));
return callback(this._formatError(err, 'ESTREAM', false, 'API'));
}.bind(this));

@@ -420,3 +422,3 @@ }

if (this._socket && !this._destroyed && this._currentAction === this._actionGreeting) {
this._onError('Greeting never received', 'ETIMEDOUT');
this._onError('Greeting never received', 'ETIMEDOUT', false, 'CONN');
}

@@ -469,3 +471,3 @@ }.bind(this), this.options.greetingTimeout || 10000);

*/
SMTPConnection.prototype._onError = function (err, type, data) {
SMTPConnection.prototype._onError = function (err, type, data, command) {
clearTimeout(this._connectionTimeout);

@@ -481,3 +483,3 @@ clearTimeout(this._greetingTimeout);

err = this._formatError(err, type, data);
err = this._formatError(err, type, data, command);

@@ -490,3 +492,3 @@ this.logger.error('[%s] %s', this.id, err.message);

SMTPConnection.prototype._formatError = function (message, type, response) {
SMTPConnection.prototype._formatError = function (message, type, response, command) {
var err;

@@ -514,2 +516,6 @@

if (command) {
err.command = command;
}
return err;

@@ -527,3 +533,3 @@ };

if ([this._actionGreeting, this.close].indexOf(this._currentAction) < 0 && !this._destroyed) {
return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION');
return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', false, 'CONN');
}

@@ -549,3 +555,3 @@

SMTPConnection.prototype._onTimeout = function () {
return this._onError(new Error('Timeout'), 'ETIMEDOUT');
return this._onError(new Error('Timeout'), 'ETIMEDOUT', false, 'CONN');
};

@@ -641,3 +647,3 @@

} else {
return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str);
return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str, 'CONN');
}

@@ -689,7 +695,7 @@ };

if (!this._envelope.to.length) {
return callback(this._formatError('No recipients defined', 'EENVELOPE'));
return callback(this._formatError('No recipients defined', 'EENVELOPE', false, 'API'));
}
if (this._envelope.from && /[\r\n<>]/.test(this._envelope.from)) {
return callback(this._formatError('Invalid sender ' + JSON.stringify(this._envelope.from), 'EENVELOPE'));
return callback(this._formatError('Invalid sender ' + JSON.stringify(this._envelope.from), 'EENVELOPE', false, 'API'));
}

@@ -705,3 +711,3 @@

if (!this._envelope.to[i] || /[\r\n<>]/.test(this._envelope.to[i])) {
return callback(this._formatError('Invalid recipient ' + JSON.stringify(this._envelope.to[i]), 'EENVELOPE'));
return callback(this._formatError('Invalid recipient ' + JSON.stringify(this._envelope.to[i]), 'EENVELOPE', false, 'API'));
}

@@ -719,4 +725,13 @@

this._envelope.rejected = [];
this._envelope.rejectedErrors = [];
this._envelope.accepted = [];
if (this._envelope.dsn) {
try {
this._envelope.dsn = this._setDsnEnvelope(this._envelope.dsn);
} catch (err) {
return callback(this._formatError('Invalid dsn ' + err.message, 'EENVELOPE', false, 'API'));
}
}
this._currentAction = function (str) {

@@ -730,7 +745,73 @@ this._actionMAIL(str, callback);

args.push('SMTPUTF8');
this._usingSmtpUtf8 = true;
}
// If the server supports 8BITMIME and the message might contain non-ascii bytes
// then append the 8BITMIME keyword to the MAIL FROM command
if (this._envelope.use8BitMime && this._supportedExtensions.indexOf('8BITMIME') >= 0) {
args.push('BODY=8BITMIME');
this._using8BitMime = true;
}
// If the server supports DSN and the envelope includes an DSN prop
// then append DSN params to the MAIL FROM command
if (this._envelope.dsn && this._supportedExtensions.indexOf('DSN') >= 0) {
if (this._envelope.dsn.ret) {
args.push('RET=' + this._envelope.dsn.ret);
}
if (this._envelope.dsn.envid) {
args.push('ENVID=' + this._envelope.dsn.envid);
}
}
this._sendCommand('MAIL FROM:<' + (this._envelope.from) + '>' + (args.length ? ' ' + args.join(' ') : ''));
};
SMTPConnection.prototype._setDsnEnvelope = function (params) {
var ret = params.ret ? params.ret.toString().toUpperCase() : null;
if (ret && ['FULL', 'HDRS'].indexOf(ret) < 0) {
throw new Error('ret: ' + JSON.stringify(ret));
}
var envid = params.envid ? params.envid.toString() : null;
var notify = params.notify ? params.notify : null;
if (notify) {
if (typeof notify === 'string') {
notify = notify.split(',');
}
notify = notify.map(function (n) {
return n.trim().toUpperCase();
});
var validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
var invaliNotify = notify.filter(function (n) {
return validNotify.indexOf(n) === -1;
});
if (invaliNotify.length || (notify.length > 1 && notify.indexOf('NEVER') >= 0)) {
throw new Error('notify: ' + JSON.stringify(notify.join(',')));
}
notify = notify.join(',');
}
var orcpt = params.orcpt ? params.orcpt.toString() : null;
return {
ret: ret,
envid: envid,
notify: notify,
orcpt: orcpt
};
};
SMTPConnection.prototype._getDsnRcptToArgs = function () {
var args = [];
// If the server supports DSN and the envelope includes an DSN prop
// then append DSN params to the RCPT TO command
if (this._envelope.dsn && this._supportedExtensions.indexOf('DSN') >= 0) {
if (this._envelope.dsn.notify) {
args.push('NOTIFY=' + this._envelope.dsn.notify);
}
if (this._envelope.dsn.orcpt) {
args.push('ORCPT=' + this._envelope.dsn.orcpt);
}
}
return (args.length ? ' ' + args.join(' ') : '');
};
SMTPConnection.prototype._createSendStream = function (callback) {

@@ -779,3 +860,3 @@ var dataStream = new DataStream();

if (str.substr(0, 3) !== '220') {
this._onError(new Error('Invalid greeting from server:\n' + str), 'EPROTOCOL', str);
this._onError(new Error('Invalid greeting from server:\n' + str), 'EPROTOCOL', str, 'CONN');
return;

@@ -801,3 +882,3 @@ }

if (str.charAt(0) !== '2') {
this._onError(new Error('Invalid response for LHLO:\n' + str), 'EPROTOCOL', str);
this._onError(new Error('Invalid response for LHLO:\n' + str), 'EPROTOCOL', str, 'LHLO');
return;

@@ -819,3 +900,3 @@ }

if (str.substr(0, 3) === '421') {
this._onError(new Error('Server terminates connection:\n' + str), 'ECONNECTION', str);
this._onError(new Error('Server terminates connection:\n' + str), 'ECONNECTION', str, 'EHLO');
return;

@@ -826,3 +907,3 @@ }

if (this.options.requireTLS) {
this._onError(new Error('EHLO failed but HELO does not support required STARTTLS:\n' + str), 'ECONNECTION', str);
this._onError(new Error('EHLO failed but HELO does not support required STARTTLS:\n' + str), 'ECONNECTION', str, 'EHLO');
return;

@@ -849,2 +930,12 @@ }

// Detect if the server supports DSN
if (/[ \-]DSN\b/mi.test(str)) {
this._supportedExtensions.push('DSN');
}
// Detect if the server supports 8BITMIME
if (/[ \-]8BITMIME\b/mi.test(str)) {
this._supportedExtensions.push('8BITMIME');
}
// Detect if the server supports PLAIN auth

@@ -881,3 +972,3 @@ if (str.match(/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i)) {

if (str.charAt(0) !== '2') {
this._onError(new Error('Invalid response for EHLO/HELO:\n' + str), 'EPROTOCOL', str);
this._onError(new Error('Invalid response for EHLO/HELO:\n' + str), 'EPROTOCOL', str, 'HELO');
return;

@@ -898,3 +989,3 @@ }

if (str.charAt(0) !== '2') {
this._onError(new Error('Error upgrading connection with STARTTLS'), 'ETLS', str);
this._onError(new Error('Error upgrading connection with STARTTLS'), 'ETLS', str, 'STARTTLS');
return;

@@ -905,3 +996,3 @@ }

if (err) {
this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS');
this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'STARTTLS');
return;

@@ -931,3 +1022,3 @@ }

if (str !== '334 VXNlcm5hbWU6') {
callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str));
callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str, 'AUTH LOGIN'));
return;

@@ -956,3 +1047,3 @@ }

if (!challengeMatch) {
return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str));
return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH NTLM'));
} else {

@@ -1001,3 +1092,3 @@ challengeString = challengeMatch[1];

if (!challengeMatch) {
return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str));
return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
} else {

@@ -1032,3 +1123,3 @@ challengeString = challengeMatch[1];

if (!str.match(/^235\s+/)) {
return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str));
return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH CRAM-MD5'));
}

@@ -1049,3 +1140,3 @@

if (!str.match(/^235\s+/)) {
return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str));
return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH NTLM'));
}

@@ -1067,3 +1158,3 @@

if (str !== '334 UGFzc3dvcmQ6') {
return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str));
return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str, 'AUTH LOGIN'));
}

@@ -1104,3 +1195,3 @@

this.logger.info('[%s] User %s failed to authenticate', this.id, JSON.stringify(this._user));
return callback(this._formatError('Invalid login', 'EAUTH', str));
return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
}

@@ -1119,8 +1210,14 @@

SMTPConnection.prototype._actionMAIL = function (str, callback) {
var message;
if (Number(str.charAt(0)) !== 2) {
return callback(this._formatError('Mail command failed', 'EENVELOPE', str));
if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
message = 'Internationalized mailbox name not allowed';
} else {
message = 'Mail command failed';
}
return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
}
if (!this._envelope.rcptQueue.length) {
return callback(this._formatError('Can\'t send mail - no recipients defined', 'EENVELOPE'));
return callback(this._formatError('Can\'t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
} else {

@@ -1131,3 +1228,3 @@ this._envelope.curRecipient = this._envelope.rcptQueue.shift();

}.bind(this);
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>');
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>' + this._getDsnRcptToArgs());
}

@@ -1142,5 +1239,15 @@ };

SMTPConnection.prototype._actionRCPT = function (str, callback) {
var message, err;
if (Number(str.charAt(0)) !== 2) {
// this is a soft error
if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.curRecipient)) {
message = 'Internationalized mailbox name not allowed';
} else {
message = 'Recipient command failed';
}
this._envelope.rejected.push(this._envelope.curRecipient);
// store error for the failed recipient
err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
err.recipient = this._envelope.curRecipient;
this._envelope.rejectedErrors.push(err);
} else {

@@ -1157,3 +1264,3 @@ this._envelope.accepted.push(this._envelope.curRecipient);

} else {
return callback(this._formatError('Can\'t send mail - all recipients were rejected', 'EENVELOPE', str));
return callback(this._formatError('Can\'t send mail - all recipients were rejected', 'EENVELOPE', str, 'RCPT TO'));
}

@@ -1165,3 +1272,3 @@ } else {

}.bind(this);
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>');
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>' + this._getDsnRcptToArgs());
}

@@ -1179,9 +1286,15 @@ };

if ([2, 3].indexOf(Number(str.charAt(0))) < 0) {
return callback(this._formatError('Data command failed', 'EENVELOPE', str));
return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
}
callback(null, {
var response = {
accepted: this._envelope.accepted,
rejected: this._envelope.rejected
});
};
if (this._envelope.rejectedErrors.length) {
response.rejectedErrors = this._envelope.rejectedErrors;
}
callback(null, response);
};

@@ -1197,3 +1310,3 @@

// Message failed
return callback(this._formatError('Message failed', 'EMESSAGE', str));
return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
} else {

@@ -1214,3 +1327,3 @@ // Message sent succesfully

this.logger.info('[%s] User %s failed to authenticate', this.id, JSON.stringify(this._user));
return callback(this._formatError(err, 'EAUTH'));
return callback(this._formatError(err, 'EAUTH', false, 'AUTH XOAUTH2'));
}

@@ -1217,0 +1330,0 @@ this._sendCommand('AUTH XOAUTH2 ' + token);

{
"name": "smtp-connection",
"version": "2.5.0",
"version": "2.6.0",
"description": "Connect to SMTP servers",

@@ -29,5 +29,5 @@ "main": "lib/smtp-connection.js",

"grunt-cli": "^1.2.0",
"grunt-eslint": "^18.1.0",
"grunt-eslint": "^19.0.0",
"grunt-mocha-test": "^0.12.7",
"mocha": "^2.4.5",
"mocha": "^2.5.3",
"proxy-test-server": "^1.0.0",

@@ -34,0 +34,0 @@ "sinon": "^1.17.4",

@@ -141,2 +141,8 @@ # smtp-connection

* **envelope.to** is the recipient address or an array of addresses
* **envelope.use8BitMime** if `true` then inform the server that this message might contain bytes outside 7bit ascii range
* **envelope.dsn** is the dsn options
* **envelope.dsn.ret** return either the full message 'FULL' or only headers 'HDRS'
* **envelope.dsn.envid** sender's 'envelope identifier' for tracking
* **envelope.dsn.notify** when to send a DSN. Multiple options are OK - array or comma delimited. NEVER must appear by itself. Available options: 'NEVER', 'SUCCESS', 'FAILURE', 'DELAY'
* **envelope.dsn.orcpt** original recipient
* **message** is either a String, Buffer or a Stream. All newlines are converted to \r\n and all dots are escaped automatically, no need to convert anything before.

@@ -149,4 +155,5 @@ * **callback** is the callback to run once the sending is finished or failed. Callback has the following arguments

* **info** information object about accepted and rejected recipients
* **accepted** and array of accepted recipient addresses
* **rejected** and array of rejected recipient addresses
* **accepted** an array of accepted recipient addresses
* **rejected** an array of rejected recipient addresses
* **rejectedErrors** if some recipients were rejected then this property holds an array of error objects for the rejected recipients
* **response** is the last response received from the server

@@ -153,0 +160,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc