From cf1c5dd1fe5024060df49e38b9447ce7105895a1 Mon Sep 17 00:00:00 2001 From: Barbosik Date: Thu, 7 Jul 2016 15:15:31 +0200 Subject: [PATCH] add secure websocket support (TLS) --- src/GameServer.js | 62 +++++++++++++++++++------------ src/HttpsServer.js | 92 ++++++++++++++++++++++++++++++++++++++++++++++ ssl/readme.txt | 7 ++++ 3 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 src/HttpsServer.js create mode 100644 ssl/readme.txt diff --git a/src/GameServer.js b/src/GameServer.js index a10ac4c9b..ab9ea46f4 100644 --- a/src/GameServer.js +++ b/src/GameServer.js @@ -7,6 +7,7 @@ var pjson = require('../package.json'); var ini = require('./modules/ini.js'); var QuadNode = require('./QuadNode.js'); var PlayerCommand = require('./modules/PlayerCommand'); +var HttpsServer = require('./HttpsServer'); // Project imports var Packet = require('./packet'); @@ -20,6 +21,9 @@ var UserRoleEnum = require('./enum/UserRoleEnum'); // GameServer implementation function GameServer() { + this.httpServer = null; + this.wsServer = null; + // Startup this.run = true; this.lastNodeId = 1; @@ -138,34 +142,32 @@ GameServer.prototype.start = function() { // Gamemode configurations this.gameMode.onServerInit(this); - var options = { - port: this.config.serverPort, - perMessageDeflate: false + if (fs.existsSync('./ssl/key.pem') && fs.existsSync('./ssl/cert.pem')) { + // HTTP/TLS + var options = { + key: fs.readFileSync('./ssl/key.pem', 'utf8'), + cert: fs.readFileSync('./ssl/cert.pem', 'utf8') + }; + Logger.info("TLS: supported"); + this.httpServer = HttpsServer.createServer(options); + } else { + // HTTP only + Logger.warn("TLS: not supported (SSL certificate not found!)"); + this.httpServer = http.createServer(); + } + var wsOptions = { + server: this.httpServer, + perMessageDeflate: true }; - - // Start the server - this.socketServer = new WebSocket.Server(options, this.onServerSocketOpen.bind(this)); - this.socketServer.on('error', this.onServerSocketError.bind(this)); - this.socketServer.on('connection', this.onClientSocketOpen.bind(this)); + this.wsServer = new WebSocket.Server(wsOptions); + this.wsServer.on('error', this.onServerSocketError.bind(this)); + this.wsServer.on('connection', this.onClientSocketOpen.bind(this)); + this.httpServer.listen(this.config.serverPort, this.onHttpServerOpen.bind(this)); this.startStatsServer(this.config.serverStatsPort); }; -GameServer.prototype.onServerSocketError = function (error) { - Logger.error("WebSocket: "+ error.code + " - " + error.message); - switch (error.code) { - case "EADDRINUSE": - Logger.error("Server could not bind to port " + this.config.serverPort + "!"); - Logger.error("Please close out of Skype or change 'serverPort' in gameserver.ini to a different number."); - break; - case "EACCES": - Logger.error("Please make sure you are running Ogar with root privileges."); - break; - } - process.exit(1); // Exits the program -}; - -GameServer.prototype.onServerSocketOpen = function () { +GameServer.prototype.onHttpServerOpen = function () { // Spawn starting food this.startingFood(); @@ -185,6 +187,20 @@ GameServer.prototype.onServerSocketOpen = function () { } }; +GameServer.prototype.onServerSocketError = function (error) { + Logger.error("WebSocket: " + error.code + " - " + error.message); + switch (error.code) { + case "EADDRINUSE": + Logger.error("Server could not bind to port " + this.config.serverPort + "!"); + Logger.error("Please close out of Skype or change 'serverPort' in gameserver.ini to a different number."); + break; + case "EACCES": + Logger.error("Please make sure you are running Ogar with root privileges."); + break; + } + process.exit(1); // Exits the program +}; + GameServer.prototype.onClientSocketOpen = function (ws) { if (this.config.serverMaxConnections > 0 && this.socketCount >= this.config.serverMaxConnections) { ws.close(1000, "No slots"); diff --git a/src/HttpsServer.js b/src/HttpsServer.js new file mode 100644 index 000000000..8caf6f522 --- /dev/null +++ b/src/HttpsServer.js @@ -0,0 +1,92 @@ +var http = require('http'), + https = require('https'), + inherits = require('util').inherits, + httpSocketHandler = http._connectionListener; + +var isOldNode = /^v0\.10\./.test(process.version); + +function Server(tlsconfig, requestListener) { + if (!(this instanceof Server)) + return new Server(tlsconfig, requestListener); + + if (typeof tlsconfig === 'function') { + requestListener = tlsconfig; + tlsconfig = undefined; + } + + if (typeof tlsconfig === 'object') { + this.removeAllListeners('connection'); + + https.Server.call(this, tlsconfig, requestListener); + + // capture https socket handler, it's not exported like http's socket + // handler + var connev = this._events.connection; + if (typeof connev === 'function') + this._tlsHandler = connev; + else + this._tlsHandler = connev[connev.length - 1]; + this.removeListener('connection', this._tlsHandler); + + this._connListener = connectionListener; + this.on('connection', connectionListener); + + // copy from http.Server + this.timeout = 2 * 60 * 1000; + this.allowHalfOpen = true; + this.httpAllowHalfOpen = false; + } else + http.Server.call(this, requestListener); +} +inherits(Server, https.Server); + +Server.prototype.setTimeout = function (msecs, callback) { + this.timeout = msecs; + if (callback) + this.on('timeout', callback); +}; + +Server.prototype.__httpSocketHandler = httpSocketHandler; + +var connectionListener; +if (isOldNode) { + connectionListener = function (socket) { + var self = this; + socket.ondata = function (d, start, end) { + var firstByte = d[start]; + if (firstByte < 32 || firstByte >= 127) { + // tls/ssl + socket.ondata = null; + self._tlsHandler(socket); + socket.push(d.slice(start, end)); + } else { + self.__httpSocketHandler(socket); + socket.ondata(d, start, end); + } + }; + }; +} else { + connectionListener = function (socket) { + var self = this; + var data = socket.read(1); + if (data === null) { + socket.once('readable', function () { + self._connListener(socket); + }); + } else { + var firstByte = data[0]; + socket.unshift(data); + if (firstByte < 32 || firstByte >= 127) { + // tls/ssl + this._tlsHandler(socket); + } else + this.__httpSocketHandler(socket); + } + }; +} + +exports.Server = Server; + +exports.createServer = function (tlsconfig, requestListener) { + return new Server(tlsconfig, requestListener); +}; \ No newline at end of file diff --git a/ssl/readme.txt b/ssl/readme.txt new file mode 100644 index 000000000..ecc03e9f2 --- /dev/null +++ b/ssl/readme.txt @@ -0,0 +1,7 @@ +Place private key and certificate files here: +key.pem - your private key +cert.pem - your certificate + +You can create it with openssl: + +openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 100 -nodes \ No newline at end of file