smtp-server
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -26,12 +26,25 @@ 'use strict'; | ||
// By default only PLAIN and LOGIN are enabled | ||
authMethods: ['PLAIN', 'LOGIN', 'CRAM-MD5'], | ||
// Setup authentication | ||
// Allow only users with username 'testuser' and password 'testpass' | ||
onAuth: function(auth, session, callback) { | ||
if (auth.username !== 'testuser' && auth.password !== 'testpass') { | ||
callback(new Error('Authentication failed')); | ||
var username = 'testuser'; | ||
var password = 'testpass'; | ||
// 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 | ||
) | ||
) { | ||
return callback(null, { | ||
user: 'userdata' // value could be an user id, or an user object etc. This value can be accessed from session.user afterwards | ||
}); | ||
} | ||
return callback(null, { | ||
user: 'userdata' // value could be an user id, or an user object etc. This value can be accessed from session.user afterwards | ||
}); | ||
return callback(new Error('Authentication failed')); | ||
}, | ||
@@ -38,0 +51,0 @@ |
'use strict'; | ||
var util = require('util'); | ||
var crypto = require('crypto'); | ||
var SASL = module.exports = { | ||
@@ -50,2 +53,19 @@ | ||
'SASL_CRAM-MD5': function(args, callback) { | ||
if (args.length) { | ||
this.send(501, 'Error: syntax: AUTH CRAM-MD5'); | ||
return callback(); | ||
} | ||
var challenge = util.format('<%s%s@%s>', | ||
String(Math.random()).replace(/^[0\.]+/, '').substr(0, 8), // random numbers | ||
Math.floor(Date.now() / 1000), // timestamp | ||
this.name // hostname | ||
); | ||
this._nextHandler = SASL['CRAM-MD5_token'].bind(this, true, challenge); | ||
this.send(334, new Buffer(challenge).toString('base64')); | ||
return callback(); | ||
}, | ||
PLAIN_token: function(canAbort, token, callback) { | ||
@@ -69,3 +89,3 @@ token = (token || '').toString().trim(); | ||
this._server.logger.info('[%s] Trying to authenticate "%s"', this._id, username); | ||
this._server.logger.info('[%s] Trying to authenticate "%s" with %s', this._id, username, 'PLAIN'); | ||
this._server.onAuth({ | ||
@@ -88,2 +108,3 @@ method: 'PLAIN', | ||
this._server.logger.info('[%s] User "%s" authenticated'); | ||
this.session.user = response.user; | ||
@@ -127,3 +148,3 @@ | ||
this._server.logger.info('[%s] Trying to authenticate "%s"', this._id, username); | ||
this._server.logger.info('[%s] Trying to authenticate "%s" with %s', this._id, username, 'LOGIN'); | ||
this._server.onAuth({ | ||
@@ -146,2 +167,3 @@ method: 'LOGIN', | ||
this._server.logger.info('[%s] User "%s" authenticated'); | ||
this.session.user = response.user; | ||
@@ -187,3 +209,3 @@ | ||
this._server.logger.info('[%s] Trying to authenticate "%s"', this._id, username); | ||
this._server.logger.info('[%s] Trying to authenticate "%s" with %s', this._id, username, 'XOAUTH2'); | ||
this._server.onAuth({ | ||
@@ -207,2 +229,3 @@ method: 'XOAUTH2', | ||
this._server.logger.info('[%s] User "%s" authenticated'); | ||
this.session.user = response.user; | ||
@@ -219,3 +242,45 @@ | ||
return callback(); | ||
}, | ||
'CRAM-MD5_token': function(canAbort, challenge, token, callback) { | ||
token = (token || '').toString().trim(); | ||
if (canAbort && token === '*') { | ||
this.send(501, 'Authentication aborted'); | ||
return callback(); | ||
} | ||
var tokenParts = new Buffer(token, 'base64').toString().split(' '); | ||
var username = tokenParts.shift(); | ||
var challengeResponse = (tokenParts.shift() || '').toLowerCase(); | ||
this._server.logger.info('[%s] Trying to authenticate "%s" with %s', this._id, username, 'CRAM-MD5'); | ||
this._server.onAuth({ | ||
method: 'CRAM-MD5', | ||
username: username, | ||
validatePassword: function(password) { | ||
var hmac = crypto.createHmac('md5', password); | ||
return hmac.update(challenge).digest('hex').toLowerCase() === challengeResponse; | ||
} | ||
}, this.session, function(err, response) { | ||
if (err) { | ||
this._server.logger.info('[%s] Authentication error for "%s"\n%s', this._id, username, err.message); | ||
this.send(err.responseCode || 535, err.message); | ||
return callback(); | ||
} | ||
if (!response.user) { | ||
this.send(response.responseCode || 535, response.message || 'Error: Authentication credentials invalid'); | ||
return callback(); | ||
} | ||
this._server.logger.info('[%s] User "%s" authenticated'); | ||
this.session.user = response.user; | ||
this.send(235, 'Authentication successful'); | ||
callback(); | ||
}.bind(this)); | ||
} | ||
}; |
@@ -51,4 +51,21 @@ 'use strict'; | ||
'-----END CERTIFICATE-----', | ||
// default cipher set copied from a random blog in the internets | ||
ciphers: 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS', | ||
// default iojs cipher set, copied from https://certsimple.com/blog/a-plus-node-js-ssl | ||
ciphers: [ | ||
'ECDHE-RSA-AES256-SHA384', | ||
'DHE-RSA-AES256-SHA384', | ||
'ECDHE-RSA-AES256-SHA256', | ||
'DHE-RSA-AES256-SHA256', | ||
'ECDHE-RSA-AES128-SHA256', | ||
'DHE-RSA-AES128-SHA256', | ||
'HIGH', | ||
'!aNULL', | ||
'!eNULL', | ||
'!EXPORT', | ||
'!DES', | ||
'!RC4', | ||
'!MD5', | ||
'!PSK', | ||
'!SRP', | ||
'!CAMELLIA' | ||
].join(':'), | ||
honorCipherOrder: true | ||
@@ -55,0 +72,0 @@ }; |
{ | ||
"name": "smtp-server", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "Create custom SMTP servers on the fly", | ||
@@ -13,8 +13,8 @@ "main": "lib/smtp-server.js", | ||
"devDependencies": { | ||
"chai": "^2.1.1", | ||
"chai": "^2.2.0", | ||
"grunt": "^0.4.5", | ||
"grunt-contrib-jshint": "^0.11.0", | ||
"grunt-contrib-jshint": "^0.11.2", | ||
"grunt-mocha-test": "^0.12.7", | ||
"mocha": "^2.2.1", | ||
"sinon": "^1.13.0", | ||
"mocha": "^2.2.4", | ||
"sinon": "^1.14.1", | ||
"smtp-connection": "^1.2.0" | ||
@@ -21,0 +21,0 @@ }, |
@@ -114,3 +114,3 @@ # smtp-server | ||
This module does not support `CRAM` based authentications since these require access to unencrypted user password during the authentication process. You shouldn't store passwords unencrypted anyway, so supporting it doesn't make much sense. | ||
This module supports `CRAM-MD5` but the use of it is discouraged as it requires access to unencrypted user passwords during the authentication process. You shouldn't store passwords unencrypted. | ||
@@ -134,3 +134,3 @@ ### Examples | ||
XOAUTH2 support needs to enabled with the `authMethods` array option to use it as it is disabled by default. | ||
XOAUTH2 support needs to enabled with the `authMethods` array option as it is disabled by default. | ||
If you support multiple authentication mechanisms, then you can check the used mechanism from the `method` property. | ||
@@ -160,2 +160,29 @@ | ||
#### CRAM-MD5 authentication | ||
CRAM-MD5 support needs to enabled with the `authMethods` array option as it is disabled by default. | ||
If you support multiple authentication mechanisms, then you can check the used mechanism from the `method` property. | ||
This authentication method does not return a password with the username but a response to a challenge. To validate the returned challenge response, the authentication object includes a method `validatePassword` that takes the actual plaintext password as an argument and returns either `true` if the password matches with the challenge response or `false` if it does not. | ||
```javascript | ||
var server = new SMTPServer({ | ||
authMethods: ['CRAM-MD5'], // CRAM-MD5 is not enabled by default | ||
onAuth: function(auth, session, callback){ | ||
if(auth.method !== 'CRAM-MD5'){ | ||
// should never occur in this case as only CRAM-MD5 is allowed | ||
return callback(new Error('Expecting CRAM-MD5')); | ||
} | ||
// CRAM-MD5 does not provide a password but a challenge response | ||
// that can be validated against the actual password of the user | ||
if(auth.username !== 'abc' || !auth.validatePassword('def')){ | ||
return callback(new Error('Invalid username or password')); | ||
} | ||
callback(null, {user: 123}); // where 123 is the user id or similar property | ||
} | ||
}); | ||
``` | ||
## Validating sender addresses | ||
@@ -223,3 +250,3 @@ | ||
var server = new SMTPServer({ | ||
onRcptTo: function(stream, session, callback){} | ||
onData: function(stream, session, callback){} | ||
}); | ||
@@ -226,0 +253,0 @@ ``` |
@@ -751,2 +751,5 @@ 'use strict'; | ||
}); | ||
// TODO: Add tests for CRAM-MD5 | ||
// smtp-connection does not support it currently | ||
}); | ||
@@ -753,0 +756,0 @@ |
103948
2258
348