Created
August 5, 2012 06:53
-
-
Save subzey/3262468 to your computer and use it in GitHub Desktop.
My old and dirty telnet server prototype
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var net = require('net'); | |
| var telnet = require('./telnet.lib.js'); | |
| var boxDrawings = { | |
| 'ascii': [ | |
| [0xDC], // lower | |
| [0xDF], // upper | |
| [0xDB], // full | |
| [0xB2], // 75%, | |
| [0xB3], // 50%, | |
| [0xB0], // 25%, | |
| [0x20], // none | |
| ], | |
| 'utf-8': [ | |
| [0xE2, 0x96, 0x84], // lower | |
| [0xE2, 0x96, 0x80], // upper | |
| [0xE2, 0x96, 0x88], // full | |
| [0xE2, 0x96, 0x93], // 75%, | |
| [0xE2, 0x96, 0x92], // 50%, | |
| [0xE2, 0x96, 0x91], // 25%, | |
| [0x20], // none | |
| ] | |
| } | |
| var server = net.createServer(function(connection){ | |
| var telnetHandler = telnet.setTelnetHandler(connection); | |
| telnetHandler.on('data', function(data){console.log("Data: " + data + " (" + data.toString('hex') + ")")}); | |
| var windowWidth = 80; | |
| var windowHeight = 25; | |
| var terminalType = []; | |
| var encoding = undefined; | |
| var encodingHackRequested; | |
| var box = []; | |
| var termEmitter = new (require("events").EventEmitter); | |
| telnetHandler.writeCommand(telnetHandler.WILL, 24); | |
| telnetHandler.writeCommand(telnetHandler.DO, 24); /* Ready to get terminal type */ | |
| telnetHandler.writeCommand(telnetHandler.SB, 24, [1]); /* Require terminal type */ | |
| telnetHandler.writeCommand(telnetHandler.WILL, 31); | |
| telnetHandler.writeCommand(telnetHandler.DO, 31); /* Ready to get window size notifications */ | |
| telnetHandler.writeCommand(telnetHandler.WILL, 1); | |
| telnetHandler.writeCommand(telnetHandler.DO, 1); /* Echo */ | |
| telnetHandler.writeCommand(telnetHandler.WILL, 3); | |
| telnetHandler.writeCommand(telnetHandler.DO, 3); /* No go-ahead term freeze */ | |
| telnetHandler.on('command', function(commandName, commandOption, commandArguments){ | |
| switch (commandOption){ | |
| case 31: | |
| if (commandName == telnetHandler.SB){ | |
| if (commandArguments.length < 4) return; | |
| var width = commandArguments.readUInt16BE(0); | |
| var height = commandArguments.readUInt16BE(2); | |
| if (windowWidth == width && windowHeight == height){ | |
| return; | |
| }; | |
| windowWidth = width; | |
| windowHeight = height; | |
| console.log("New termsize: " + windowWidth + "x" + windowHeight); | |
| termEmitter.emit('size', windowWidth, windowHeight) | |
| }; | |
| break; | |
| case 24: | |
| console.log([commandName, commandOption, commandArguments.toString('hex')].join("\t")); | |
| if (commandName == telnetHandler.SB){ | |
| if (commandArguments == [0]){ | |
| var termtype = "NODEJS"; | |
| telnetHandler.writeCommand(telnetHandler.SB, 24, "\x01" + termtype); | |
| } else { | |
| var terminalTypeReported = commandArguments.toString('ascii', 1).toLowerCase(); | |
| if (!~terminalType.indexOf(terminalTypeReported)){ | |
| terminalType.push(terminalTypeReported); | |
| /* Windows 7 terminal gets stuck until it says all */ | |
| telnetHandler.writeCommand(telnetHandler.SB, 24, [1]); | |
| } else { | |
| console.log("Terminal type: " + terminalType); | |
| getEncoding(); | |
| }; | |
| }; | |
| }; | |
| break; | |
| }; | |
| }) | |
| telnetHandler.on('data', function(data){ | |
| /* Process CSI */ | |
| if (data[0] == 0x1B && data[1] == 0x5b){ | |
| var csi = []; | |
| for (var i=2; i<data.length; i++){ | |
| csi.push(data[i]); | |
| if (data[i] >= 0x40 && data[i] <= 0x5F) break; | |
| }; | |
| var csiAscii = new Buffer(csi).toString('ascii'); | |
| if (cursorPos = /^(\d+);(\d+)R$/.exec(csiAscii)){ | |
| console.log('Cursor position is ' + cursorPos[1] + "x" + cursorPos[2]); | |
| if (encodingHackRequested){ | |
| encodingHackRequested = false; | |
| if (cursorPos[2] > 5){ | |
| encoding = 'ascii'; | |
| } else { | |
| encoding = 'utf-8'; | |
| }; | |
| console.log('Detected: ' + encoding); | |
| metaReady(); | |
| }; | |
| }; | |
| } else { | |
| connection.end('\r\nBye!'); | |
| }; | |
| }) | |
| function getEncoding(){ | |
| telnetHandler.write([ | |
| 0x1b, 0x5d, /* XTERM OSC */ | |
| 0x32, 0x3b, /* "2;" meaning set window title */ | |
| 0x46, 0x6F, 0x6F, /* "Foo" */ | |
| 0x1b, 0x5C, // XTERM ST | |
| 0x1b, 0x5b, 0x3F, 0x37, 0x6C, // CSI "?7l", no wrap-around | |
| 0x1b, 0x5b, 0x3F, 0x32, 0x35, 0x6C, // CSI "?25l", hide cursor | |
| 0x1b, 0x5b, 0x31, 0x3B, 0x31, 0x48, // Set cursor to 1; 1 | |
| 0xE2, 0x93, 0x83, 0xE2, 0x93, 0x8E, 0xE2, 0x92, 0xB6, 0xE2, 0x93, 0x83, /* Test */ | |
| 0x1b, 0x5b, 0x36, 0x6e, // CSI, "6n" tell cursor position | |
| 0x1b, 0x5b, 0x32, 0x4A, // Clear screen entirely | |
| 0x1b, 0x5b, 0x31, 0x3B, 0x31, 0x48, // Set cursor to 1; 1 | |
| ]); | |
| encodingHackRequested = true; | |
| } | |
| function metaReady(){ | |
| if (encoding && terminalType.length){ | |
| box = boxDrawings[encoding]; | |
| drawGreets(); | |
| termEmitter.on('size', drawGreets); | |
| }; | |
| } | |
| function drawGreets(){ | |
| color = [ | |
| [0x1B, 0x5B, 0x31, 0x3B, 0x33, 0x37, 0x6D], | |
| [0x1B, 0x5B, 0x31, 0x3B, 0x33, 0x32, 0x6D] | |
| ] | |
| var spacer = []; | |
| var spacing = Math.max(windowWidth - 78, 0) >> 1; | |
| for (var i=0; i<spacing; i++){ | |
| spacer[i] = box[6]; | |
| }; | |
| var vertcing = Math.max(windowHeight - 12, 0) >> 1; | |
| var vertcer = []; | |
| for (var i=0; i<vertcing; i++){ | |
| vertcer.push(0x0D, 0x0A); | |
| }; | |
| telnetHandler.write([0x1b, 0x5b, 0x32, 0x4A, 0x1b, 0x5b, 0x31, 0x3B, 0x31, 0x48]); // cls | |
| telnetHandler.write(vertcer); | |
| telnetHandler.write([].concat(spacer, color[0],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[2],box[2],box[0],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[2],box[2],box[2],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[2],box[2],box[2],box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[6],box[6],box[6],box[6],box[0],box[0],box[2],box[2],box[0],box[0],box[6],box[6],box[6],box[6],box[6],box[6], color[1], box[6],box[6],box[6],box[6],box[0],box[0],box[2],box[2],box[0],box[0],box[6],box[6],box[6],box[6], color[0], box[6],box[6],box[6],box[6],box[6],box[6],box[0],box[0],box[2],box[2],box[0],box[0],box[6],box[2],box[2],box[2],box[6],box[6],box[6],box[6],box[6],box[6], box[0],box[0],box[2],box[2],box[0],box[0], box[6],box[6],box[6],box[6],box[6],box[6], color[1], box[6],box[6],box[6],box[6],box[0],box[0],box[1],box[1],box[0],box[0],box[6],box[6],box[6],box[6],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[0],box[0],box[2],box[2],box[2],box[2],box[1],box[1],box[2],box[2],box[2],box[2],box[0],box[0],box[6],box[6], color[1], box[0],box[0],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[0],box[0], color[0], box[6],box[6],box[0],box[0],box[2],box[2],box[2],box[2],box[1],box[1],box[2],box[2],box[2],box[2],box[2],box[2],box[6],box[6],box[0],box[0],box[2],box[2], box[2],box[2],box[1],box[1],box[2],box[2], box[2],box[2],box[0],box[0],box[6],box[6], color[1], box[0],box[0],box[1],box[1],box[6],box[6],box[6],box[6],box[6],box[6],box[1],box[1],box[0],box[0],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[2],box[2],box[2],box[1],box[6],box[6],box[6],box[6],box[6],box[6],box[1],box[2],box[2],box[2],box[6],box[6], color[1], box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2], color[0], box[6],box[6],box[2],box[2],box[2],box[1],box[6],box[6],box[6],box[6],box[6],box[6],box[1],box[2],box[2],box[2],box[6],box[6],box[2],box[2],box[2],box[1], color[1], box[6],box[0],box[0],box[0],box[0],box[6], color[0], box[1],box[2],box[2],box[2],box[6],box[6], color[1], box[2],box[6],box[6],box[6],box[2],box[6],box[6],box[0],box[1],box[1],box[1],box[6],box[6],box[2],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[2],box[2],box[2],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[2],box[2],box[2],box[6],box[6], color[1], box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2], color[0], box[6],box[6],box[2],box[2],box[2],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[2],box[2],box[2],box[6],box[6],box[2],box[2],box[2],box[6], color[1], box[2],box[2],box[2],box[2],box[2],box[2], color[0], box[6],box[2],box[2],box[1],box[6],box[6], color[1], box[2],box[6],box[6],box[6],box[2],box[6],box[6],box[6],box[1],box[1],box[0],box[6],box[6],box[2],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[2],box[2],box[2],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[2],box[2],box[2],box[6],box[6], color[1], box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2], color[0], box[6],box[6],box[2],box[2],box[2],box[0],box[6],box[6],box[6],box[6],box[6],box[6],box[0],box[2],box[2],box[2],box[6],box[6],box[2],box[2],box[2],box[0], color[1], box[6],box[1],box[1],box[1],box[1],box[6], color[0], box[6],box[6],box[6],box[6],box[6],box[6], color[1], box[2],box[6],box[6],box[6],box[2],box[6],box[6],box[0],box[0],box[0],box[1],box[6],box[6],box[2],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[1],box[1],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[1],box[1],box[6],box[6], color[1], box[1],box[1],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[2],box[1],box[1], color[0], box[6],box[6],box[1],box[1],box[2],box[2],box[2],box[2],box[0],box[0],box[2],box[2],box[2],box[2],box[1],box[1],box[6],box[6],box[1],box[1],box[2],box[2], box[2],box[2],box[0],box[0],box[6],box[6], box[6],box[6],box[6],box[6],box[6],box[6], color[1], box[1],box[1],box[0],box[0],box[1],box[6],box[6],box[6],box[6],box[6],box[0],box[0],box[1],box[1],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([].concat(spacer, color[0],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], color[1], box[6],box[6],box[6],box[6],box[1],box[1],box[2],box[2],box[1],box[1],box[6],box[6],box[6],box[6], color[0], box[6],box[6],box[6],box[6],box[6],box[6],box[1],box[1],box[2],box[2],box[1],box[1],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6],box[6], box[1],box[1],box[2],box[2],box[1],box[1], box[6],box[6],box[6],box[6],box[6],box[6], color[1], box[6],box[6],box[6],box[6],box[6],box[1],box[0],box[0],box[1],box[1],box[6],box[6],box[6],box[6],[0x0D, 0x0A, 0x00])); | |
| telnetHandler.write([0x1B, 0x5B, 0x30, 0x6D]); // reset color | |
| // telnetHandler.write('\r\nGreetings!\r\nYour terminal type is ' + terminalType + ' and encoding is detected as ' + encoding + '.\r\n'); | |
| } | |
| console.log('Connection established'); | |
| connection.on('end', function() { | |
| console.log('Connection closed'); | |
| }); | |
| connection.on('data', function(data){console.log(data);}); | |
| }); | |
| server.listen(2323, function() { | |
| console.log('Server started'); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var constants = { | |
| "SE" : 240, | |
| "NOP" : 241, | |
| "DM" : 242, | |
| "BRK" : 243, | |
| "IP" : 244, | |
| "AO" : 245, | |
| "AYT" : 246, | |
| "EC" : 247, | |
| "EL" : 248, | |
| "GA" : 249, | |
| "SB" : 250, | |
| "WILL" : 251, | |
| "WONT" : 252, | |
| "WON'T" : 252, | |
| "DO" : 253, | |
| "DONT" : 254, | |
| "DON'T" : 254, | |
| "IAC" : 255 | |
| }; | |
| with(constants){ | |
| var TelnetParser = function(emitter){ | |
| var dataTrailer = []; | |
| var parserState = 0; | |
| var commandName = 0; | |
| var commandOption = 0; | |
| var commandArguments = []; | |
| return function(data){ | |
| for (var i=0; i<data.length; i++){ | |
| var octet = data[i]; | |
| switch(parserState){ | |
| case 0: /* plain text */ | |
| switch(data[i]){ | |
| case 0: | |
| break; /* null bytes are ignored */ | |
| case IAC: | |
| if (data[i+1] == IAC){ | |
| i++; | |
| dataTrailer.push(octet); | |
| } else { | |
| if (dataTrailer.length){ | |
| emitter.emit('data', new Buffer(dataTrailer)); | |
| dataTrailer = []; | |
| }; | |
| parserState = 100; | |
| }; | |
| break; | |
| default: | |
| dataTrailer.push(octet); | |
| } | |
| break; | |
| case 100: /* telnet command */ | |
| commandName = octet; | |
| switch (octet){ | |
| case SB: | |
| parserState = 110; | |
| commandOption = 0; | |
| break; | |
| case WILL: | |
| case WONT: | |
| case DO: | |
| case DONT: | |
| parserState = 101; | |
| break; | |
| default: | |
| emitter.emit('command', octet, 0, new Buffer(0)); | |
| parserState = 0; | |
| }; | |
| break; | |
| case 101: /* command option */ | |
| emitter.emit('command', commandName, octet, new Buffer(0)); | |
| parserState = 0; | |
| break; | |
| case 110: /* subnegotiation option */ | |
| commandOption = octet; | |
| commandArguments = []; | |
| parserState = 111; | |
| break; | |
| case 111: /* subnegotiation arguments */ | |
| if (octet == IAC){ | |
| parserState = 112; | |
| } else { | |
| commandArguments.push(octet); | |
| } | |
| break; | |
| case 112: /* IAC inside subnegotiation */ | |
| switch(octet){ | |
| case SE: | |
| emitter.emit('command', commandName, commandOption, new Buffer(commandArguments)); | |
| parserState = 0; | |
| break; | |
| case IAC: | |
| commandArguments.push(IAC); | |
| default: | |
| commandArguments.push(IAC, octet); /* not covered in spec */ | |
| } | |
| break; | |
| }; | |
| }; | |
| if(dataTrailer.length){ | |
| emitter.emit('data', new Buffer(dataTrailer)); | |
| dataTrailer = []; | |
| }; | |
| }; | |
| }; | |
| var TelnetHandler = function(socket){ | |
| if(this.constructor != TelnetHandler){ | |
| throw new Error("TelnetHandler is a constructor") | |
| }; | |
| if (!(0 in arguments) || socket == null){ | |
| throw new Error("socket is required") | |
| }; | |
| delete this.skipWrite; | |
| this.socket = socket; | |
| this.features = []; | |
| socket.on('data', TelnetParser(this)); | |
| }; | |
| require("util").inherits(TelnetHandler, require("events").EventEmitter); | |
| TelnetHandler.prototype.skipWrite = false; | |
| TelnetHandler.prototype.socket = null; | |
| TelnetHandler.prototype.features = null; | |
| TelnetHandler.prototype.write = function(data){ | |
| if (!Buffer.isBuffer(data)){ | |
| data = new Buffer(data); | |
| }; | |
| var output; | |
| var dups = []; | |
| var len = data.length; | |
| for (var i=0; i<len; i++){ | |
| if (data[i] == 255){ | |
| dups.push(i); | |
| }; | |
| }; | |
| if (dups.length){ | |
| output = new Buffer(data.length + dups.length); | |
| var start = 0; | |
| for (var i=0; i< dups.length; i++){ | |
| var offset = dups[i]; | |
| data.copy(output, start + i, start, offset); | |
| output[offset + i] = 255; | |
| start = offset; | |
| }; | |
| data.copy(output, start + i, start); | |
| } else { | |
| output = data; | |
| }; | |
| if (this.socket){ | |
| this.socket.write(output); | |
| }; | |
| }; | |
| TelnetHandler.prototype.writeCommand = function(commandName, commandOption, commandArguments){ | |
| commandName &= 0xFF; | |
| commandOption &= 0xFF; | |
| var output = null; | |
| switch (commandName){ | |
| case SB: | |
| commandArguments = new Buffer(commandArguments); | |
| var output = new Buffer(5 + commandArguments.length); | |
| (new Buffer([IAC, SB, commandOption])).copy(output, 0); | |
| commandArguments.copy(output, 3); | |
| (new Buffer([IAC, SE])).copy(output, output.length - 2); | |
| break; | |
| case WILL: | |
| case WONT: | |
| case DO: | |
| case DONT: | |
| output = new Buffer([IAC, commandName, commandOption]); | |
| break; | |
| default: | |
| output = new Buffer([IAC, commandName]); | |
| }; | |
| if (output && this.socket){ | |
| this.socket.write(output); | |
| }; | |
| }; | |
| }; | |
| for (var i in constants){ | |
| TelnetHandler.prototype[i] = constants[i]; | |
| }; | |
| exports.setTelnetHandler = function(socket){ | |
| return new TelnetHandler(socket); | |
| }; | |
| exports.TelnetHandler = TelnetHandler; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment