smtp-server
Advanced tools
Comparing version 3.0.1 to 3.1.0
# Changelog | ||
## v3.1.0 2017-08-16 | ||
* Added new server option `needsUpgrade` to upgrade sockets to TLS immediately after connection is established. Works with secure: true | ||
## v3.0.0 2017-04-06 | ||
@@ -4,0 +8,0 @@ |
@@ -18,3 +18,2 @@ /* eslint no-console: 0 */ | ||
const server = new SMTPServer({ | ||
// log to console | ||
@@ -21,0 +20,0 @@ logger: true, |
@@ -21,3 +21,2 @@ /* eslint no-console: 0 */ | ||
const server = new SMTPServer({ | ||
// log to console | ||
@@ -53,8 +52,7 @@ logger: true, | ||
// check username and password | ||
if (auth.username === username && | ||
( | ||
auth.method === 'CRAM-MD5' ? | ||
auth.validatePassword(password) : // if cram-md5, validate challenge response | ||
auth.password === password // for other methods match plaintext passwords | ||
) | ||
if ( | ||
auth.username === username && | ||
(auth.method === 'CRAM-MD5' | ||
? auth.validatePassword(password) // if cram-md5, validate challenge response | ||
: auth.password === password) // for other methods match plaintext passwords | ||
) { | ||
@@ -61,0 +59,0 @@ return callback(null, { |
'use strict'; | ||
module.exports = function (grunt) { | ||
module.exports = function(grunt) { | ||
// Project configuration. | ||
@@ -6,0 +5,0 @@ grunt.initConfig({ |
406
lib/sasl.js
@@ -6,4 +6,3 @@ 'use strict'; | ||
const SASL = module.exports = { | ||
const SASL = (module.exports = { | ||
SASL_PLAIN(args, callback) { | ||
@@ -54,3 +53,3 @@ if (args.length > 1) { | ||
'SASL_CRAM-MD5' (args, callback) { | ||
'SASL_CRAM-MD5'(args, callback) { | ||
if (args.length) { | ||
@@ -61,4 +60,5 @@ this.send(501, 'Error: syntax: AUTH CRAM-MD5'); | ||
let challenge = util.format('<%s%s@%s>', | ||
String(Math.random()).replace(/^[0\.]+/, '').substr(0, 8), // random numbers | ||
let challenge = util.format( | ||
'<%s%s@%s>', | ||
String(Math.random()).replace(/^[0.]+/, '').substr(0, 8), // random numbers | ||
Math.floor(Date.now() / 1000), // timestamp | ||
@@ -91,44 +91,62 @@ this.name // hostname | ||
this._server.onAuth({ | ||
method: 'PLAIN', | ||
username, | ||
password | ||
}, this.session, (err, response) => { | ||
this._server.onAuth( | ||
{ | ||
method: 'PLAIN', | ||
username, | ||
password | ||
}, | ||
this.session, | ||
(err, response) => { | ||
if (err) { | ||
this._server.logger.info( | ||
{ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, | ||
'Authentication error for %s using %s. %s', | ||
username, | ||
'PLAIN', | ||
err.message | ||
); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (err) { | ||
this._server.logger.info({ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, 'Authentication error for %s using %s. %s', username, 'PLAIN', err.message); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info( | ||
{ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, | ||
'Authentication failed for %s using %s', | ||
username, | ||
'PLAIN' | ||
); | ||
this.send(response.responseCode || 535, response.message || 'Error: Authentication credentials invalid'); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info({ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, 'Authentication failed for %s using %s', username, 'PLAIN'); | ||
this.send(response.responseCode || 535, response.message || 'Error: Authentication credentials invalid'); | ||
return callback(); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, | ||
'%s authenticated using %s', | ||
username, | ||
'PLAIN' | ||
); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
} | ||
this._server.logger.info({ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, '%s authenticated using %s', username, 'PLAIN'); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
}); | ||
); | ||
}, | ||
@@ -166,44 +184,62 @@ | ||
this._server.onAuth({ | ||
method: 'LOGIN', | ||
username, | ||
password | ||
}, this.session, (err, response) => { | ||
this._server.onAuth( | ||
{ | ||
method: 'LOGIN', | ||
username, | ||
password | ||
}, | ||
this.session, | ||
(err, response) => { | ||
if (err) { | ||
this._server.logger.info( | ||
{ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'LOGIN', | ||
user: username | ||
}, | ||
'Authentication error for %s using %s. %s', | ||
username, | ||
'LOGIN', | ||
err.message | ||
); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (err) { | ||
this._server.logger.info({ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'LOGIN', | ||
user: username | ||
}, 'Authentication error for %s using %s. %s', username, 'LOGIN', err.message); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info( | ||
{ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'LOGIN', | ||
user: username | ||
}, | ||
'Authentication failed for %s using %s', | ||
username, | ||
'LOGIN' | ||
); | ||
this.send(response.responseCode || 535, response.message || 'Error: Authentication credentials invalid'); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info({ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'LOGIN', | ||
user: username | ||
}, 'Authentication failed for %s using %s', username, 'LOGIN'); | ||
this.send(response.responseCode || 535, response.message || 'Error: Authentication credentials invalid'); | ||
return callback(); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, | ||
'%s authenticated using %s', | ||
username, | ||
'LOGIN' | ||
); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
} | ||
this._server.logger.info({ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'PLAIN', | ||
user: username | ||
}, '%s authenticated using %s', username, 'LOGIN'); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
}); | ||
); | ||
}, | ||
@@ -243,45 +279,63 @@ | ||
this._server.onAuth({ | ||
method: 'XOAUTH2', | ||
username, | ||
accessToken | ||
}, this.session, (err, response) => { | ||
this._server.onAuth( | ||
{ | ||
method: 'XOAUTH2', | ||
username, | ||
accessToken | ||
}, | ||
this.session, | ||
(err, response) => { | ||
if (err) { | ||
this._server.logger.info( | ||
{ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'XOAUTH2', | ||
user: username | ||
}, | ||
'Authentication error for %s using %s. %s', | ||
username, | ||
'XOAUTH2', | ||
err.message | ||
); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (err) { | ||
this._server.logger.info({ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'XOAUTH2', | ||
user: username | ||
}, 'Authentication error for %s using %s. %s', username, 'XOAUTH2', err.message); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info( | ||
{ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'XOAUTH2', | ||
user: username | ||
}, | ||
'Authentication failed for %s using %s', | ||
username, | ||
'XOAUTH2' | ||
); | ||
this._nextHandler = SASL.XOAUTH2_error.bind(this); | ||
this.send(response.responseCode || 334, new Buffer(JSON.stringify(response.data || {})).toString('base64')); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info({ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'XOAUTH2', | ||
user: username | ||
}, 'Authentication failed for %s using %s', username, 'XOAUTH2'); | ||
this._nextHandler = SASL.XOAUTH2_error.bind(this); | ||
this.send(response.responseCode || 334, new Buffer(JSON.stringify(response.data || {})).toString('base64')); | ||
return callback(); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'XOAUTH2', | ||
user: username | ||
}, | ||
'%s authenticated using %s', | ||
username, | ||
'XOAUTH2' | ||
); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
} | ||
this._server.logger.info({ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'XOAUTH2', | ||
user: username | ||
}, '%s authenticated using %s', username, 'XOAUTH2'); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
}); | ||
); | ||
}, | ||
@@ -294,3 +348,3 @@ | ||
'CRAM-MD5_token' (canAbort, challenge, token, callback) { | ||
'CRAM-MD5_token'(canAbort, challenge, token, callback) { | ||
token = (token || '').toString().trim(); | ||
@@ -307,48 +361,66 @@ | ||
this._server.onAuth({ | ||
method: 'CRAM-MD5', | ||
username, | ||
validatePassword(password) { | ||
let hmac = crypto.createHmac('md5', password); | ||
return hmac.update(challenge).digest('hex').toLowerCase() === challengeResponse; | ||
} | ||
}, this.session, (err, response) => { | ||
this._server.onAuth( | ||
{ | ||
method: 'CRAM-MD5', | ||
username, | ||
validatePassword(password) { | ||
let hmac = crypto.createHmac('md5', password); | ||
return hmac.update(challenge).digest('hex').toLowerCase() === challengeResponse; | ||
} | ||
}, | ||
this.session, | ||
(err, response) => { | ||
if (err) { | ||
this._server.logger.info( | ||
{ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'CRAM-MD5', | ||
user: username | ||
}, | ||
'Authentication error for %s using %s. %s', | ||
username, | ||
'CRAM-MD5', | ||
err.message | ||
); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (err) { | ||
this._server.logger.info({ | ||
err, | ||
tnx: 'autherror', | ||
cid: this._id, | ||
method: 'CRAM-MD5', | ||
user: username | ||
}, 'Authentication error for %s using %s. %s', username, 'CRAM-MD5', err.message); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info( | ||
{ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'CRAM-MD5', | ||
user: username | ||
}, | ||
'Authentication failed for %s using %s', | ||
username, | ||
'CRAM-MD5' | ||
); | ||
this.send(response.responseCode || 535, response.message || 'Error: Authentication credentials invalid'); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this._server.logger.info({ | ||
tnx: 'authfail', | ||
cid: this._id, | ||
method: 'CRAM-MD5', | ||
user: username | ||
}, 'Authentication failed for %s using %s', username, 'CRAM-MD5'); | ||
this.send(response.responseCode || 535, response.message || 'Error: Authentication credentials invalid'); | ||
return callback(); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'CRAM-MD5', | ||
user: username | ||
}, | ||
'%s authenticated using %s', | ||
username, | ||
'CRAM-MD5' | ||
); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
} | ||
this._server.logger.info({ | ||
tnx: 'auth', | ||
cid: this._id, | ||
method: 'CRAM-MD5', | ||
user: username | ||
}, '%s authenticated using %s', username, 'CRAM-MD5'); | ||
this.session.user = response.user; | ||
this.session.transmissionType = this._transmissionType(); | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
}); | ||
); | ||
} | ||
}; | ||
}); |
@@ -61,4 +61,6 @@ 'use strict'; | ||
this.tlsOptions = this.secure ? this._socket.getCipher() : false; | ||
this.needsUpgrade = !!this._server.options.needsUpgrade; | ||
this.tlsOptions = this.secure && !this.needsUpgrade ? this._socket.getCipher() : false; | ||
// Store remote address for later usage | ||
@@ -111,17 +113,16 @@ this.remoteAddress = (this._socket.remoteAddress || '').replace(/^::ffff:/, ''); | ||
// Setup event handlers for the socket | ||
this._setListeners(); | ||
this._setListeners(() => { | ||
// Check that connection limit is not exceeded | ||
if (this._server.options.maxClients && this._server.connections.size > this._server.options.maxClients) { | ||
return this.send(421, this.name + ' Too many connected clients, try again in a moment'); | ||
} | ||
// Check that connection limit is not exceeded | ||
if (this._server.options.maxClients && this._server.connections.size > this._server.options.maxClients) { | ||
return this.send(421, this.name + ' Too many connected clients, try again in a moment'); | ||
} | ||
if (!this._server.options.useProxy) { | ||
// Keep a small delay for detecting early talkers | ||
setTimeout(() => this.connectionReady(), 100); | ||
} | ||
if (!this._server.options.useProxy) { | ||
// Keep a small delay for detecting early talkers | ||
setTimeout(() => this.connectionReady(), 100); | ||
} | ||
}); | ||
} | ||
connectionReady(next) { | ||
// Resolve hostname for the remote IP | ||
@@ -137,3 +138,3 @@ let reverseCb = (err, hostnames) => { | ||
this.clientHostname = hostnames && hostnames.shift() || '[' + this.remoteAddress + ']'; | ||
this.clientHostname = (hostnames && hostnames.shift()) || '[' + this.remoteAddress + ']'; | ||
@@ -143,8 +144,12 @@ this._resetSession(); | ||
this._server.onConnect(this.session, err => { | ||
this._server.logger.info({ | ||
tnx: 'connection', | ||
cid: this._id, | ||
host: this.remoteAddress, | ||
hostname: this.clientHostname | ||
}, 'Connection from %s', this.clientHostname); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'connection', | ||
cid: this._id, | ||
host: this.remoteAddress, | ||
hostname: this.clientHostname | ||
}, | ||
'Connection from %s', | ||
this.clientHostname | ||
); | ||
@@ -162,3 +167,6 @@ if (err) { | ||
this.send(220, this.name + ' ' + (this._server.options.lmtp ? 'LMTP' : 'ESMTP') + (this._server.options.banner ? ' ' + this._server.options.banner : '')); | ||
this.send( | ||
220, | ||
this.name + ' ' + (this._server.options.lmtp ? 'LMTP' : 'ESMTP') + (this._server.options.banner ? ' ' + this._server.options.banner : '') | ||
); | ||
@@ -182,3 +190,2 @@ if (typeof next === 'function') { | ||
} | ||
} | ||
@@ -203,7 +210,11 @@ | ||
this._socket.write(payload + '\r\n'); | ||
this._server.logger.debug({ | ||
tnx: 'send', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, 'S:', payload); | ||
this._server.logger.debug( | ||
{ | ||
tnx: 'send', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'S:', | ||
payload | ||
); | ||
} | ||
@@ -234,3 +245,3 @@ | ||
*/ | ||
_setListeners() { | ||
_setListeners(callback) { | ||
this._socket.on('close', () => this._onClose()); | ||
@@ -240,2 +251,6 @@ this._socket.on('error', err => this._onError(err)); | ||
this._socket.pipe(this._parser); | ||
if (!this.needsUpgrade) { | ||
return callback(); | ||
} | ||
this.upgrade(() => false, callback); | ||
} | ||
@@ -247,3 +262,3 @@ | ||
*/ | ||
_onClose( /* hadError */ ) { | ||
_onClose(/* hadError */) { | ||
if (this._parser) { | ||
@@ -269,8 +284,12 @@ this._parser.closed = true; | ||
this._server.logger.info({ | ||
tnx: 'close', | ||
cid: this._id, | ||
host: this.remoteAddress, | ||
user: this.session.user && this.session.user.username | ||
}, 'Connection closed to %s', this.clientHostname || this.remoteAddress); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'close', | ||
cid: this._id, | ||
host: this.remoteAddress, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'Connection closed to %s', | ||
this.clientHostname || this.remoteAddress | ||
); | ||
setImmediate(() => this._server.onClose(this.session)); | ||
@@ -286,4 +305,3 @@ } | ||
_onError(err) { | ||
if ((err.code === 'ECONNRESET' || err.code === 'EPIPE') && | ||
(!this.session.envelope || !this.session.envelope.mailFrom)) { | ||
if ((err.code === 'ECONNRESET' || err.code === 'EPIPE') && (!this.session.envelope || !this.session.envelope.mailFrom)) { | ||
// We got a connection error outside transaction. In most cases it means dirty | ||
@@ -295,7 +313,11 @@ // connection ending by the other party, so we can just ignore it | ||
this._server.logger.error({ | ||
err, | ||
tnx: 'error', | ||
user: this.session.user && this.session.user.username | ||
}, '%s', err.message); | ||
this._server.logger.error( | ||
{ | ||
err, | ||
tnx: 'error', | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'%s', | ||
err.message | ||
); | ||
this.emit('error', err); | ||
@@ -321,8 +343,12 @@ } | ||
let commandName = (command || '').toString().split(' ').shift().toUpperCase(); | ||
this._server.logger.debug({ | ||
tnx: 'command', | ||
cid: this._id, | ||
command: commandName, | ||
user: this.session.user && this.session.user.username | ||
}, 'C:', (command || '').toString()); | ||
this._server.logger.debug( | ||
{ | ||
tnx: 'command', | ||
cid: this._id, | ||
command: commandName, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'C:', | ||
(command || '').toString() | ||
); | ||
@@ -342,9 +368,14 @@ let handler; | ||
if (params[1]) { | ||
this._server.logger.info({ | ||
tnx: 'proxy', | ||
cid: this._id, | ||
proxy: params[1].trim().toLowerCase(), | ||
destination: this.remoteAddress, | ||
user: this.session.user && this.session.user.username | ||
}, 'PROXY from %s through %s', params[1].trim().toLowerCase(), this.remoteAddress); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'proxy', | ||
cid: this._id, | ||
proxy: params[1].trim().toLowerCase(), | ||
destination: this.remoteAddress, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'PROXY from %s through %s', | ||
params[1].trim().toLowerCase(), | ||
this.remoteAddress | ||
); | ||
this.remoteAddress = params[1].trim().toLowerCase(); | ||
@@ -446,4 +477,3 @@ if (params[3]) { | ||
command = (command || '').toString().trim().toUpperCase(); | ||
return !this._server.options.disabledCommands.includes(command) && | ||
typeof this['handler_' + command] === 'function'; | ||
return !this._server.options.disabledCommands.includes(command) && typeof this['handler_' + command] === 'function'; | ||
} | ||
@@ -500,3 +530,4 @@ | ||
address = address.split('@'); | ||
if (address.length !== 2 || !address[0] || !address[1]) { // really bad e-mail address validation. was not able to use joi because of the missing unicode support | ||
if (address.length !== 2 || !address[0] || !address[1]) { | ||
// really bad e-mail address validation. was not able to use joi because of the missing unicode support | ||
invalid = true; | ||
@@ -508,6 +539,8 @@ } else { | ||
return invalid ? false : { | ||
address, | ||
args | ||
}; | ||
return invalid | ||
? false | ||
: { | ||
address, | ||
args | ||
}; | ||
} | ||
@@ -520,3 +553,2 @@ | ||
_resetSession() { | ||
let session = this.session; | ||
@@ -748,15 +780,24 @@ | ||
if (this.session.user) { | ||
this._server.logger.info({ | ||
tnx: 'deauth', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, 'User deauthenticated using %s', 'XCLIENT'); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'deauth', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'User deauthenticated using %s', | ||
'XCLIENT' | ||
); | ||
this.session.user = false; | ||
} | ||
} else { | ||
this._server.logger.info({ | ||
tnx: 'auth', | ||
cid: this._id, | ||
user: value | ||
}, '%s authenticated using %s', value, 'XCLIENT'); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'auth', | ||
cid: this._id, | ||
user: value | ||
}, | ||
'%s authenticated using %s', | ||
value, | ||
'XCLIENT' | ||
); | ||
@@ -781,9 +822,14 @@ this.session.user = { | ||
this._server.logger.info({ | ||
tnx: 'xclient', | ||
cid: this._id, | ||
xclientKey: 'ADDR', | ||
xclient: value, | ||
user: this.session.user && this.session.user.username | ||
}, 'XCLIENT from %s through %s', value, this.remoteAddress); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'xclient', | ||
cid: this._id, | ||
xclientKey: 'ADDR', | ||
xclient: value, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'XCLIENT from %s through %s', | ||
value, | ||
this.remoteAddress | ||
); | ||
@@ -801,9 +847,13 @@ // store original value for reference as ADDR:DEFAULT | ||
value = value || ''; | ||
this._server.logger.info({ | ||
tnx: 'xclient', | ||
cid: this._id, | ||
xclientKey: 'NAME', | ||
xclient: value, | ||
user: this.session.user && this.session.user.username | ||
}, 'XCLIENT hostname resolved as "%s"', value); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'xclient', | ||
cid: this._id, | ||
xclientKey: 'NAME', | ||
xclient: value, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'XCLIENT hostname resolved as "%s"', | ||
value | ||
); | ||
@@ -819,9 +869,13 @@ // store original value for reference as NAME:DEFAULT | ||
value = Number(value) || ''; | ||
this._server.logger.info({ | ||
tnx: 'xclient', | ||
cid: this._id, | ||
xclientKey: 'PORT', | ||
xclient: value, | ||
user: this.session.user && this.session.user.username | ||
}, 'XCLIENT remote port resolved as "%s"', value); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'xclient', | ||
cid: this._id, | ||
xclientKey: 'PORT', | ||
xclient: value, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'XCLIENT remote port resolved as "%s"', | ||
value | ||
); | ||
@@ -836,3 +890,3 @@ // store original value for reference as NAME:DEFAULT | ||
default: | ||
// other values are not relevant | ||
// other values are not relevant | ||
} | ||
@@ -852,3 +906,6 @@ this._xClient.set(key, value); | ||
// success | ||
this.send(220, this.name + ' ' + (this._server.options.lmtp ? 'LMTP' : 'ESMTP') + (this._server.options.banner ? ' ' + this._server.options.banner : '')); | ||
this.send( | ||
220, | ||
this.name + ' ' + (this._server.options.lmtp ? 'LMTP' : 'ESMTP') + (this._server.options.banner ? ' ' + this._server.options.banner : '') | ||
); | ||
callback(); | ||
@@ -925,9 +982,14 @@ } | ||
this._server.logger.info({ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'ADDR', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, 'XFORWARD from %s through %s', value, this.remoteAddress); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'ADDR', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'XFORWARD from %s through %s', | ||
value, | ||
this.remoteAddress | ||
); | ||
@@ -945,9 +1007,13 @@ // store original value for reference as ADDR:DEFAULT | ||
value = value || ''; | ||
this._server.logger.info({ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'NAME', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, 'XFORWARD hostname resolved as "%s"', value); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'NAME', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'XFORWARD hostname resolved as "%s"', | ||
value | ||
); | ||
this.clientHostname = value.toLowerCase(); | ||
@@ -957,9 +1023,13 @@ break; | ||
value = Number(value) || 0; | ||
this._server.logger.info({ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'PORT', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, 'XFORWARD port resolved as "%s"', value); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'PORT', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'XFORWARD port resolved as "%s"', | ||
value | ||
); | ||
this.remotePort = value; | ||
@@ -969,13 +1039,17 @@ break; | ||
value = Number(value) || 0; | ||
this._server.logger.info({ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'HELO', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, 'XFORWARD HELO name resolved as "%s"', value); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'xforward', | ||
cid: this._id, | ||
xforwardKey: 'HELO', | ||
xforward: value, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'XFORWARD HELO name resolved as "%s"', | ||
value | ||
); | ||
this.hostNameAppearsAs = value; | ||
break; | ||
default: | ||
// other values are not relevant | ||
// other values are not relevant | ||
} | ||
@@ -999,3 +1073,2 @@ this._xForward.set(key, value); | ||
handler_STARTTLS(command, callback) { | ||
if (this.secure) { | ||
@@ -1007,51 +1080,4 @@ this.send(503, 'Error: TLS already active'); | ||
this.send(220, 'Ready to start TLS'); | ||
this._socket.unpipe(this._parser); | ||
this._upgrading = true; | ||
setImmediate(callback); // resume input stream | ||
let secureContext = this._server.secureContext.get('default'); | ||
let socketOptions = { | ||
secureContext, | ||
isServer: true, | ||
server: this._server.server, | ||
SNICallback: (servername, cb) => { | ||
cb(null, this._server.secureContext.get(servername.toLowerCase().trim()) || this._server.secureContext.get('default')); | ||
} | ||
}; | ||
// Apply additional socket options if these are set in the server options | ||
['requestCert', 'rejectUnauthorized', 'NPNProtocols', 'SNICallback', 'session', 'requestOCSP'].forEach(key => { | ||
if (key in this._server.options) { | ||
socketOptions[key] = this._server.options[key]; | ||
} | ||
}); | ||
// remove all listeners from the original socket besides the error handler | ||
this._socket.removeAllListeners(); | ||
this._socket.on('error', err => this._onError(err)); | ||
// upgrade connection | ||
let secureSocket = new tls.TLSSocket(this._socket, socketOptions); | ||
secureSocket.on('close', () => this._onClose()); | ||
secureSocket.on('error', err => this._onError(err)); | ||
secureSocket.on('clientError', err => this._onError(err)); | ||
secureSocket.setTimeout(this._server.options.socketTimeout || SOCKET_TIMEOUT, () => this._onTimeout()); | ||
secureSocket.on('secure', () => { | ||
this.session.secure = this.secure = true; | ||
this._socket = secureSocket; | ||
this._upgrading = false; | ||
this.session.tlsOptions = this.tlsOptions = this._socket.getCipher(); | ||
let cipher = this.session.tlsOptions && this.session.tlsOptions.name; | ||
this._server.logger.info({ | ||
tnx: 'starttls', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username, | ||
cipher | ||
}, 'Connection upgraded to TLS using ', cipher || 'N/A'); | ||
this._socket.pipe(this._parser); | ||
}); | ||
this.upgrade(callback); | ||
} | ||
@@ -1187,10 +1213,14 @@ | ||
this._server.logger.debug({ | ||
tnx: 'data', | ||
cid: this._id, | ||
bytes: this._parser.dataBytes, | ||
user: this.session.user && this.session.user.username | ||
}, 'C: <%s bytes of DATA>', this._parser.dataBytes); | ||
this._server.logger.debug( | ||
{ | ||
tnx: 'data', | ||
cid: this._id, | ||
bytes: this._parser.dataBytes, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'C: <%s bytes of DATA>', | ||
this._parser.dataBytes | ||
); | ||
if ((typeof this._dataStream === 'object') && (this._dataStream) && (this._dataStream.readable)) { | ||
if (typeof this._dataStream === 'object' && this._dataStream && this._dataStream.readable) { | ||
this._dataStream.removeAllListeners(); | ||
@@ -1233,3 +1263,3 @@ } | ||
if ((typeof this._parser === 'object') && (this._parser)) { | ||
if (typeof this._parser === 'object' && this._parser) { | ||
this._parser.continue(); | ||
@@ -1242,3 +1272,3 @@ } | ||
// do not continue until the stream has actually ended | ||
if ((typeof this._dataStream === 'object') && (this._dataStream) && (this._dataStream.readable)) { | ||
if (typeof this._dataStream === 'object' && this._dataStream && this._dataStream.readable) { | ||
this._dataStream.on('end', () => close(err, message)); | ||
@@ -1283,7 +1313,10 @@ return; | ||
handler_SHELL(command, callback) { | ||
this._server.logger.info({ | ||
tnx: 'shell', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, 'Client tried to invoke SHELL'); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'shell', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'Client tried to invoke SHELL' | ||
); | ||
@@ -1303,7 +1336,10 @@ if (!this.session.isWizard) { | ||
handler_KILL(command, callback) { | ||
this._server.logger.info({ | ||
tnx: 'kill', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, 'Client tried to invoke KILL'); | ||
this._server.logger.info( | ||
{ | ||
tnx: 'kill', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username | ||
}, | ||
'Client tried to invoke KILL' | ||
); | ||
@@ -1314,4 +1350,62 @@ this.send(500, 'Can\'t kill Mom'); | ||
upgrade(callback, secureCallback) { | ||
this._socket.unpipe(this._parser); | ||
this._upgrading = true; | ||
setImmediate(callback); // resume input stream | ||
let secureContext = this._server.secureContext.get('default'); | ||
let socketOptions = { | ||
secureContext, | ||
isServer: true, | ||
server: this._server.server, | ||
SNICallback: (servername, cb) => { | ||
cb(null, this._server.secureContext.get(servername.toLowerCase().trim()) || this._server.secureContext.get('default')); | ||
} | ||
}; | ||
// Apply additional socket options if these are set in the server options | ||
['requestCert', 'rejectUnauthorized', 'NPNProtocols', 'SNICallback', 'session', 'requestOCSP'].forEach(key => { | ||
if (key in this._server.options) { | ||
socketOptions[key] = this._server.options[key]; | ||
} | ||
}); | ||
// remove all listeners from the original socket besides the error handler | ||
this._socket.removeAllListeners(); | ||
this._socket.on('error', err => this._onError(err)); | ||
// upgrade connection | ||
let secureSocket = new tls.TLSSocket(this._socket, socketOptions); | ||
secureSocket.on('close', () => this._onClose()); | ||
secureSocket.on('error', err => this._onError(err)); | ||
secureSocket.on('clientError', err => this._onError(err)); | ||
secureSocket.setTimeout(this._server.options.socketTimeout || SOCKET_TIMEOUT, () => this._onTimeout()); | ||
secureSocket.on('secure', () => { | ||
this.session.secure = this.secure = true; | ||
this._socket = secureSocket; | ||
this._upgrading = false; | ||
this.session.tlsOptions = this.tlsOptions = this._socket.getCipher(); | ||
let cipher = this.session.tlsOptions && this.session.tlsOptions.name; | ||
this._server.logger.info( | ||
{ | ||
tnx: 'starttls', | ||
cid: this._id, | ||
user: this.session.user && this.session.user.username, | ||
cipher | ||
}, | ||
'Connection upgraded to TLS using ', | ||
cipher || 'N/A' | ||
); | ||
this._socket.pipe(this._parser); | ||
if (typeof secureCallback === 'function') { | ||
secureCallback(); | ||
} | ||
}); | ||
} | ||
} | ||
// Expose to the world | ||
module.exports.SMTPConnection = SMTPConnection; |
@@ -47,8 +47,6 @@ 'use strict'; | ||
// setup disabled commands list | ||
this.options.disabledCommands = [].concat(this.options.disabledCommands || []) | ||
.map(command => (command || '').toString().toUpperCase().trim()); | ||
this.options.disabledCommands = [].concat(this.options.disabledCommands || []).map(command => (command || '').toString().toUpperCase().trim()); | ||
// setup allowed auth methods | ||
this.options.authMethods = [].concat(this.options.authMethods || []) | ||
.map(method => (method || '').toString().toUpperCase().trim()); | ||
this.options.authMethods = [].concat(this.options.authMethods || []).map(method => (method || '').toString().toUpperCase().trim()); | ||
@@ -81,4 +79,3 @@ if (!this.options.authMethods.length) { | ||
// setup server listener and connection handler | ||
this.server = (this.options.secure ? tls : net) | ||
.createServer(this.options, socket => this.connect(socket)); | ||
this.server = (this.options.secure && !this.options.needsUpgrade ? tls : net).createServer(this.options, socket => this.connect(socket)); | ||
@@ -125,5 +122,11 @@ // ensure _sharedCreds, fixes an issue in node v4+ where STARTTLS fails because _sharedCreds does not exist | ||
if (connections) { | ||
this.logger.info({ | ||
tnx: 'close' | ||
}, 'Server closing with %s pending connection%s, waiting %s seconds before terminating', connections, connections !== 1 ? 's' : '', timeout / 1000); | ||
this.logger.info( | ||
{ | ||
tnx: 'close' | ||
}, | ||
'Server closing with %s pending connection%s, waiting %s seconds before terminating', | ||
connections, | ||
connections !== 1 ? 's' : '', | ||
timeout / 1000 | ||
); | ||
} | ||
@@ -134,5 +137,10 @@ | ||
if (connections) { | ||
this.logger.info({ | ||
tnx: 'close' | ||
}, 'Closing %s pending connection%s to close the server', connections, connections !== 1 ? 's' : ''); | ||
this.logger.info( | ||
{ | ||
tnx: 'close' | ||
}, | ||
'Closing %s pending connection%s to close the server', | ||
connections, | ||
connections !== 1 ? 's' : '' | ||
); | ||
@@ -189,6 +197,10 @@ this.connections.forEach(connection => { | ||
stream.on('end', () => { | ||
this.logger.info({ | ||
tnx: 'message', | ||
size: chunklen | ||
}, '<received %s bytes>', chunklen); | ||
this.logger.info( | ||
{ | ||
tnx: 'message', | ||
size: chunklen | ||
}, | ||
'<received %s bytes>', | ||
chunklen | ||
); | ||
callback(); | ||
@@ -198,3 +210,3 @@ }); | ||
onClose( /* session */ ) { | ||
onClose(/* session */) { | ||
// do nothing | ||
@@ -234,3 +246,4 @@ } | ||
address.family === 'IPv4' ? address.address : '[' + address.address + ']', | ||
address.port); | ||
address.port | ||
); | ||
} | ||
@@ -244,5 +257,8 @@ | ||
_onClose() { | ||
this.logger.info({ | ||
tnx: 'closed' | ||
}, (this.options.lmtp ? 'LMTP' : 'SMTP') + ' Server closed'); | ||
this.logger.info( | ||
{ | ||
tnx: 'closed' | ||
}, | ||
(this.options.lmtp ? 'LMTP' : 'SMTP') + ' Server closed' | ||
); | ||
this.emit('close'); | ||
@@ -249,0 +265,0 @@ } |
@@ -42,3 +42,3 @@ 'use strict'; | ||
*/ | ||
oncommand( /* command, callback */ ) { | ||
oncommand(/* command, callback */) { | ||
throw new Error('Command handler is not set'); | ||
@@ -54,3 +54,3 @@ } | ||
this._dataMode = true; | ||
this._maxBytes = maxBytes && Number(maxBytes) || Infinity; | ||
this._maxBytes = (maxBytes && Number(maxBytes)) || Infinity; | ||
this.dataBytes = 0; | ||
@@ -65,3 +65,3 @@ this._dataStream = new PassThrough(); | ||
*/ | ||
continue () { | ||
continue() { | ||
if (typeof this._continueCallback === 'function') { | ||
@@ -104,3 +104,2 @@ this._continueCallback(); | ||
if (!this._dataMode) { | ||
newlineRegex = /\r?\n/g; | ||
@@ -137,3 +136,2 @@ data = this._remainder + chunk.toString('binary'); | ||
readLine(); | ||
} else { | ||
@@ -168,3 +166,3 @@ this._feedDataStream(chunk, done); | ||
// check if the first symbol is a escape dot | ||
if (!this.dataBytes && len >= 2 && chunk[0] === 0x2E && chunk[1] === 0x2E) { | ||
if (!this.dataBytes && len >= 2 && chunk[0] === 0x2e && chunk[1] === 0x2e) { | ||
chunk = chunk.slice(1); | ||
@@ -176,9 +174,6 @@ len--; | ||
for (i = 2; i < len - 2; i++) { | ||
// if the dot is the first char in a line | ||
if (chunk[i] === 0x2E && chunk[i - 1] === 0x0A) { | ||
if (chunk[i] === 0x2e && chunk[i - 1] === 0x0a) { | ||
// if the dot matches end terminator | ||
if (Buffer.compare(chunk.slice(i - 2, i + 3), endseq) === 0) { | ||
if (i > 2) { | ||
@@ -196,3 +191,3 @@ buf = chunk.slice(0, i); | ||
// check if the dot is an escape char and remove it | ||
if (chunk[i + 1] === 0x2E) { | ||
if (chunk[i + 1] === 0x2e) { | ||
buf = chunk.slice(0, i); | ||
@@ -222,3 +217,2 @@ | ||
if (this._lastBytes.length < chunk.length) { | ||
buf = chunk.slice(0, chunk.length - this._lastBytes.length); | ||
@@ -238,3 +232,2 @@ this.dataBytes += buf.length; | ||
} | ||
} else { | ||
@@ -283,3 +276,2 @@ // nothing to emit, continue with the input stream | ||
} | ||
} | ||
@@ -286,0 +278,0 @@ |
@@ -9,3 +9,4 @@ 'use strict'; | ||
// obviusly, do not use in production | ||
key: '-----BEGIN RSA PRIVATE KEY-----\n' + | ||
key: | ||
'-----BEGIN RSA PRIVATE KEY-----\n' + | ||
'MIIEpAIBAAKCAQEA6Z5Qqhw+oWfhtEiMHE32Ht94mwTBpAfjt3vPpX8M7DMCTwHs\n' + | ||
@@ -37,3 +38,4 @@ '1xcXvQ4lQ3rwreDTOWdoJeEEy7gMxXqH0jw0WfBx+8IIJU69xstOyT7FRFDvA1yT\n' + | ||
'-----END RSA PRIVATE KEY-----', | ||
cert: '-----BEGIN CERTIFICATE-----\n' + | ||
cert: | ||
'-----BEGIN CERTIFICATE-----\n' + | ||
'MIICpDCCAYwCCQCuVLVKVTXnAjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwls\n' + | ||
@@ -91,3 +93,3 @@ 'b2NhbGhvc3QwHhcNMTUwMjEyMTEzMjU4WhcNMjUwMjA5MTEzMjU4WjAUMRIwEAYD\n' + | ||
Object.keys(opts).forEach(key => result[key] = opts[key]); | ||
Object.keys(opts).forEach(key => (result[key] = opts[key])); | ||
@@ -94,0 +96,0 @@ Object.keys(tlsDefaults).forEach(key => { |
{ | ||
"name": "smtp-server", | ||
"version": "3.0.1", | ||
"description": "Create custom SMTP servers on the fly", | ||
"main": "lib/smtp-server.js", | ||
"scripts": { | ||
"test": "grunt" | ||
}, | ||
"author": "Andris Reinman", | ||
"license": "MIT", | ||
"dependencies": { | ||
"ipv6-normalize": "^1.0.1", | ||
"nodemailer": "^4.0.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^3.5.0", | ||
"eslint-config-nodemailer": "^1.0.0", | ||
"grunt": "^1.0.1", | ||
"grunt-cli": "^1.2.0", | ||
"grunt-eslint": "^19.0.0", | ||
"grunt-mocha-test": "^0.13.2", | ||
"mocha": "^3.2.0" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/andris9/smtp-server.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/andris9/smtp-server/issues" | ||
}, | ||
"keywords": [ | ||
"SMTP" | ||
], | ||
"engines": { | ||
"node": ">=6.0.0" | ||
} | ||
"name": "smtp-server", | ||
"version": "3.1.0", | ||
"description": "Create custom SMTP servers on the fly", | ||
"main": "lib/smtp-server.js", | ||
"scripts": { | ||
"test": "grunt" | ||
}, | ||
"author": "Andris Reinman", | ||
"license": "MIT", | ||
"dependencies": { | ||
"ipv6-normalize": "^1.0.1", | ||
"nodemailer": "^4.0.1" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.1.1", | ||
"eslint-config-nodemailer": "^1.2.0", | ||
"grunt": "^1.0.1", | ||
"grunt-cli": "^1.2.0", | ||
"grunt-eslint": "^20.0.0", | ||
"grunt-mocha-test": "^0.13.2", | ||
"mocha": "^3.5.0" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/andris9/smtp-server.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/andris9/smtp-server/issues" | ||
}, | ||
"keywords": ["SMTP"], | ||
"engines": { | ||
"node": ">=6.0.0" | ||
} | ||
} |
@@ -18,13 +18,15 @@ /* eslint no-unused-expressions:0, prefer-arrow-callback: 0 */ | ||
describe('SMTPServer', function () { | ||
describe('SMTPServer', function() { | ||
this.timeout(10 * 1000); // eslint-disable-line no-invalid-this | ||
describe('Unit tests', function () { | ||
describe('Unit tests', function() { | ||
describe('#_parseAddressCommand', function() { | ||
it('should parse MAIL FROM/RCPT TO', function() { | ||
let conn = new SMTPConnection( | ||
{ | ||
options: {} | ||
}, | ||
{} | ||
); | ||
describe('#_parseAddressCommand', function () { | ||
it('should parse MAIL FROM/RCPT TO', function () { | ||
let conn = new SMTPConnection({ | ||
options: {} | ||
}, {}); | ||
expect(conn._parseAddressCommand('MAIL FROM', 'MAIL FROM:<test@example.com>')).to.deep.equal({ | ||
@@ -58,6 +60,5 @@ address: 'test@example.com', | ||
}); | ||
}); | ||
describe('Plaintext server', function () { | ||
describe('Plaintext server', function() { | ||
let PORT = 1336; | ||
@@ -71,11 +72,11 @@ | ||
beforeEach(function (done) { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
afterEach(function (done) { | ||
afterEach(function(done) { | ||
server.close(done); | ||
}); | ||
it('should connect without TLS', function (done) { | ||
it('should connect without TLS', function(done) { | ||
let connection = new Client({ | ||
@@ -89,3 +90,3 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
connection.quit(); | ||
@@ -95,3 +96,3 @@ }); | ||
it('should connect with TLS', function (done) { | ||
it('should connect with TLS', function(done) { | ||
let connection = new Client({ | ||
@@ -107,3 +108,3 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
connection.quit(); | ||
@@ -113,3 +114,3 @@ }); | ||
it('open multiple connections', function (done) { | ||
it('open multiple connections', function(done) { | ||
let limit = 5; | ||
@@ -120,3 +121,3 @@ let disconnected = 0; | ||
let createConnection = function (callback) { | ||
let createConnection = function(callback) { | ||
let connection = new Client({ | ||
@@ -130,3 +131,3 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
connected++; | ||
@@ -137,3 +138,3 @@ expect(err).to.not.exist; | ||
connection.on('end', function () { | ||
connection.on('end', function() { | ||
disconnected++; | ||
@@ -145,3 +146,3 @@ if (disconnected >= limit) { | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
connected++; | ||
@@ -152,3 +153,3 @@ callback(null, connection); | ||
let connCb = function (err, conn) { | ||
let connCb = function(err, conn) { | ||
expect(err).to.not.exist; | ||
@@ -158,3 +159,3 @@ connections.push(conn); | ||
if (connected >= limit) { | ||
connections.forEach(function (connection) { | ||
connections.forEach(function(connection) { | ||
connection.close(); | ||
@@ -168,6 +169,5 @@ }); | ||
} | ||
}); | ||
it('should reject too many connections', function (done) { | ||
it('should reject too many connections', function(done) { | ||
let limit = 7; | ||
@@ -179,3 +179,3 @@ let expectedErrors = 2; | ||
let createConnection = function (callback) { | ||
let createConnection = function(callback) { | ||
let connection = new Client({ | ||
@@ -189,3 +189,3 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
connected++; | ||
@@ -200,3 +200,3 @@ if (!expectedErrors) { | ||
connection.on('end', function () { | ||
connection.on('end', function() { | ||
disconnected++; | ||
@@ -208,3 +208,3 @@ if (disconnected >= limit) { | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
connected++; | ||
@@ -215,3 +215,3 @@ callback(null, connection); | ||
let connCb = function (err, conn) { | ||
let connCb = function(err, conn) { | ||
expect(err).to.not.exist; | ||
@@ -221,3 +221,3 @@ connections.push(conn); | ||
if (connected >= limit) { | ||
connections.forEach(function (connection) { | ||
connections.forEach(function(connection) { | ||
connection.close(); | ||
@@ -231,6 +231,5 @@ }); | ||
} | ||
}); | ||
it('should close on timeout', function (done) { | ||
it('should close on timeout', function(done) { | ||
let connection = new Client({ | ||
@@ -242,3 +241,3 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
expect(err).to.exist; | ||
@@ -249,3 +248,3 @@ }); | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
// do nothing, wait until timeout occurs | ||
@@ -255,3 +254,3 @@ }); | ||
it('should close on timeout using secure socket', function (done) { | ||
it('should close on timeout using secure socket', function(done) { | ||
let connection = new Client({ | ||
@@ -265,3 +264,3 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
expect(err).to.exist; | ||
@@ -272,3 +271,3 @@ }); | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
// do nothing, wait until timeout occurs | ||
@@ -279,3 +278,3 @@ }); | ||
describe('Plaintext server with no connection limit', function () { | ||
describe('Plaintext server with no connection limit', function() { | ||
this.timeout(60 * 1000); // eslint-disable-line no-invalid-this | ||
@@ -291,7 +290,7 @@ | ||
beforeEach(function (done) { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
it('open multiple connections and close all at once', function (done) { | ||
it('open multiple connections and close all at once', function(done) { | ||
let limit = 100; | ||
@@ -304,3 +303,3 @@ let cleanClose = 4; | ||
let createConnection = function (callback) { | ||
let createConnection = function(callback) { | ||
let connection = new Client({ | ||
@@ -314,7 +313,7 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
expect(err.responseCode).to.equal(421); // Server shutting down | ||
}); | ||
connection.on('end', function () { | ||
connection.on('end', function() { | ||
disconnected++; | ||
@@ -327,3 +326,3 @@ | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
connected++; | ||
@@ -334,3 +333,3 @@ callback(null, connection); | ||
let connCb = function (err, conn) { | ||
let connCb = function(err, conn) { | ||
expect(err).to.not.exist; | ||
@@ -341,3 +340,3 @@ connections.push(conn); | ||
server.close(); | ||
setTimeout(function () { | ||
setTimeout(function() { | ||
for (let i = 0; i < cleanClose; i++) { | ||
@@ -353,7 +352,6 @@ connections[i].quit(); | ||
createConnection(connCb); | ||
}); | ||
}); | ||
describe('Plaintext server with hidden STARTTLS', function () { | ||
describe('Plaintext server with hidden STARTTLS', function() { | ||
let PORT = 1336; | ||
@@ -368,11 +366,11 @@ | ||
beforeEach(function (done) { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
afterEach(function (done) { | ||
afterEach(function(done) { | ||
server.close(done); | ||
}); | ||
it('should connect without TLS', function (done) { | ||
it('should connect without TLS', function(done) { | ||
let connection = new Client({ | ||
@@ -385,3 +383,3 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
expect(connection.secure).to.be.false; | ||
@@ -392,3 +390,3 @@ connection.quit(); | ||
it('should connect with TLS', function (done) { | ||
it('should connect with TLS', function(done) { | ||
let connection = new Client({ | ||
@@ -405,3 +403,3 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
expect(connection.secure).to.be.true; | ||
@@ -413,3 +411,3 @@ connection.quit(); | ||
describe('Plaintext server with no STARTTLS', function () { | ||
describe('Plaintext server with no STARTTLS', function() { | ||
let PORT = 1336; | ||
@@ -436,11 +434,11 @@ | ||
beforeEach(function (done) { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
afterEach(function (done) { | ||
afterEach(function(done) { | ||
server.close(done); | ||
}); | ||
it('should connect without TLS', function (done) { | ||
it('should connect without TLS', function(done) { | ||
let connection = new Client({ | ||
@@ -453,3 +451,3 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
expect(connection.secure).to.be.false; | ||
@@ -460,3 +458,3 @@ connection.quit(); | ||
it('should not connect with TLS', function (done) { | ||
it('should not connect with TLS', function(done) { | ||
let connection = new Client({ | ||
@@ -473,7 +471,7 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
error = err; | ||
}); | ||
connection.on('end', function () { | ||
connection.on('end', function() { | ||
expect(error).to.exist; | ||
@@ -483,3 +481,3 @@ done(); | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
// should not be called | ||
@@ -491,3 +489,3 @@ expect(false).to.be.true; | ||
it('should close after too many unauthenticated commands', function (done) { | ||
it('should close after too many unauthenticated commands', function(done) { | ||
let connection = new Client({ | ||
@@ -499,3 +497,3 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
expect(err).to.exist; | ||
@@ -506,5 +504,5 @@ }); | ||
connection.connect(function () { | ||
let looper = function () { | ||
connection._currentAction = function () { | ||
connection.connect(function() { | ||
let looper = function() { | ||
connection._currentAction = function() { | ||
looper(); | ||
@@ -518,3 +516,3 @@ }; | ||
it('should close after too many unrecognized commands', function (done) { | ||
it('should close after too many unrecognized commands', function(done) { | ||
let connection = new Client({ | ||
@@ -526,3 +524,3 @@ port: PORT, | ||
connection.on('error', function (err) { | ||
connection.on('error', function(err) { | ||
expect(err).to.exist; | ||
@@ -533,27 +531,30 @@ }); | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'testuser', | ||
pass: 'testpass' | ||
}, function (err) { | ||
expect(err).to.not.exist; | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'testuser', | ||
pass: 'testpass' | ||
}, | ||
function(err) { | ||
expect(err).to.not.exist; | ||
let looper = function () { | ||
connection._currentAction = function () { | ||
looper(); | ||
let looper = function() { | ||
connection._currentAction = function() { | ||
looper(); | ||
}; | ||
connection._sendCommand('ZOOP'); | ||
}; | ||
connection._sendCommand('ZOOP'); | ||
}; | ||
looper(); | ||
}); | ||
looper(); | ||
} | ||
); | ||
}); | ||
}); | ||
it('should reject early talker', function (done) { | ||
let socket = net.connect(PORT, '127.0.0.1', function () { | ||
it('should reject early talker', function(done) { | ||
let socket = net.connect(PORT, '127.0.0.1', function() { | ||
let buffers = []; | ||
socket.on('data', function (chunk) { | ||
socket.on('data', function(chunk) { | ||
buffers.push(chunk); | ||
}); | ||
socket.on('end', function () { | ||
socket.on('end', function() { | ||
let data = Buffer.concat(buffers).toString(); | ||
@@ -567,7 +568,7 @@ expect(/^421 /.test(data)).to.be.true; | ||
it('should reject HTTP requests', function (done) { | ||
let socket = net.connect(PORT, '127.0.0.1', function () { | ||
it('should reject HTTP requests', function(done) { | ||
let socket = net.connect(PORT, '127.0.0.1', function() { | ||
let buffers = []; | ||
let started = false; | ||
socket.on('data', function (chunk) { | ||
socket.on('data', function(chunk) { | ||
buffers.push(chunk); | ||
@@ -580,3 +581,3 @@ | ||
}); | ||
socket.on('end', function () { | ||
socket.on('end', function() { | ||
let data = Buffer.concat(buffers).toString(); | ||
@@ -588,6 +589,41 @@ expect(/^421 /m.test(data)).to.be.true; | ||
}); | ||
}); | ||
describe('Secure server', function() { | ||
let PORT = 1336; | ||
let server = new SMTPServer({ | ||
secure: true, | ||
logger: false | ||
}); | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
afterEach(function(done) { | ||
server.close(function() { | ||
done(); | ||
}); | ||
}); | ||
it('should connect to secure server', function(done) { | ||
let connection = new Client({ | ||
port: PORT, | ||
host: '127.0.0.1', | ||
secure: true, | ||
tls: { | ||
rejectUnauthorized: false | ||
} | ||
}); | ||
connection.on('end', done); | ||
connection.connect(function() { | ||
connection.quit(); | ||
}); | ||
}); | ||
}); | ||
describe('Secure server', function () { | ||
describe('Secure server with upgrade', function() { | ||
let PORT = 1336; | ||
@@ -597,11 +633,12 @@ | ||
secure: true, | ||
needsUpgrade: true, | ||
logger: false | ||
}); | ||
beforeEach(function (done) { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
afterEach(function (done) { | ||
server.close(function () { | ||
afterEach(function(done) { | ||
server.close(function() { | ||
done(); | ||
@@ -611,3 +648,3 @@ }); | ||
it('should connect to secure server', function (done) { | ||
it('should connect to secure server', function(done) { | ||
let connection = new Client({ | ||
@@ -624,3 +661,3 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
connection.quit(); | ||
@@ -631,3 +668,3 @@ }); | ||
describe('Authentication tests', function () { | ||
describe('Authentication tests', function() { | ||
let PORT = 1336; | ||
@@ -656,9 +693,3 @@ | ||
} | ||
} else if (auth.username === 'testuser' && | ||
( | ||
auth.method === 'CRAM-MD5' ? | ||
auth.validatePassword('testpass') : | ||
auth.password === 'testpass' | ||
) | ||
) { | ||
} else if (auth.username === 'testuser' && (auth.method === 'CRAM-MD5' ? auth.validatePassword('testpass') : auth.password === 'testpass')) { | ||
return callback(null, { | ||
@@ -675,13 +706,12 @@ user: 'userdata' | ||
beforeEach(function (done) { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
afterEach(function (done) { | ||
afterEach(function(done) { | ||
server.close(done); | ||
}); | ||
describe('PLAIN', function () { | ||
it('should authenticate', function (done) { | ||
describe('PLAIN', function() { | ||
it('should authenticate', function(done) { | ||
let connection = new Client({ | ||
@@ -697,15 +727,18 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'PLAIN' | ||
}, function (err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'PLAIN' | ||
}, | ||
function(err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
}); | ||
it('should fail', function (done) { | ||
it('should fail', function(done) { | ||
let connection = new Client({ | ||
@@ -721,11 +754,14 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'zzzz', | ||
pass: 'yyyy', | ||
method: 'PLAIN' | ||
}, function (err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'zzzz', | ||
pass: 'yyyy', | ||
method: 'PLAIN' | ||
}, | ||
function(err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
@@ -735,5 +771,4 @@ }); | ||
describe('LOGIN', function () { | ||
it('should authenticate', function (done) { | ||
describe('LOGIN', function() { | ||
it('should authenticate', function(done) { | ||
let connection = new Client({ | ||
@@ -750,15 +785,18 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'LOGIN' | ||
}, function (err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'LOGIN' | ||
}, | ||
function(err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
}); | ||
it('should authenticate without STARTTLS', function (done) { | ||
it('should authenticate without STARTTLS', function(done) { | ||
let connection = new Client({ | ||
@@ -773,15 +811,18 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'LOGIN' | ||
}, function (err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'LOGIN' | ||
}, | ||
function(err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
}); | ||
it('should fail', function (done) { | ||
it('should fail', function(done) { | ||
let connection = new Client({ | ||
@@ -797,11 +838,14 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'zzzz', | ||
pass: 'yyyy', | ||
method: 'LOGIN' | ||
}, function (err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'zzzz', | ||
pass: 'yyyy', | ||
method: 'LOGIN' | ||
}, | ||
function(err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
@@ -811,5 +855,4 @@ }); | ||
describe('XOAUTH2', function () { | ||
it('should authenticate', function (done) { | ||
describe('XOAUTH2', function() { | ||
it('should authenticate', function(done) { | ||
let connection = new Client({ | ||
@@ -825,19 +868,25 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
type: 'oauth2', | ||
user: 'testuser', | ||
method: 'XOAUTH2', | ||
oauth2: new XOAuth2({ | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
type: 'oauth2', | ||
user: 'testuser', | ||
accessToken: 'testtoken' | ||
}, false) | ||
}, function (err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
}); | ||
method: 'XOAUTH2', | ||
oauth2: new XOAuth2( | ||
{ | ||
user: 'testuser', | ||
accessToken: 'testtoken' | ||
}, | ||
false | ||
) | ||
}, | ||
function(err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
}); | ||
it('should fail', function (done) { | ||
it('should fail', function(done) { | ||
let connection = new Client({ | ||
@@ -853,15 +902,21 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
type: 'oauth2', | ||
user: 'zzzz', | ||
method: 'XOAUTH2', | ||
oauth2: new XOAuth2({ | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
type: 'oauth2', | ||
user: 'zzzz', | ||
accessToken: 'testtoken' | ||
}, false) | ||
}, function (err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
}); | ||
method: 'XOAUTH2', | ||
oauth2: new XOAuth2( | ||
{ | ||
user: 'zzzz', | ||
accessToken: 'testtoken' | ||
}, | ||
false | ||
) | ||
}, | ||
function(err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
@@ -871,5 +926,4 @@ }); | ||
describe('CRAM-MD5', function () { | ||
it('should authenticate', function (done) { | ||
describe('CRAM-MD5', function() { | ||
it('should authenticate', function(done) { | ||
let connection = new Client({ | ||
@@ -885,15 +939,18 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'CRAM-MD5' | ||
}, function (err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'testuser', | ||
pass: 'testpass', | ||
method: 'CRAM-MD5' | ||
}, | ||
function(err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
}); | ||
it('should fail', function (done) { | ||
it('should fail', function(done) { | ||
let connection = new Client({ | ||
@@ -909,11 +966,14 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'zzzz', | ||
pass: 'yyyy', | ||
method: 'CRAM-MD5' | ||
}, function (err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'zzzz', | ||
pass: 'yyyy', | ||
method: 'CRAM-MD5' | ||
}, | ||
function(err) { | ||
expect(err).to.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
@@ -924,3 +984,3 @@ }); | ||
describe('Mail tests', function () { | ||
describe('Mail tests', function() { | ||
let PORT = 1336; | ||
@@ -937,3 +997,3 @@ | ||
server.onAuth = function (auth, session, callback) { | ||
server.onAuth = function(auth, session, callback) { | ||
if (auth.username === 'testuser' && auth.password === 'testpass') { | ||
@@ -950,3 +1010,3 @@ return callback(null, { | ||
server.onMailFrom = function (address, session, callback) { | ||
server.onMailFrom = function(address, session, callback) { | ||
if (/^deny/i.test(address.address)) { | ||
@@ -958,3 +1018,3 @@ return callback(new Error('Not accepted')); | ||
server.onRcptTo = function (address, session, callback) { | ||
server.onRcptTo = function(address, session, callback) { | ||
if (/^deny/i.test(address.address)) { | ||
@@ -966,3 +1026,3 @@ return callback(new Error('Not accepted')); | ||
server.onData = function (stream, session, callback) { | ||
server.onData = function(stream, session, callback) { | ||
let chunks = []; | ||
@@ -992,4 +1052,4 @@ let chunklen = 0; | ||
beforeEach(function (done) { | ||
server.listen(PORT, '127.0.0.1', function () { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', function() { | ||
connection = new Client({ | ||
@@ -1003,10 +1063,13 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.login({ | ||
user: 'testuser', | ||
pass: 'testpass' | ||
}, function (err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
connection.connect(function() { | ||
connection.login( | ||
{ | ||
user: 'testuser', | ||
pass: 'testpass' | ||
}, | ||
function(err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
} | ||
); | ||
}); | ||
@@ -1016,4 +1079,4 @@ }); | ||
afterEach(function (done) { | ||
connection.on('end', function () { | ||
afterEach(function(done) { | ||
connection.on('end', function() { | ||
server.close(done); | ||
@@ -1024,79 +1087,98 @@ }); | ||
it('should send', function (done) { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, 'testmessage', function (err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(0); | ||
done(); | ||
}); | ||
it('should send', function(done) { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, | ||
'testmessage', | ||
function(err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(0); | ||
done(); | ||
} | ||
); | ||
}); | ||
it('should reject single recipient', function (done) { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com', 'deny-recipient@example.com'] | ||
}, 'testmessage', function (err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(1); | ||
done(); | ||
}); | ||
it('should reject single recipient', function(done) { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com', 'deny-recipient@example.com'] | ||
}, | ||
'testmessage', | ||
function(err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(1); | ||
done(); | ||
} | ||
); | ||
}); | ||
it('should reject sender', function (done) { | ||
connection.send({ | ||
from: 'deny-sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, 'testmessage', function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
it('should reject sender', function(done) { | ||
connection.send( | ||
{ | ||
from: 'deny-sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, | ||
'testmessage', | ||
function(err) { | ||
expect(err).to.exist; | ||
done(); | ||
} | ||
); | ||
}); | ||
it('should reject recipients', function (done) { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['deny-recipient@exmaple.com'] | ||
}, 'testmessage', function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
it('should reject recipients', function(done) { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['deny-recipient@exmaple.com'] | ||
}, | ||
'testmessage', | ||
function(err) { | ||
expect(err).to.exist; | ||
done(); | ||
} | ||
); | ||
}); | ||
it('should reject message', function (done) { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, 'deny-testmessage', function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
it('should reject message', function(done) { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, | ||
'deny-testmessage', | ||
function(err) { | ||
expect(err).to.exist; | ||
done(); | ||
} | ||
); | ||
}); | ||
it('should reject too big message', function (done) { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, new Array(1000).join('testmessage'), function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
it('should reject too big message', function(done) { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, | ||
new Array(1000).join('testmessage'), | ||
function(err) { | ||
expect(err).to.exist; | ||
done(); | ||
} | ||
); | ||
}); | ||
it('should send multiple messages', function (done) { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, 'testmessage 1', function (err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(0); | ||
connection.send({ | ||
it('should send multiple messages', function(done) { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, 'testmessage 2', function (err, status) { | ||
}, | ||
'testmessage 1', | ||
function(err, status) { | ||
expect(err).to.not.exist; | ||
@@ -1106,25 +1188,46 @@ expect(status.accepted.length).to.equal(1); | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, 'deny-testmessage', function (err) { | ||
expect(err).to.exist; | ||
connection.send({ | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, 'testmessage 3', function (err, status) { | ||
}, | ||
'testmessage 2', | ||
function(err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(0); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, | ||
'deny-testmessage', | ||
function(err) { | ||
expect(err).to.exist; | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['recipient@exmaple.com'] | ||
}, | ||
'testmessage 3', | ||
function(err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(0); | ||
done(); | ||
} | ||
); | ||
} | ||
); | ||
} | ||
); | ||
} | ||
); | ||
}); | ||
}); | ||
describe('SMTPUTF8', function () { | ||
it('should allow addresses with UTF-8 characters', function (done) { | ||
describe('SMTPUTF8', function() { | ||
it('should allow addresses with UTF-8 characters', function(done) { | ||
let utf8Address = 'δοκιμή@παράδειγμα.δοκιμή'; | ||
@@ -1140,3 +1243,3 @@ let PORT = 1336; | ||
server.onRcptTo = function (address, session, callback) { | ||
server.onRcptTo = function(address, session, callback) { | ||
expect(utf8Address).to.equal(address.address); | ||
@@ -1146,3 +1249,3 @@ callback(); | ||
server.listen(PORT, '127.0.0.1', function () { | ||
server.listen(PORT, '127.0.0.1', function() { | ||
connection = new Client({ | ||
@@ -1153,16 +1256,20 @@ port: PORT, | ||
connection.on('end', function () { | ||
connection.on('end', function() { | ||
server.close(done); | ||
}); | ||
connection.connect(function () { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: [utf8Address] | ||
}, 'testmessage', function (err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(0); | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: [utf8Address] | ||
}, | ||
'testmessage', | ||
function(err, status) { | ||
expect(err).to.not.exist; | ||
expect(status.accepted.length).to.equal(1); | ||
expect(status.rejected.length).to.equal(0); | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
@@ -1173,4 +1280,4 @@ }); | ||
describe('#onData', function () { | ||
it('should accept a prematurely called continue callback', function (done) { | ||
describe('#onData', function() { | ||
it('should accept a prematurely called continue callback', function(done) { | ||
let PORT = 1336; | ||
@@ -1185,3 +1292,3 @@ | ||
server.onData = function (stream, session, callback) { | ||
server.onData = function(stream, session, callback) { | ||
stream.pipe(fs.createWriteStream('/dev/null')); | ||
@@ -1191,3 +1298,3 @@ callback(); | ||
server.listen(PORT, '127.0.0.1', function () { | ||
server.listen(PORT, '127.0.0.1', function() { | ||
connection = new Client({ | ||
@@ -1198,14 +1305,18 @@ port: PORT, | ||
connection.on('end', function () { | ||
connection.on('end', function() { | ||
server.close(done); | ||
}); | ||
connection.connect(function () { | ||
connection.send({ | ||
from: 'sender@example.com', | ||
to: ['receiver@example.com'] | ||
}, new Array(1024 * 1024).join('#'), function (err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
}); | ||
connection.connect(function() { | ||
connection.send( | ||
{ | ||
from: 'sender@example.com', | ||
to: ['receiver@example.com'] | ||
}, | ||
new Array(1024 * 1024).join('#'), | ||
function(err) { | ||
expect(err).to.not.exist; | ||
connection.quit(); | ||
} | ||
); | ||
}); | ||
@@ -1216,3 +1327,3 @@ }); | ||
describe('PROXY server', function () { | ||
describe('PROXY server', function() { | ||
let PORT = 1336; | ||
@@ -1234,11 +1345,11 @@ | ||
beforeEach(function (done) { | ||
beforeEach(function(done) { | ||
server.listen(PORT, '127.0.0.1', done); | ||
}); | ||
afterEach(function (done) { | ||
afterEach(function(done) { | ||
server.close(done); | ||
}); | ||
it('should rewrite remote address value', function (done) { | ||
it('should rewrite remote address value', function(done) { | ||
let connection = new Client({ | ||
@@ -1252,6 +1363,6 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
let conn; | ||
// get first connection | ||
server.connections.forEach(function (val) { | ||
server.connections.forEach(function(val) { | ||
if (!conn) { | ||
@@ -1270,9 +1381,9 @@ conn = val; | ||
it('should block blacklisted connection', function (done) { | ||
let socket = net.connect(PORT, '127.0.0.1', function () { | ||
it('should block blacklisted connection', function(done) { | ||
let socket = net.connect(PORT, '127.0.0.1', function() { | ||
let buffers = []; | ||
socket.on('data', function (chunk) { | ||
socket.on('data', function(chunk) { | ||
buffers.push(chunk); | ||
}); | ||
socket.on('end', function () { | ||
socket.on('end', function() { | ||
let data = Buffer.concat(buffers).toString(); | ||
@@ -1288,6 +1399,6 @@ expect(data.indexOf('421 ')).to.equal(0); | ||
describe('onClose handler', function () { | ||
describe('onClose handler', function() { | ||
let PORT = 1336; | ||
it('should detect once a connection is closed', function (done) { | ||
it('should detect once a connection is closed', function(done) { | ||
let closed = 0; | ||
@@ -1306,4 +1417,4 @@ let total = 50; | ||
server.listen(PORT, '127.0.0.1', function () { | ||
let createConnection = function () { | ||
server.listen(PORT, '127.0.0.1', function() { | ||
let createConnection = function() { | ||
let connection = new Client({ | ||
@@ -1315,3 +1426,3 @@ port: PORT, | ||
connection.connect(function () { | ||
connection.connect(function() { | ||
setTimeout(() => connection.quit(), 100); | ||
@@ -1318,0 +1429,0 @@ }); |
@@ -12,13 +12,9 @@ /* eslint no-unused-expressions:0, prefer-arrow-callback: 0 */ | ||
describe('SMTPStream', function () { | ||
it('should emit commands', function (done) { | ||
describe('SMTPStream', function() { | ||
it('should emit commands', function(done) { | ||
let stream = new SMTPStream(); | ||
let expecting = [ | ||
new Buffer([0x43, 0x4d, 0x44, 0x31]), | ||
new Buffer([0x43, 0x4d, 0x44, 0x32]), | ||
new Buffer([0x43, 0x4d, 0x44, 0x33]) | ||
]; | ||
let expecting = [new Buffer([0x43, 0x4d, 0x44, 0x31]), new Buffer([0x43, 0x4d, 0x44, 0x32]), new Buffer([0x43, 0x4d, 0x44, 0x33])]; | ||
stream.oncommand = function (cmd, cb) { | ||
stream.oncommand = function(cmd, cb) { | ||
expect(cmd).to.deep.equal(expecting.shift()); | ||
@@ -35,11 +31,8 @@ if (cb) { | ||
it('should start data stream', function (done) { | ||
it('should start data stream', function(done) { | ||
let stream = new SMTPStream(); | ||
let expecting = [ | ||
'DATA', | ||
'QUIT' | ||
]; | ||
let expecting = ['DATA', 'QUIT']; | ||
stream.oncommand = function (cmd, cb) { | ||
stream.oncommand = function(cmd, cb) { | ||
cmd = cmd.toString(); | ||
@@ -52,6 +45,6 @@ expect(cmd).to.deep.equal(expecting.shift()); | ||
datastream = stream.startDataMode(); | ||
datastream.on('data', function (chunk) { | ||
datastream.on('data', function(chunk) { | ||
output += chunk.toString(); | ||
}); | ||
datastream.on('end', function () { | ||
datastream.on('end', function() { | ||
expect(output).to.equal('test1\r\n.test2\r\n.test3\r\n'); | ||
@@ -58,0 +51,0 @@ stream.continue(); |
142958
3465
Updatednodemailer@^4.0.1