Socket
Socket
Sign inDemoInstall

smtp-server

Package Overview
Dependencies
3
Maintainers
1
Versions
65
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.3.1 to 3.4.0

5

CHANGELOG.md
# Changelog
## v3.4.0 2017-12-01
* Added new property `secured` to indicate an TLS server where TLS is handled upstream
* Allow handling TLS after PROXY header
## v3.3.1 2017-11-28

@@ -4,0 +9,0 @@

57

lib/smtp-connection.js

@@ -13,2 +13,3 @@ 'use strict';

const EventEmitter = require('events');
const base32 = require('base32.js');

@@ -25,7 +26,11 @@ const SOCKET_TIMEOUT = 60 * 1000;

class SMTPConnection extends EventEmitter {
constructor(server, socket) {
constructor(server, socket, options) {
super();
options = options || {};
// Random session ID, used for logging
this._id = crypto.randomBytes(9).toString('base64');
this.id = options.id || base32.encode(crypto.randomBytes(10)).toLowerCase();
this.ignore = options.ignore;
this._server = server;

@@ -65,7 +70,7 @@ this._socket = socket;

this.tlsOptions = this.secure && !this.needsUpgrade ? this._socket.getCipher() : false;
this.tlsOptions = this.secure && !this.needsUpgrade && this._socket.getCipher ? this._socket.getCipher() : false;
// Store remote address for later usage
this.remoteAddress = (this._socket.remoteAddress || '').replace(/^::ffff:/, '');
this.remotePort = Number(this._socket.remotePort) || 0;
this.remoteAddress = (options.remoteAddress || this._socket.remoteAddress || '').replace(/^::ffff:/, '');
this.remotePort = Number(options.remotePort || this._socket.remotePort) || 0;

@@ -124,6 +129,4 @@ // normalize IPv6 addresses

if (!this._server.options.useProxy) {
// Keep a small delay for detecting early talkers
setTimeout(() => this.connectionReady(), 100);
}
// Keep a small delay for detecting early talkers
setTimeout(() => this.connectionReady(), 100);
});

@@ -373,38 +376,6 @@ }

let handler;
let params;
if (!this._ready) {
if (this._server.options.useProxy) {
params = (command || '').toString().split(' ');
commandName = params.shift().toUpperCase();
if (commandName !== 'PROXY') {
this.send(500, 'Invalid PROXY header');
return this.close();
}
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.remoteAddress = params[1].trim().toLowerCase();
if (params[3]) {
this.remotePort = Number(params[3].trim()) || this.remotePort;
}
this.emitConnection();
}
return this.connectionReady(callback);
} else {
// block spammers that send payloads before server greeting
return this.send(421, this.name + ' You talk too soon');
}
// block spammers that send payloads before server greeting
return this.send(421, this.name + ' You talk too soon');
}

@@ -411,0 +382,0 @@

@@ -10,2 +10,4 @@ 'use strict';

const punycode = require('punycode');
const crypto = require('crypto');
const base32 = require('base32.js');

@@ -72,11 +74,26 @@ const CLOSE_TIMEOUT = 30 * 1000; // how much to wait until pending connections are terminated

