Skip to content

Commit

Permalink
feat: add explicit TLS support with AUTH
Browse files Browse the repository at this point in the history
  • Loading branch information
trs committed May 12, 2017
1 parent 8394714 commit 9bece5f
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 22 deletions.
23 changes: 18 additions & 5 deletions src/commands/registration/auth.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const _ = require('lodash');
const tls = require('tls');

module.exports = {
directive: 'AUTH',
Expand All @@ -7,7 +8,6 @@ module.exports = {

switch (method) {
case 'TLS': return handleTLS.call(this);
case 'SSL': return handleSSL.call(this);
default: return this.reply(504);
}
},
Expand All @@ -19,9 +19,22 @@ module.exports = {
};

function handleTLS() {
return this.reply(504);
}
if (!this.server._tls) return this.reply(504);

function handleSSL() {
return this.reply(504);
return this.reply(234)
.then(() => {
const secureContext = tls.createSecureContext(this.server._tls);
const secureSocket = new tls.TLSSocket(this.commandSocket, {
isServer: true,
secureContext
});
['data', 'timeout', 'end', 'close', 'drain', 'error'].forEach(event => {
function forwardEvent() {
this.emit.apply(this, arguments);
}
secureSocket.on(event, forwardEvent.bind(this.commandSocket, event));
});
this.commandSocket = secureSocket;
this.secure = true;
});
}
10 changes: 10 additions & 0 deletions src/commands/registration/pbsz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
directive: 'PBSZ',
handler: function ({command} = {}) {
if (!this.server._tls) return this.reply(202, 'Not suppored');
this.bufferSize = parseInt(command.arg, 10);
return this.reply(200, this.bufferSize === 0 ? 'OK' : 'Buffer too large: PBSZ=0');
},
syntax: '{{cmd}}',
description: 'Protection Buffer Size'
};
19 changes: 19 additions & 0 deletions src/commands/registration/prot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const _ = require('lodash');

module.exports = {
directive: 'PROT',
handler: function ({command} = {}) {
if (!this.server._tls) return this.reply(202, 'Not suppored');
if (this.bufferSize === false) return this.reply(503);

switch (_.toUpper(command.arg)) {
case 'P': return this.reply(200, 'OK');
case 'C':
case 'S':
case 'E': return this.reply(536, 'Not supported');
default: return this.reply(504);
}
},
syntax: '{{cmd}}',
description: 'Data Channel Protection Level'
};
2 changes: 1 addition & 1 deletion src/commands/registration/type.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const _ = require('lodash');

const ENCODING_TYPES = {
A: 'utf-8',
A: 'utf8',
I: 'binary',
L: 'binary'
};
Expand Down
4 changes: 3 additions & 1 deletion src/commands/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ const commands = [
require('./registration/stru'),
require('./registration/syst'),
require('./registration/type'),
require('./registration/user')
require('./registration/user'),
require('./registration/pbsz'),
require('./registration/prot')
];

const registry = commands.reduce((result, cmd) => {
Expand Down
5 changes: 3 additions & 2 deletions src/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class FtpConnection {
this.id = uuid.v4();
this.log = options.log.child({id: this.id, ip: this.ip});
this.commands = new Commands(this);
this.encoding = 'utf-8';
this.encoding = 'utf8';
this.bufferSize = false;

this.connector = new BaseConnector(this);

Expand All @@ -33,7 +34,7 @@ class FtpConnection {
}

_handleData(data) {
const messages = _.compact(data.toString('utf-8').split('\r\n'));
const messages = _.compact(data.toString('utf8').split('\r\n'));
this.log.trace(messages, 'Messages');
return sequence(messages.map(message => this.commands.handle.bind(this.commands, message)));
}
Expand Down
24 changes: 17 additions & 7 deletions src/connector/passive.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const net = require('net');
const tls = require('tls');
const when = require('when');

const Connector = require('./base');
Expand All @@ -12,9 +13,7 @@ class Passive extends Connector {
}

waitForConnection({timeout = 5000, delay = 250} = {}) {
if (!this.dataServer) {
return when.reject(new errors.ConnectorError('Passive server not setup'));
}
if (!this.dataServer) return when.reject(new errors.ConnectorError('Passive server not setup'));
return when.iterate(
() => {},
() => this.dataServer && this.dataServer.listening && this.dataSocket && this.dataSocket.connected,
Expand All @@ -31,8 +30,7 @@ class Passive extends Connector {
return closeExistingServer()
.then(() => this.getPort())
.then(port => {
this.dataSocket = null;
this.dataServer = net.createServer({ pauseOnConnect: true }, socket => {
const connectionHandler = socket => {
if (this.connection.commandSocket.remoteAddress !== socket.remoteAddress) {
this.log.error({
pasv_connection: socket.remoteAddress,
Expand All @@ -45,15 +43,27 @@ class Passive extends Connector {
}
this.log.debug({port}, 'Passive connection fulfilled.');

this.dataSocket = socket;
if (this.connection.secure) {
const secureContext = tls.createSecureContext(this.server._tls);
const secureSocket = new tls.TLSSocket(socket, {
isServer: true,
secureContext
});
this.dataSocket = secureSocket;
} else {
this.dataSocket = socket;
}
this.dataSocket.connected = true;
this.dataSocket.setEncoding(this.connection.encoding);
this.dataSocket.on('error', err => this.server.emit('client-error', {connection: this, context: 'dataSocket', error: err}));
this.dataSocket.on('close', () => {
this.log.debug('Passive connection closed');
this.end();
});
});
};

this.dataSocket = null;
this.dataServer = net.createServer({ pauseOnConnect: true }, connectionHandler);
this.dataServer.maxConnections = 1;
this.dataServer.on('error', err => this.server.emit('client-error', {connection: this, context: 'dataServer', error: err}));
this.dataServer.on('close', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/resolve-host.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = function (hostname) {
if (response.statusCode !== 200) {
return reject(new errors.GeneralError('Unable to resolve hostname', response.statusCode));
}
response.setEncoding('utf-8');
response.setEncoding('utf8');
response.on('data', chunk => {
ip += chunk;
});
Expand Down
10 changes: 7 additions & 3 deletions test/commands/registration/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ const CMD = 'AUTH';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => when.resolve(),
server: {
_tls: {}
}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

Expand All @@ -19,10 +22,11 @@ describe(CMD, function () {
sandbox.restore();
});

it('TLS // not supported', done => {
it('TLS // supported', done => {
cmdFn({command: { arg: 'TLS', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
expect(mockClient.reply.args[0][0]).to.equal(234);
expect(mockClient.secure).to.equal(true);
done();
})
.catch(done);
Expand Down
2 changes: 1 addition & 1 deletion test/commands/registration/type.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe(CMD, function () {
cmdFn({ command: { arg: 'A' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.encoding).to.equal('utf-8');
expect(mockClient.encoding).to.equal('utf8');
done();
})
.catch(done);
Expand Down
2 changes: 1 addition & 1 deletion test/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const bunyan = require('bunyan');
const FtpServer = require('../src');

const log = bunyan.createLogger({name: 'test'});
log.level('info');
log.level('trace');
const server = new FtpServer('ftp://127.0.0.1:8880', {
log,
pasv_range: 8881,
Expand Down

0 comments on commit 9bece5f

Please sign in to comment.