190 lines
4.4 KiB
JavaScript
190 lines
4.4 KiB
JavaScript
var net = require('net');
|
|
var http = require('http');
|
|
var crypto = require('crypto');
|
|
var express = require('express');
|
|
var expressWs = require('express-ws');
|
|
var bodyParser = require('body-parser');
|
|
|
|
function generateToken() {
|
|
return crypto.randomBytes(32).toString('hex');
|
|
}
|
|
|
|
function checkTo(allowed, requested) {
|
|
if (!(allowed instanceof Array)) {
|
|
allowed = [allowed];
|
|
}
|
|
|
|
// For each rule
|
|
for (var i = 0; i < allowed.length; i++) {
|
|
var to = allowed[i];
|
|
|
|
if ((to.host == requested.host || !to.host) && (to.port == requested.port || !to.port)) {
|
|
if (to.blacklist) { // This item is blacklisted
|
|
return false;
|
|
} else { // Otheriwse, it's whitelisted
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No rule found, access denied
|
|
return false;
|
|
}
|
|
|
|
module.exports = function (options, connectionListener) {
|
|
options = options || {};
|
|
|
|
var app = express();
|
|
var jsonParser = bodyParser.json();
|
|
var urlRoot = options.urlRoot || '/api/vm/net';
|
|
|
|
var server;
|
|
if (options.server) {
|
|
server = options.server;
|
|
} else {
|
|
server = http.createServer();
|
|
}
|
|
|
|
var sockets = {};
|
|
|
|
if (options.allowOrigin) {
|
|
var allowOrigin = options.allowOrigin;
|
|
if (typeof options.allowOrigin != 'string') {
|
|
allowOrigin = (options.allowOrigin === true) ? '*' : '';
|
|
}
|
|
|
|
if (allowOrigin) {
|
|
// Set Access-Control headers (CORS)
|
|
app.use(function (req, res, next) {
|
|
if (req.path.indexOf(urlRoot) !== 0) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
res.header('Access-Control-Allow-Origin', allowOrigin);
|
|
|
|
if (req.method.toUpperCase() == 'OPTIONS') { // Preflighted requests
|
|
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
|
|
res.header('Access-Control-Max-Age', 1728000); // Access-Control headers cached for 20 days
|
|
}
|
|
next();
|
|
});
|
|
}
|
|
}
|
|
|
|
app.post(urlRoot + '/connect', jsonParser, function (req, res) {
|
|
var host = req.body.host,
|
|
port = req.body.port;
|
|
|
|
if (!host || !port) {
|
|
res.status(400).send({
|
|
code: 400,
|
|
error: 'No host and port specified'
|
|
});
|
|
return;
|
|
}
|
|
if (options.to) {
|
|
if (!checkTo(options.to, { host: host, port: port })) {
|
|
res.status(403).send({
|
|
code: 403,
|
|
error: 'Destination not allowed'
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
var socket = net.connect({
|
|
host: host,
|
|
port: port
|
|
}, function (err) {
|
|
if (err) {
|
|
res.status(500).send({
|
|
code: 500,
|
|
error: err
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Generate a token for this connection
|
|
var token = generateToken();
|
|
sockets[token] = socket;
|
|
|
|
// Remove the socket from the list when closed
|
|
socket.on('end', function () {
|
|
if (sockets[token]) {
|
|
delete sockets[token];
|
|
}
|
|
});
|
|
|
|
console.log('Connected to '+req.body.host+':'+req.body.port+' ('+token+')');
|
|
|
|
var remote = socket.address();
|
|
res.send({
|
|
token: token,
|
|
remote: remote
|
|
});
|
|
});
|
|
socket.on('error', function (err) {
|
|
if (res.finished) {
|
|
console.log("Socket error after response closed: "+err);
|
|
return;
|
|
}
|
|
res.status(502).send({
|
|
code: 502,
|
|
error: 'Socket error: '+err.code,
|
|
details: err
|
|
});
|
|
});
|
|
if (connectionListener) {
|
|
connectionListener(socket)
|
|
}
|
|
});
|
|
|
|
var wss = expressWs(app, server);
|
|
|
|
app.ws(urlRoot + '/socket', function (ws, req) {
|
|
var token = req.query.token;
|
|
|
|
if (!sockets[token]) {
|
|
console.warn('WARN: Unknown TCP connection with token "'+token+'"');
|
|
ws.close();
|
|
return;
|
|
}
|
|
|
|
var socket = sockets[token];
|
|
//delete sockets[token];
|
|
|
|
console.log('Forwarding socket with token '+token);
|
|
|
|
ws.on('message', function (chunk, flags) {
|
|
socket.write(flags.buffer || chunk, 'binary', function () {
|
|
//console.log('Sent: ', (flags.buffer || chunk).toString());
|
|
});
|
|
});
|
|
socket.on('data', function (chunk) {
|
|
//console.log('Received: ', chunk.toString());
|
|
// Providing a callback is important, otherwise errors can be thrown
|
|
ws.send(chunk, { binary: true }, function (err) {});
|
|
});
|
|
socket.on('end', function () {
|
|
console.log('TCP connection closed by remote ('+token+')');
|
|
ws.close();
|
|
});
|
|
ws.on('close', function () {
|
|
socket.end();
|
|
console.log('Websocket connection closed ('+token+')');
|
|
});
|
|
});
|
|
|
|
app.on('mount', function (parentApp) {
|
|
// @see https://github.com/strongloop/express/blob/master/lib/application.js#L615
|
|
parentApp.listen = function listen() {
|
|
server.addListener('request', this);
|
|
return server.listen.apply(server, arguments);
|
|
};
|
|
});
|
|
|
|
return app;
|
|
};
|