smtp-server
Advanced tools
Comparing version 1.6.0 to 1.7.0
# Changelog | ||
## v1.7.0 2015-10-27 | ||
* Added support for XCLIENT with `useXClient` option | ||
* Fixed an issue with an empty space after EHLO (67acb1534 by AtlasDev) | ||
* Added dummy handlers for KILL, WIZ, SHELL | ||
## v1.6.0 2015-09-29 | ||
@@ -4,0 +10,0 @@ |
@@ -32,2 +32,5 @@ 'use strict'; | ||
// allow overriding connection properties. Only makes sense behind proxy | ||
useXClient: true, | ||
// Setup authentication | ||
@@ -104,2 +107,2 @@ // Allow only users with username 'testuser' and password 'testpass' | ||
// start listening | ||
server.listen(SERVER_PORT, SERVER_HOST); | ||
server.listen(SERVER_PORT, SERVER_HOST); |
@@ -6,2 +6,3 @@ 'use strict'; | ||
var tls = require('tls'); | ||
var net = require('net'); | ||
var sasl = require('./sasl'); | ||
@@ -80,2 +81,5 @@ var crypto = require('crypto'); | ||
// data passed from XCLIENT command | ||
this._xclient = new Map(); | ||
// increment connection count | ||
@@ -434,3 +438,3 @@ this._closing = false; | ||
SMTPConnection.prototype.handler_EHLO = function(command, callback) { | ||
var parts = command.toString().split(/\s+/); | ||
var parts = command.toString().trim().split(/\s+/); | ||
var hostname = parts[1] || ''; | ||
@@ -459,2 +463,6 @@ | ||
if (!this._xclient.has('ADDR') && this._server.options.useXClient && this._isSupported('XCLIENT')) { | ||
features.push('XCLIENT NAME ADDR PORT PROTO HELO LOGIN'); | ||
} | ||
this._startSession(); // EHLO is effectively the same as RSET | ||
@@ -470,3 +478,3 @@ this.send(250, ['OK: Nice to meet you ' + this.clientHostname].concat(features || [])); | ||
SMTPConnection.prototype.handler_HELO = function(command, callback) { | ||
var parts = command.toString().split(/\s+/); | ||
var parts = command.toString().trim().split(/\s+/); | ||
var hostname = parts[1] || ''; | ||
@@ -531,2 +539,91 @@ | ||
/** | ||
* Overrides connection info | ||
*/ | ||
SMTPConnection.prototype.handler_XCLIENT = function(command, callback) { | ||
// check if user is authorized to perform this command | ||
if (this._xclient.has('ADDR') || !this._server.options.useXClient) { | ||
this.send(550, 'Error: Not allowed'); | ||
return callback(); | ||
} | ||
// not allowed to change properties if already processing mail | ||
if (this.session.envelope.mailFrom) { | ||
this.send(503, 'Error: Mail transaction in progress'); | ||
return callback(); | ||
} | ||
var allowedKeys = ['NAME', 'ADDR', 'PORT', 'PROTO', 'HELO', 'LOGIN']; | ||
var parts = command.toString().trim().split(/\s+/); | ||
var key, value; | ||
var data = new Map(); | ||
parts.shift(); // remove XCLIENT prefix | ||
if (!parts.length) { | ||
this.send(501, 'Error: Bad command parameter syntax'); | ||
return callback(); | ||
} | ||
// parse and validate arguments | ||
for (var i = 0, len = parts.length; i < len; i++) { | ||
value = parts[i].split('='); | ||
key = value.shift(); | ||
if (value.length !== 1 || allowedKeys.indexOf(key.toUpperCase()) < 0) { | ||
this.send(501, 'Error: Bad command parameter syntax'); | ||
return callback(); | ||
} | ||
key = key.toUpperCase(); | ||
value = value[0]; | ||
if (['[UNAVAILABLE]', '[TEMPUNAVAIL]'].indexOf(value.toUpperCase()) >= 0) { | ||
value = false; | ||
} | ||
data.set(key, value); | ||
} | ||
// override connection properties | ||
data.forEach(function(value, key) { | ||
switch (key) { | ||
case 'LOGIN': | ||
if (!value) { | ||
if (this.session.user) { | ||
this._server.logger.info('[%s] User deauthenticated using %s', this._id, 'XCLIENT'); | ||
} | ||
} else { | ||
this._server.logger.info('[%s] %s authenticated using %s', this._id, value, 'XCLIENT'); | ||
this.session.user = { | ||
username: value | ||
}; | ||
} | ||
break; | ||
case 'ADDR': | ||
if (value) { | ||
if (!net.isIP) { | ||
this.send(501, 'Error: Bad command parameter syntax. Invalid address'); | ||
return callback(); | ||
} | ||
this._server.logger.info('[%s] XCLIENT from %s through %s', this._id, value, this.remoteAddress); | ||
this.remoteAddress = value.toLowerCase(); | ||
this.hostNameAppearsAs = false; // reset client provided hostname, require HELO/EHLO | ||
} | ||
break; | ||
case 'NAME': | ||
this._server.logger.info('[%s] XCLIENT hostname resolved as "%s"', this._id, value || ''); | ||
this.clientHostname = value.toLowerCase(); | ||
break; | ||
default: | ||
// other values are not relevant | ||
} | ||
this._xclient.set(key, value); | ||
}.bind(this)); | ||
// Use [ADDR] if NAME was empty | ||
if (this.remoteAddress && !this.clientHostname) { | ||
this.clientHostname = '[' + this.remoteAddress + ']'; | ||
} | ||
// success | ||
this.send(220, this.name + ' ESMTP' + (this._server.options.banner ? ' ' + this._server.options.banner : '')); | ||
callback(); | ||
}; | ||
/** | ||
* Upgrades connection to TLS if possible | ||
@@ -737,1 +834,48 @@ */ | ||
}; | ||
// Dummy handlers for some old sendmail specific commands | ||
/** | ||
* Processes sendmail WIZ command, upgrades to "wizard mode" | ||
*/ | ||
SMTPConnection.prototype.handler_WIZ = function(command, callback) { | ||
var args = command.toString().trim().split(/\s+/); | ||
var password; | ||
args.shift(); // remove WIZ | ||
password = (args.shift() || '').toString(); | ||
// require password argument | ||
if (!password) { | ||
this.send(500, 'You are no wizard!'); | ||
return callback(); | ||
} | ||
// all passwords pass validation, so everyone is a wizard! | ||
this.session.isWizard = true; | ||
this.send(200, 'Please pass, oh mighty wizard'); | ||
callback(); | ||
}; | ||
/** | ||
* Processes sendmail SHELL command, should return interactive shell but this is a dummy function | ||
* so no actual shell is provided to the client | ||
*/ | ||
SMTPConnection.prototype.handler_SHELL = function(command, callback) { | ||
if (!this.session.isWizard) { | ||
this.send(500, 'Mere mortals musn\'t mutter that mantra'); | ||
return callback(); | ||
} | ||
this._server.logger.info('[%s] Client tried to invoke SHELL', this._id); | ||
this.send(500, 'Error: Invoking shell is not allowed. This incident will be reported.'); | ||
callback(); | ||
}; | ||
/** | ||
* Processes sendmail KILL command | ||
*/ | ||
SMTPConnection.prototype.handler_KILL = function(command, callback) { | ||
this.send(500, 'Can\'t kill Mom'); | ||
callback(); | ||
}; |
@@ -7,2 +7,4 @@ 'use strict'; | ||
var tlsDefaults = { | ||
// pregenerated default certificates for localhost | ||
// obviusly, do not use in production | ||
key: '-----BEGIN RSA PRIVATE KEY-----\n' + | ||
@@ -99,2 +101,2 @@ 'MIIEpAIBAAKCAQEA6Z5Qqhw+oWfhtEiMHE32Ht94mwTBpAfjt3vPpX8M7DMCTwHs\n' + | ||
return result; | ||
} | ||
} |
{ | ||
"name": "smtp-server", | ||
"version": "1.6.0", | ||
"version": "1.7.0", | ||
"description": "Create custom SMTP servers on the fly", | ||
@@ -13,3 +13,3 @@ "main": "lib/smtp-server.js", | ||
"devDependencies": { | ||
"chai": "^3.3.0", | ||
"chai": "^3.4.0", | ||
"grunt": "^0.4.5", | ||
@@ -16,0 +16,0 @@ "grunt-contrib-jshint": "^0.11.3", |
@@ -44,2 +44,3 @@ # smtp-server | ||
* **options.useProxy** boolean, if set to true expects to be behind a proxy that emits a [PROXY header](http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt) (version 1 only) | ||
* **options.useXClient** boolean, if set to true, enables usage of [XCLIENT](http://www.postfix.org/XCLIENT_README.html) extension to override connection properties | ||
* **options.socketTimeout** how many milliseconds of inactivity to allow before disconnecting the client (defaults to 1 minute) | ||
@@ -208,3 +209,3 @@ * **options.closeTimeout** how many millisceonds to wait before disconnecting pending connections once server.close() has been called (defaults to 30 seconds) | ||
var server = new SMTPServer({ | ||
onConnect: function(address, session, callback){ | ||
onConnect: function(session, callback){ | ||
if(session.remoteAddress === '127.0.0.1'){ | ||
@@ -211,0 +212,0 @@ return callback(new Error('No connections from localhost allowed')); |
121771
2608
403
6