this.server = net.createServer(this.options, socket => {
this._upgrade(socket, (err, tlsSocket) => {
this._handleProxy(socket, (err, socketOptions) => {
if (err) {
return this._onError(err);
// ignore, should not happen
}
this.connect(tlsSocket);
if (this.options.secured) {
return this.connect(socket, socketOptions);
}
this._upgrade(socket, (err, tlsSocket) => {
if (err) {
return this._onError(err);
}
this.connect(tlsSocket, socketOptions);
});
});
});
} else {
this.server = net.createServer(this.options, socket => this.connect(socket));
this.server = net.createServer(this.options, socket =>
this._handleProxy(socket, (err, socketOptions) => {
if (err) {
// ignore, should not happen
}
this.connect(socket, socketOptions);
})
);
}

@@ -87,4 +104,4 @@

connect(socket) {
let connection = new SMTPConnection(this, socket);
connect(socket, socketOptions) {
let connection = new SMTPConnection(this, socket, socketOptions);
this.connections.add(connection);

@@ -316,2 +333,81 @@ connection.on('error', err => this._onError(err));

_handleProxy(socket, callback) {
let socketOptions = {
id: base32.encode(crypto.randomBytes(10)).toLowerCase()
};
if (
!this.options.useProxy ||
(Array.isArray(this.options.useProxy) && !this.options.useProxy.includes(socket.remoteAddress) && !this.options.useProxy.includes('*'))
) {
socketOptions.ignore = this.options.ignoredHosts && this.options.ignoredHosts.includes(socket.remoteAddress);
return setImmediate(() => callback(null, socketOptions));
}
let chunks = [];
let chunklen = 0;
let socketReader = () => {
let chunk;
while ((chunk = socket.read()) !== null) {
for (let i = 0, len = chunk.length; i < len; i++) {
let chr = chunk[i];
if (chr === 0x0a) {
socket.removeListener('readable', socketReader);
chunks.push(chunk.slice(0, i + 1));
chunklen += i + 1;
let remainder = chunk.slice(i + 1);
if (remainder.length) {
socket.unshift(remainder);
}
let header = Buffer.concat(chunks, chunklen)
.toString()
.trim();
let params = (header || '').toString().split(' ');
let commandName = params.shift().toUpperCase();
if (commandName !== 'PROXY') {
try {
socket.end('* BAD Invalid PROXY header\r\n');
} catch (E) {
// ignore
}
return;
}
if (params[1]) {
socketOptions.remoteAddress = params[1].trim().toLowerCase();
socketOptions.ignore = this.options.ignoredHosts && this.options.ignoredHosts.includes(socketOptions.remoteAddress);
if (!socketOptions.ignore) {
this.logger.info(
{
tnx: 'proxy',
cid: socketOptions.id,
proxy: params[1].trim().toLowerCase()
},
'[%s] PROXY from %s through %s (%s)',
socketOptions.id,
params[1].trim().toLowerCase(),
params[2].trim().toLowerCase(),
JSON.stringify(params)
);
}
if (params[3]) {
socketOptions.remotePort = Number(params[3].trim()) || socketOptions.remotePort;
}
}
return callback(null, socketOptions);
}
}
chunks.push(chunk);
chunklen += chunk.length;
}
};
socket.on('readable', socketReader);
}
/**

@@ -318,0 +414,0 @@ * Called when a new connection is established. This might not be the same time the socket is opened

{
"name": "smtp-server",
"version": "3.3.1",
"version": "3.4.0",
"description": "Create custom SMTP servers on the fly",

@@ -12,4 +12,5 @@ "main": "lib/smtp-server.js",

"dependencies": {
"base32.js": "^0.1.0",
"ipv6-normalize": "^1.0.1",
"nodemailer": "^4.1.2"
"nodemailer": "^4.4.0"
},

@@ -23,4 +24,4 @@ "devDependencies": {

"grunt-mocha-test": "^0.13.3",
"mocha": "^4.0.0",
"pem": "^1.12.0"
"mocha": "^4.0.1",
"pem": "^1.12.3"
},

@@ -34,3 +35,5 @@ "repository": {

},
"keywords": ["SMTP"],
"keywords": [
"SMTP"
],
"engines": {

@@ -37,0 +40,0 @@ "node": ">=6.0.0"

@@ -1387,2 +1387,61 @@ /* eslint no-unused-expressions:0, prefer-arrow-callback: 0 */

describe('Secure PROXY server', function() {
let PORT = 1336;
let server = new SMTPServer({
maxClients: 5,
logger: false,
useProxy: true,
secure: true,
onConnect(session, callback) {
if (session.remoteAddress === '1.2.3.4') {
let err = new Error('Blacklisted IP');
err.responseCode = 421;
return callback(err);
}
callback();
}
});
beforeEach(function(done) {
server.listen(PORT, '127.0.0.1', done);
});
afterEach(function(done) {
server.close(done);
});
it('should rewrite remote address value', function(done) {
let connection = new Client({
port: PORT,
host: '127.0.0.1',
tls: {
rejectUnauthorized: false
}
});
connection.on('end', done);
connection.connect(function() {
let conn;
// get first connection
server.connections.forEach(function(val) {
if (!conn) {
conn = val;
}
});
// default remote address should be overriden by the value from the PROXY header
expect(conn.remoteAddress).to.equal('198.51.100.22');
expect(conn.remotePort).to.equal(35646);
connection.quit();
});
connection._socket.write('PROXY TCP4 198.51.100.22 203.0.113.7 35646 80\r\n');
connection._upgradeConnection(err => {
expect(err).to.not.exist;
// server should respond with greeting after this point
});
});
});
describe('onClose handler', function() {

@@ -1389,0 +1448,0 @@ let PORT = 1336;

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc