diff --git a/README.md b/README.md index e521aa5..115a9c4 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,19 @@ server.on('login', function(client) { * `playerCount` * `maxPlayers` +### mc.createServer(options) + +Returns a `Server` instance and starts listening. + +### Server + +#### server.onlineModeExceptions + +This is a plain old JavaScript object. Add a key with the username you want to +be exempt from online mode or offline mode (whatever mode the server is in). + +#### server.maxPlayers + ### Not Immediately Obvious Data Type Formats #### entityMetadata diff --git a/index.js b/index.js index 4476952..9ef4088 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ var EventEmitter = require('events').EventEmitter , assert = require('assert') , ursa = require('ursa') , crypto = require('crypto') + , bufferEqual = require('buffer-equal') , superagent = require('superagent') , protocol = require('./lib/protocol') , Client = require('./lib/client') @@ -35,6 +36,7 @@ function createServer(options) { var serverKey = ursa.generatePrivateKey(1024); var server = new Server(options); + server.onlineModeExceptions = {}; server.maxPlayers = maxPlayers; server.on("connection", function(client) { client.once(0xfe, onPing); @@ -101,8 +103,10 @@ function createServer(options) { function onHandshake(packet) { client.username = packet.username; + var isException = !!server.onlineModeExceptions[client.username]; + var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException); var serverId; - if (onlineMode) { + if (needToVerify) { serverId = crypto.randomBytes(4).toString('hex'); } else { serverId = '-'; @@ -128,50 +132,55 @@ function createServer(options) { } function onEncryptionKeyResponse(packet) { - var success = client.verifyToken.toString("hex") === serverKey.decrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING).toString("hex"); - if (success) { - var sharedSecret = serverKey.decrypt(packet.sharedSecret, undefined, undefined, ursa.RSA_PKCS1_PADDING); - client.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret); - client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret); - hash.update(sharedSecret); - hash.update(client.publicKey); - var digest = mcHexDigest(hash); - if (onlineMode) { - var request = superagent.get("http://session.minecraft.net/game/checkserver.jsp"); - request.query({ - user: client.username, - serverId: digest - }); - request.end(function(err, resp) { - var myErr; - if (err) { - server.emit('error', err); - client.end('McSessionUnavailable'); - } else if (resp.serverError) { - myErr = new Error("session.minecraft.net is broken: " + resp.status); - myErr.code = 'EMCSESSION500'; - server.emit('error', myErr); - client.end('McSessionDown'); - } else if (resp.serverError) { - myErr = new Error("session.minecraft.net rejected request: " + resp.status); - myErr.code = 'EMCSESSION400'; - server.emit('error', myErr); - client.end('McSessionRejectedAuthRequest'); - } else if (resp.text !== "YES") { - client.end('FailedToVerifyUsername'); - } else { - loginClient(); - } - }); - } - client.write(0xFC, { - sharedSecret: new Buffer(0), - verifyToken: new Buffer(0) - }); - client.encryptionEnabled = true; - if (! onlineMode) loginClient(); - } else { + var verifyToken = serverKey.decrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING); + if (! bufferEqual(client.verifyToken, verifyToken)) { client.end('DidNotEncryptVerifyTokenProperly'); + return; + } + var sharedSecret = serverKey.decrypt(packet.sharedSecret, undefined, undefined, ursa.RSA_PKCS1_PADDING); + client.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret); + client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret); + hash.update(sharedSecret); + hash.update(client.publicKey); + client.write(0xFC, { + sharedSecret: new Buffer(0), + verifyToken: new Buffer(0) + }); + client.encryptionEnabled = true; + + var isException = !!server.onlineModeExceptions[client.username]; + var needToVerify = (onlineMode && ! isException) || (! onlineMode && isException); + var nextStep = needToVerify ? verifyUsername : loginClient; + nextStep(); + + function verifyUsername() { + var digest = mcHexDigest(hash); + var request = superagent.get("http://session.minecraft.net/game/checkserver.jsp"); + request.query({ + user: client.username, + serverId: digest + }); + request.end(function(err, resp) { + var myErr; + if (err) { + server.emit('error', err); + client.end('McSessionUnavailable'); + } else if (resp.serverError) { + myErr = new Error("session.minecraft.net is broken: " + resp.status); + myErr.code = 'EMCSESSION500'; + server.emit('error', myErr); + client.end('McSessionDown'); + } else if (resp.serverError) { + myErr = new Error("session.minecraft.net rejected request: " + resp.status); + myErr.code = 'EMCSESSION400'; + server.emit('error', myErr); + client.end('McSessionRejectedAuthRequest'); + } else if (resp.text !== "YES") { + client.end('FailedToVerifyUsername'); + } else { + loginClient(); + } + }); } } diff --git a/package.json b/package.json index ef6e7e0..5434275 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "ursa": "~0.8.0", - "superagent": "~0.10.0" + "superagent": "~0.10.0", + "buffer-equal": "0.0.0" } }