Skip to content

Instantly share code, notes, and snippets.

@subzey
Created August 5, 2012 06:53
Show Gist options
  • Select an option

  • Save subzey/3262468 to your computer and use it in GitHub Desktop.

Select an option

Save subzey/3262468 to your computer and use it in GitHub Desktop.
My old and dirty telnet server prototype
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');
});
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