telnet-stream
Advanced tools
Comparing version 0.0.4 to 1.0.3
@@ -1,1 +0,1 @@ | ||
module.exports = require('./lib/telnetStream'); | ||
module.exports = require("./lib/telnetStream"); |
@@ -1,17 +0,35 @@ | ||
// Generated by CoffeeScript 1.7.1 | ||
// Generated by CoffeeScript 2.0.2 | ||
(function() { | ||
var SUBNEG_BUFFER_SIZE, TELNET_COMMAND, TELNET_DATA, TELNET_DO, TELNET_DONT, TELNET_IAC, TELNET_OPTION, TELNET_SUBNEG, TELNET_SUBNEG_COMMAND, TELNET_SUB_BEGIN, TELNET_SUB_END, TELNET_WILL, TELNET_WONT, TelnetInput, Transform, util; | ||
// telnetInput.coffee | ||
// Copyright 2017 Patrick Meade. | ||
SUBNEG_BUFFER_SIZE = 8192; | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as | ||
// published by the Free Software Foundation, either version 3 of the | ||
// License, or (at your option) any later version. | ||
TELNET_COMMAND = 'TELNET_COMMAND'; | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
TELNET_DATA = 'TELNET_DATA'; | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
//---------------------------------------------------------------------------- | ||
var DEFAULT_SUBNEGOTIATION_BUFFER_SIZE, DEFAULT_SUBNEGOTIATION_ERROR_POLICY, TELNET_COMMAND, TELNET_DATA, TELNET_DO, TELNET_DONT, TELNET_IAC, TELNET_OPTION, TELNET_SUBNEG, TELNET_SUBNEG_COMMAND, TELNET_SUB_BEGIN, TELNET_SUB_END, TELNET_WILL, TELNET_WONT, TelnetInput, Transform; | ||
TELNET_OPTION = 'TELNET_OPTION'; | ||
DEFAULT_SUBNEGOTIATION_BUFFER_SIZE = 8192; | ||
TELNET_SUBNEG = 'TELNET_SUBNEG'; | ||
DEFAULT_SUBNEGOTIATION_ERROR_POLICY = "keepBoth"; | ||
TELNET_SUBNEG_COMMAND = 'TELNET_SUBNEG_COMMAND'; | ||
TELNET_COMMAND = "TELNET_COMMAND"; | ||
TELNET_DATA = "TELNET_DATA"; | ||
TELNET_OPTION = "TELNET_OPTION"; | ||
TELNET_SUBNEG = "TELNET_SUBNEG"; | ||
TELNET_SUBNEG_COMMAND = "TELNET_SUBNEG_COMMAND"; | ||
TELNET_DO = 253; | ||
@@ -31,19 +49,22 @@ | ||
Transform = require('stream').Transform; | ||
({Transform} = require("stream")); | ||
util = require('util'); | ||
TelnetInput = class TelnetInput extends Transform { | ||
constructor(opt) { | ||
var options; | ||
options = opt || {}; | ||
super(options); | ||
this.state = TELNET_DATA; | ||
this.subBufSize = options.bufferSize || DEFAULT_SUBNEGOTIATION_BUFFER_SIZE; | ||
this.subBuf = Buffer.alloc(this.subBufSize); | ||
this.errorPolicy = options.errorPolicy || DEFAULT_SUBNEGOTIATION_ERROR_POLICY; | ||
} | ||
TelnetInput = function(options) { | ||
if ((this instanceof TelnetInput) === false) { | ||
return new TelnetInput(options); | ||
} | ||
Transform.call(this, options); | ||
this.state = TELNET_DATA; | ||
this.subBuf = new Buffer(SUBNEG_BUFFER_SIZE); | ||
this._transform = function(chunk, encoding, done) { | ||
var i, _i, _ref; | ||
this.dataBuf = new Buffer(chunk.length); | ||
_transform(chunk, encoding, callback) { | ||
var byte, i, len; | ||
this.dataBuf = Buffer.alloc(chunk.length * 2); | ||
this.dataBufIndex = 0; | ||
for (i = _i = 0, _ref = chunk.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { | ||
this.handle(chunk[i]); | ||
for (i = 0, len = chunk.length; i < len; i++) { | ||
byte = chunk[i]; | ||
this._handle(byte); | ||
} | ||
@@ -53,5 +74,6 @@ if (this.dataBufIndex > 0) { | ||
} | ||
return done(); | ||
}; | ||
this.handle = function(chunkData) { | ||
return callback(); | ||
} | ||
_handle(chunkData) { | ||
switch (this.state) { | ||
@@ -82,3 +104,3 @@ case TELNET_DATA: | ||
this.state = TELNET_DATA; | ||
return this.emit('command', chunkData); | ||
return this.emit("command", chunkData); | ||
} | ||
@@ -90,16 +112,17 @@ break; | ||
this.state = TELNET_DATA; | ||
return this.emit('do', chunkData); | ||
return this.emit("do", chunkData); | ||
case TELNET_DONT: | ||
this.state = TELNET_DATA; | ||
return this.emit('dont', chunkData); | ||
return this.emit("dont", chunkData); | ||
case TELNET_WILL: | ||
this.state = TELNET_DATA; | ||
return this.emit('will', chunkData); | ||
return this.emit("will", chunkData); | ||
case TELNET_WONT: | ||
this.state = TELNET_DATA; | ||
return this.emit('wont', chunkData); | ||
return this.emit("wont", chunkData); | ||
case TELNET_SUB_BEGIN: | ||
this.state = TELNET_SUBNEG; | ||
this.option = chunkData; | ||
return this.subBufIndex = 0; | ||
this.subBufIndex = 0; | ||
return this.subOverflowEmit = false; | ||
} | ||
@@ -112,4 +135,3 @@ break; | ||
default: | ||
this.subBuf[this.subBufIndex] = chunkData; | ||
return this.subBufIndex++; | ||
return this._handleSub(chunkData); | ||
} | ||
@@ -121,19 +143,42 @@ break; | ||
this.state = TELNET_SUBNEG; | ||
this.subBuf[this.subBufIndex] = TELNET_IAC; | ||
return this.subBufIndex++; | ||
return this._handleSub(TELNET_IAC); | ||
case TELNET_SUB_END: | ||
this.state = TELNET_DATA; | ||
return this.emit('sub', this.option, this.subBuf.slice(0, this.subBufIndex)); | ||
return this.emit("sub", this.option, this.subBuf.slice(0, this.subBufIndex)); | ||
default: | ||
return this.state = TELNET_SUBNEG; | ||
this.state = TELNET_SUBNEG; | ||
this.emit("error", new Error("expected IAC or SE")); | ||
switch (this.errorPolicy) { | ||
case "discardBoth": | ||
break; | ||
case "keepData": | ||
return this._handleSub(chunkData); | ||
default: | ||
// "keepBoth" | ||
this._handleSub(TELNET_IAC); | ||
return this._handleSub(chunkData); | ||
} | ||
} | ||
} | ||
}; | ||
return this; | ||
} | ||
_handleSub(subByte) { | ||
if (this.subBufIndex >= this.subBufSize) { | ||
if (!this.subOverflowEmit) { | ||
this.subOverflowEmit = true; | ||
this.emit("error", new Error("subnegotiation buffer overflow")); | ||
} | ||
return; | ||
} | ||
this.subBuf[this.subBufIndex] = subByte; | ||
return this.subBufIndex++; | ||
} | ||
}; | ||
util.inherits(TelnetInput, Transform); | ||
exports.TelnetInput = TelnetInput; | ||
//---------------------------------------------------------------------------- | ||
// end of telnetInput.coffee | ||
}).call(this); |
@@ -1,5 +0,21 @@ | ||
// Generated by CoffeeScript 1.7.1 | ||
// Generated by CoffeeScript 2.0.2 | ||
(function() { | ||
var TELNET_DO, TELNET_DONT, TELNET_IAC, TELNET_SUB_BEGIN, TELNET_SUB_END, TELNET_WILL, TELNET_WONT, TelnetOutput, Transform, duplicateIAC, util; | ||
// telnetOutput.coffee | ||
// Copyright 2017 Patrick Meade. | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as | ||
// published by the Free Software Foundation, either version 3 of the | ||
// License, or (at your option) any later version. | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
//---------------------------------------------------------------------------- | ||
var TELNET_DO, TELNET_DONT, TELNET_IAC, TELNET_SUB_BEGIN, TELNET_SUB_END, TELNET_WILL, TELNET_WONT, TelnetOutput, Transform; | ||
TELNET_DO = 253; | ||
@@ -19,89 +35,84 @@ | ||
Transform = require('stream').Transform; | ||
({Transform} = require("stream")); | ||
util = require('util'); | ||
TelnetOutput = class TelnetOutput extends Transform { | ||
constructor(options) { | ||
super(options); | ||
} | ||
duplicateIAC = function(buffer) { | ||
var bufferIndex, data, xlateBuf, xlateIndex; | ||
bufferIndex = 0; | ||
xlateIndex = 0; | ||
xlateBuf = new Buffer(buffer.length * 2); | ||
while (bufferIndex < buffer.length) { | ||
data = buffer[bufferIndex]; | ||
bufferIndex++; | ||
xlateBuf.writeUInt8(data, xlateIndex); | ||
xlateIndex++; | ||
if (data === TELNET_IAC) { | ||
xlateBuf.writeUInt8(data, xlateIndex); | ||
_transform(chunk, encoding, done) { | ||
this.push(this._duplicateIAC(chunk)); | ||
return done(); | ||
} | ||
_duplicateIAC(buffer) { | ||
var byte, i, len, xlateBuf, xlateIndex; | ||
xlateIndex = 0; | ||
xlateBuf = Buffer.alloc(buffer.length * 2); | ||
for (i = 0, len = buffer.length; i < len; i++) { | ||
byte = buffer[i]; | ||
xlateBuf[xlateIndex] = byte; | ||
xlateIndex++; | ||
if (byte === TELNET_IAC) { | ||
xlateBuf[xlateIndex] = byte; | ||
xlateIndex++; | ||
} | ||
} | ||
return xlateBuf.slice(0, xlateIndex); | ||
} | ||
return xlateBuf.slice(0, xlateIndex); | ||
}; | ||
TelnetOutput = function(options) { | ||
if ((this instanceof TelnetOutput) === false) { | ||
return new TelnetOutput(options); | ||
_writeOption(command, option) { | ||
var cmdBuf; | ||
cmdBuf = Buffer.alloc(3); | ||
cmdBuf[0] = TELNET_IAC; | ||
cmdBuf[1] = command; | ||
cmdBuf[2] = option; | ||
return this.push(cmdBuf); | ||
} | ||
Transform.call(this, options); | ||
return this; | ||
}; | ||
util.inherits(TelnetOutput, Transform); | ||
writeCommand(command) { | ||
var cmdBuf; | ||
cmdBuf = Buffer.alloc(2); | ||
cmdBuf[0] = TELNET_IAC; | ||
cmdBuf[1] = command; | ||
return this.push(cmdBuf); | ||
} | ||
TelnetOutput.prototype._transform = function(chunk, encoding, done) { | ||
this.push(duplicateIAC(chunk)); | ||
return done(); | ||
}; | ||
writeDo(option) { | ||
return this._writeOption(TELNET_DO, option); | ||
} | ||
TelnetOutput.prototype._writeOption = function(command, option) { | ||
var cmdBuf; | ||
cmdBuf = new Buffer(3); | ||
cmdBuf[0] = TELNET_IAC; | ||
cmdBuf[1] = command; | ||
cmdBuf[2] = option; | ||
return this.push(cmdBuf); | ||
}; | ||
writeDont(option) { | ||
return this._writeOption(TELNET_DONT, option); | ||
} | ||
TelnetOutput.prototype.writeCommand = function(command) { | ||
var cmdBuf; | ||
cmdBuf = new Buffer(2); | ||
cmdBuf[0] = TELNET_IAC; | ||
cmdBuf[1] = command; | ||
return this.push(cmdBuf); | ||
}; | ||
writeSub(option, buffer) { | ||
var negBuf, subBegin, subBuf, subEnd; | ||
negBuf = this._duplicateIAC(buffer); | ||
subBegin = Buffer.alloc(3); | ||
subBegin[0] = TELNET_IAC; | ||
subBegin[1] = TELNET_SUB_BEGIN; | ||
subBegin[2] = option; | ||
subEnd = Buffer.alloc(2); | ||
subEnd[0] = TELNET_IAC; | ||
subEnd[1] = TELNET_SUB_END; | ||
subBuf = Buffer.concat([subBegin, negBuf, subEnd], negBuf.length + 5); | ||
return this.push(subBuf); | ||
} | ||
TelnetOutput.prototype.writeDo = function(option) { | ||
return this._writeOption(TELNET_DO, option); | ||
}; | ||
writeWill(option) { | ||
return this._writeOption(TELNET_WILL, option); | ||
} | ||
TelnetOutput.prototype.writeDont = function(option) { | ||
return this._writeOption(TELNET_DONT, option); | ||
}; | ||
TelnetOutput.prototype.writeSub = function(option, buffer) { | ||
var i, negBuf, subBuf, _i, _ref; | ||
negBuf = duplicateIAC(buffer); | ||
subBuf = new Buffer(negBuf.length + 5); | ||
subBuf[0] = TELNET_IAC; | ||
subBuf[1] = TELNET_SUB_BEGIN; | ||
subBuf[2] = option; | ||
for (i = _i = 0, _ref = negBuf.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { | ||
subBuf[i + 3] = negBuf[i]; | ||
writeWont(option) { | ||
return this._writeOption(TELNET_WONT, option); | ||
} | ||
subBuf[negBuf.length + 3] = TELNET_IAC; | ||
subBuf[negBuf.length + 4] = TELNET_SUB_END; | ||
return this.push(subBuf); | ||
}; | ||
TelnetOutput.prototype.writeWill = function(option) { | ||
return this._writeOption(TELNET_WILL, option); | ||
}; | ||
TelnetOutput.prototype.writeWont = function(option) { | ||
return this._writeOption(TELNET_WONT, option); | ||
}; | ||
exports.TelnetOutput = TelnetOutput; | ||
//---------------------------------------------------------------------------- | ||
// end of telnetOutput.coffee | ||
}).call(this); |
@@ -1,7 +0,28 @@ | ||
// Generated by CoffeeScript 1.7.1 | ||
// Generated by CoffeeScript 2.0.2 | ||
(function() { | ||
exports.TelnetInput = require('./telnetInput').TelnetInput; | ||
// telnetStream.coffee | ||
// Copyright 2017 Patrick Meade. | ||
exports.TelnetOutput = require('./telnetOutput').TelnetOutput; | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as | ||
// published by the Free Software Foundation, either version 3 of the | ||
// License, or (at your option) any later version. | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
//---------------------------------------------------------------------------- | ||
exports.TelnetInput = require("./telnetInput").TelnetInput; | ||
exports.TelnetOutput = require("./telnetOutput").TelnetOutput; | ||
exports.TelnetSocket = require("./telnetSocket").TelnetSocket; | ||
//---------------------------------------------------------------------------- | ||
// end of telnetStream.coffee | ||
}).call(this); |
{ | ||
"name": "telnet-stream", | ||
"version": "0.0.4", | ||
"description": "Transform streams for TELNET protocol", | ||
"version": "1.0.3", | ||
"description": "TELNET protocol support library", | ||
"keywords": [ | ||
@@ -17,2 +17,3 @@ "do", | ||
"rfc855", | ||
"socket", | ||
"stream", | ||
@@ -29,5 +30,8 @@ "TELNET", | ||
"files": [ | ||
"CHANGELOG.md", | ||
"index.js", | ||
"lib", | ||
"package.json" | ||
"LICENSE", | ||
"package.json", | ||
"README.md" | ||
], | ||
@@ -40,6 +44,7 @@ "main": "telnet-stream", | ||
"devDependencies": { | ||
"coffee-script": "1.7.x", | ||
"mocha": "1.19.x", | ||
"should": "3.3.x" | ||
"coffeescript": "2.0.2", | ||
"istanbul": "0.4.5", | ||
"mocha": "4.0.1", | ||
"should": "13.1.3" | ||
} | ||
} |
445
README.md
@@ -5,49 +5,41 @@ # telnet-stream | ||
## Motivation | ||
Although venerable, the [TELNET](https://en.wikipedia.org/wiki/Telnet) | ||
protocol is still in use by some services and expected by some clients. | ||
If you need to connect to something that "speaks TELNET", this module | ||
offers a pair of bare-bones Transform streams for that purpose. | ||
offers some simple objects for that purpose. | ||
## Simple Solution | ||
## Example 0: A Simple Solution | ||
If you need to connect to something that speaks TELNET, but you don't | ||
care about options or negotiations, then simply use the stream as-is. | ||
It will filter out all the "TELNET stuff" and pass the rest on to you. | ||
care about options or negotiations, then simply use TelnetSocket to | ||
decorate a regular socket. It will filter out all the "TELNET stuff" | ||
and pass the remaining data on to you. | ||
Because TelnetInput and TelnetOutput are Node.js Transform streams, | ||
they support all the same operations that regular streams do. See the | ||
[Node.js Stream API](http://nodejs.org/api/stream.html) for more details. | ||
// get references to the required stuff | ||
var net = require('net'); | ||
var TelnetInput = require('telnet-stream').TelnetInput; | ||
var TelnetOutput = require('telnet-stream').TelnetOutput; | ||
var TelnetSocket = require('telnet-stream').TelnetSocket; | ||
// create a Socket connection | ||
var socket = net.createConnection(3000, 'godwars2.org'); | ||
// decorate the Socket connection as a TelnetSocket | ||
var tSocket = new TelnetSocket(socket); | ||
// send the TelnetSocket output to the standard output (terminal) | ||
tSocket.pipe(process.stdout); | ||
// send the standard input (keyboard) to the TelnetSocket | ||
process.stdin.pipe(tSocket); | ||
var socket = net.createConnection(3000, 'godwars2.org', function() { | ||
var telnetInput = new TelnetInput(); | ||
var telnetOutput = new TelnetOutput(); | ||
socket.pipe(telnetInput).pipe(process.stdout); | ||
process.stdin.pipe(telnetOutput).pipe(socket); | ||
}); | ||
## Usage | ||
Maybe you have more complex needs. Perhaps you need certain options | ||
to be turned on or off, or have important information to pull from | ||
a subnegotiation. telnet-stream has you covered in those situations. | ||
a subnegotiation. This is pretty easy to do with TelnetSocket. | ||
### TelnetInput | ||
### TelnetSocket input | ||
TelnetSocket is a decorator for a [net.Socket](https://nodejs.org/api/net.html) | ||
object. Incoming TELNET commands, options, and negotiations are emitted as | ||
events. Non-TELNET data is passed through without changes. | ||
TelnetInput is a Transform stream for the input side of TELNET. | ||
TELNET commands, options, and negotiations are emitted as events. | ||
Non-TELNET data is passed transparently as input data. | ||
#### Event: 'command' | ||
When the remote system issues a TELNET command that is not option | ||
negotiation, TelnetInput will emit a 'command' event. | ||
negotiation, TelnetSocket will emit a 'command' event. | ||
var telnetIn = new TelnetInput(); | ||
telnetIn.on('command', function(command) { | ||
var tSocket = new TelnetSocket(socket); | ||
tSocket.on('command', function(command) { | ||
// Received: IAC <command> - See RFC 854 | ||
@@ -57,9 +49,8 @@ }); | ||
#### Event: 'do' | ||
When the remote system wants to request that the local system | ||
perform some function or obey some protocol, TelnetInput will | ||
perform some function or obey some protocol, TelnetSocket will | ||
emit a 'do' event: | ||
var telnetIn = new TelnetInput(); | ||
telnetIn.on('do', function(option) { | ||
var tSocket = new TelnetSocket(socket); | ||
tSocket.on('do', function(option) { | ||
// Received: IAC DO <option> - See RFC 854 | ||
@@ -69,9 +60,8 @@ }); | ||
#### Event: 'dont' | ||
When the remote system wants to request that the local system | ||
NOT perform some function or NOT obey some protocol, TelnetInput | ||
NOT perform some function or NOT obey some protocol, TelnetSocket | ||
will emit a 'dont' event: | ||
var telnetIn = new TelnetInput(); | ||
telnetIn.on('dont', function(option) { | ||
var tSocket = new TelnetSocket(socket); | ||
tSocket.on('dont', function(option) { | ||
// Received: IAC DONT <option> - See RFC 854 | ||
@@ -81,3 +71,2 @@ }); | ||
#### Event: 'sub' | ||
After negotiating an option, either the local or remote system | ||
@@ -88,4 +77,4 @@ may engage in a more complex subnegotiation. For example, the | ||
var telnetIn = new TelnetInput(); | ||
telnetIn.on('sub', function(option, buffer) { | ||
var tSocket = new TelnetSocket(socket); | ||
tSocket.on('sub', function(option, buffer) { | ||
// Received: IAC SB <option> <buffer> IAC SE - See RFC 855 | ||
@@ -95,9 +84,8 @@ }); | ||
#### Event: 'will' | ||
When the remote system wants to offer that it will perform some | ||
function or obey some protocol for the local system, TelnetInput | ||
function or obey some protocol for the local system, TelnetSocket | ||
will emit a 'will' event: | ||
var telnetIn = new TelnetInput(); | ||
telnetIn.on('will', function(option) { | ||
var tSocket = new TelnetSocket(socket); | ||
tSocket.on('will', function(option) { | ||
// Received: IAC WILL <option> - See RFC 854 | ||
@@ -107,41 +95,36 @@ }); | ||
#### Event: 'wont' | ||
When the remote system wants to refuse to perform some function | ||
or obey some protocol for the local system, TelnetInput will | ||
or obey some protocol for the local system, TelnetSocket will | ||
emit a 'wont' event: | ||
var telnetIn = new TelnetInput(); | ||
telnetIn.on('wont', function(option) { | ||
var tSocket = new TelnetSocket(socket); | ||
tSocket.on('wont', function(option) { | ||
// Received: IAC WONT <option> - See RFC 854 | ||
}); | ||
### TelnetOutput | ||
### TelnetSocket output | ||
TelnetSocket is a decorator for a [net.Socket](https://nodejs.org/api/net.html) | ||
object. Outgoing data is properly escaped where it might be confused | ||
for a TELNET command. There are also support functions to allow sending | ||
TELNET commands, options, and negotiations as well. | ||
TelnetOutput is a Transform stream for the output side of TELNET. | ||
Data written to TelnetOutput is properly escaped to ensure that | ||
it isn't interpreted as a TELNET command. It also has methods for | ||
sending TELNET option negotiations and subnegotiations. | ||
#### IAC escape | ||
TELNET commands start with the Interpret as Command (IAC) octet. | ||
In order to send a literal IAC octet (one that is intended as data, | ||
not as a TELNET command), it must be sent as IAC IAC. TelnetOutput | ||
TELNET commands start with the Interpret as Command (IAC) byte. | ||
In order to send a literal IAC byte (one that is intended as data, | ||
not as a TELNET command), it must be sent as IAC IAC. TelnetSocket | ||
takes care of this transformation automatically. | ||
#### writeCommand(command) | ||
* command - The command byte to send | ||
* command - The command octet to send | ||
Call this method to send a TELNET command to the remote system. | ||
var NOP = 241; // No operation. -- See RFC 854 | ||
var telnetOut = new TelnetOutput(); | ||
var tSocket = new TelnetSocket(socket); | ||
// Sends: IAC NOP | ||
telnetOut.writeCommand(NOP); | ||
tSocket.writeCommand(NOP); | ||
#### writeDo(option) | ||
* option - The option byte to request of the remote system | ||
* option - The option octet to request of the remote system | ||
Call this method to send a TELNET DO option negotiation to the remote | ||
@@ -152,10 +135,9 @@ system. A DO request is sent when the local system wants the remote | ||
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073 | ||
var telnetOut = new TelnetOutput(); | ||
var tSocket = new TelnetSocket(socket); | ||
// Sends: IAC DO NAWS | ||
telnetOut.writeDo(NAWS); | ||
tSocket.writeDo(NAWS); | ||
#### writeDont(option) | ||
* option - The option byte to request of the remote system | ||
* option - The option octet to request of the remote system | ||
Call this method to send a TELNET DONT option negotiation to the remote | ||
@@ -166,14 +148,13 @@ system. A DONT request is sent when the local system wants the remote | ||
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073 | ||
var telnetOut = new TelnetOutput(); | ||
var tSocket = new TelnetSocket(socket); | ||
// Sends: IAC DONT NAWS | ||
telnetOut.writeDont(NAWS); | ||
tSocket.writeDont(NAWS); | ||
#### writeSub(option, buffer) | ||
* option - The option octet; identifies what the subnegotiation is about | ||
* option - The option byte; identifies what the subnegotiation is about | ||
* buffer - The buffer containing the subnegotiation data to send | ||
Call this method to send a TELNET subnegotiation to the remote system. | ||
After the local and remote system have negotiated an option, then | ||
subnegotiation information can be sent. | ||
After the local and remote system have negotiated and agreed to use | ||
an option, then subnegotiation information can be sent. | ||
@@ -183,5 +164,4 @@ See Example #2: Negotiate About Window Size (NAWS) below. | ||
#### writeWill(option) | ||
* option - The option byte to offer to the remote system | ||
* option - The option octet to offer to the remote system | ||
Call this method to send a TELNET WILL option negotiation to the remote | ||
@@ -192,10 +172,9 @@ system. A WILL offer is sent when the local system wants to inform the | ||
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073 | ||
var telnetOut = new TelnetOutput(); | ||
var tSocket = new TelnetSocket(socket); | ||
// Sends: IAC WILL NAWS | ||
telnetOut.writeWill(NAWS); | ||
tSocket.writeWill(NAWS); | ||
#### writeWont(option) | ||
* option - The option byte to refuse to the remote system | ||
* option - The option octet to refuse to the remote system | ||
Call this method to send a TELNET WONT option negotiation to the remote | ||
@@ -207,8 +186,7 @@ system. A WONT refusal is sent when the remote system has requested that | ||
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073 | ||
var telnetOut = new TelnetOutput(); | ||
var tSocket = new TelnetSocket(socket); | ||
// Sends: IAC WONT NAWS | ||
telnetOut.writeWont(NAWS); | ||
tSocket.writeWont(NAWS); | ||
### Example 1: Options Actively Refused | ||
The simple example above provided a simple TELNET client. | ||
@@ -225,20 +203,21 @@ However, all TELNET commands were filtered and ignored. | ||
var net = require('net'); | ||
var TelnetInput = require('telnet-stream').TelnetInput; | ||
var TelnetOutput = require('telnet-stream').TelnetOutput; | ||
var TelnetSocket = require('telnet-stream').TelnetSocket; | ||
var socket = net.createConnection(3000, 'godwars2.org'); | ||
var tSocket = new TelnetSocket(socket); | ||
var socket = net.createConnection(3000, 'godwars2.org', function() { | ||
var telnetInput = new TelnetInput(); | ||
var telnetOutput = new TelnetOutput(); | ||
// tell remote we WONT do anything we're asked to DO | ||
tSocket.on('do', function(option) { | ||
tSocket.writeWont(option); | ||
}); | ||
telnetInput.on('do', function(option) { | ||
telnetOutput.writeWont(option); | ||
}); | ||
// tell the remote DONT do whatever they WILL offer | ||
tSocket.on('will', function(option) { | ||
tSocket.writeDont(option); | ||
}); | ||
telnetInput.on('will', function(option) { | ||
telnetOutput.writeDont(option); | ||
}); | ||
tSocket.pipe(process.stdout); | ||
process.stdin.pipe(tSocket); | ||
socket.pipe(telnetInput).pipe(process.stdout); | ||
process.stdin.pipe(telnetOutput).pipe(socket); | ||
}); | ||
This code is mostly the same as Example 0 except that we respond | ||
to incoming 'do' and 'will' events sent by the remote side. | ||
@@ -251,3 +230,2 @@ Note that incoming 'dont' and 'wont' events are ignored. | ||
### Example 2: Negotiate About Window Size (NAWS) | ||
There is a TELNET option called "Negotiate About Window Size" (NAWS) | ||
@@ -260,3 +238,2 @@ that allows the server to learn the dimensions of the client's output | ||
#### Server Side | ||
This code implements a simple TELNET server that listens for | ||
@@ -266,29 +243,31 @@ NAWS subnegotiations and reports the client's window size | ||
// setup stuff -- see the other examples for details | ||
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073 | ||
var net = require('net'); | ||
var TelnetInput = require('telnet-stream').TelnetInput; | ||
var TelnetOutput = require('telnet-stream').TelnetOutput; | ||
var TelnetSocket = require('telnet-stream').TelnetSocket; | ||
var serverSocket = net.createServer(function(connection) { | ||
var telnetInput = new TelnetInput(); | ||
var telnetOutput = new TelnetOutput(); | ||
// create a service to listen for incoming connections | ||
var server = net.createServer(function(socket) { | ||
// wrap the socket as a TelnetSocket | ||
var tSocket = new TelnetSocket(socket); | ||
telnetInput.on('sub', function(option, buffer) { | ||
// if they send us a subnegotiation | ||
tSocket.on('sub', function(option, buffer) { | ||
// if they are telling us their window size | ||
if(option === NAWS) { | ||
// display it to the console | ||
var width = buffer.readInt16BE(0); | ||
var height = buffer.readInt16BE(2); | ||
console.log( 'Client window: ' + width + 'x' + height); | ||
console.log('Client window: ' + width + 'x' + height); | ||
} | ||
}); | ||
connection.pipe(telnetInput).pipe(process.stdout); | ||
process.stdin.pipe(telnetOutput).pipe(connection); | ||
telnetOutput.writeDo(NAWS); | ||
// tell the client to send window size subnegotiations | ||
tSocket.writeDo(NAWS); | ||
}); | ||
serverSocket.listen(3000); | ||
// start our server listening on port 3000 | ||
server.listen(3000); | ||
#### Client Side | ||
This code implements a simple TELNET client that sends NAWS | ||
@@ -299,62 +278,100 @@ subnegotiations when the output window is resized. Note that | ||
// setup stuff -- see the other examples for details | ||
var NAWS = 31; // Negotiate About Window Size -- See RFC 1073 | ||
var net = require('net'); | ||
var TelnetInput = require('telnet-stream').TelnetInput; | ||
var TelnetOutput = require('telnet-stream').TelnetOutput; | ||
var TelnetSocket = require('telnet-stream').TelnetSocket; | ||
var socket = net.createConnection(3000); | ||
var tSocket = new TelnetSocket(socket); | ||
var socket = net.createConnection(3000, function() { | ||
var telnetInput = new TelnetInput(); | ||
var telnetOutput = new TelnetOutput(); | ||
var serverNawsOk = false; | ||
// flag to indicate if its OK to send window size | ||
// subnegotiations to the server | ||
var serverNawsOk = false; | ||
var sendWindowSize = function() { | ||
var nawsBuffer = new Buffer(4); | ||
nawsBuffer.writeInt16BE(process.stdout.columns, 0); | ||
nawsBuffer.writeInt16BE(process.stdout.rows, 2); | ||
telnetOutput.writeSub(NAWS, nawsBuffer); | ||
}; | ||
// function: send window size to the server | ||
var sendWindowSize = function() { | ||
// create a buffer | ||
var nawsBuffer = new Buffer(4); | ||
// fill the buffer up with our window dimensions | ||
nawsBuffer.writeInt16BE(process.stdout.columns, 0); | ||
nawsBuffer.writeInt16BE(process.stdout.rows, 2); | ||
// send that buffer as a subnegotiation to the server | ||
tSocket.writeSub(NAWS, nawsBuffer); | ||
}; | ||
telnetInput.on('do', function(option) { | ||
if(option === NAWS) { | ||
serverNawsOk = true; | ||
telnetOutput.writeWill(NAWS); | ||
sendWindowSize(); | ||
} | ||
}); | ||
// if the server sends us a DO negotiation | ||
telnetInput.on('do', function(option) { | ||
// if that negotiation is about window size | ||
if(option === NAWS) { | ||
// set the flag indicating that the server has | ||
// told us it's OK to send our window size | ||
serverNawsOk = true; | ||
// tell the server that we WILL send window size | ||
telnetOutput.writeWill(NAWS); | ||
// send our current window size to the server | ||
sendWindowSize(); | ||
} | ||
}); | ||
process.stdout.on('resize', function() { | ||
if(serverNawsOk) { | ||
sendWindowSize(); | ||
} | ||
}); | ||
// if the terminal window is resized | ||
process.stdout.on('resize', function() { | ||
// if we're OK to send our window size to the server | ||
if(serverNawsOk) { | ||
// send the new window size to the server | ||
sendWindowSize(); | ||
} | ||
}); | ||
socket.pipe(telnetInput).pipe(process.stdout); | ||
process.stdin.pipe(telnetOutput).pipe(socket); | ||
// setup stuff -- see the other examples for details | ||
tSocket.pipe(process.stdout); | ||
process.stdin.pipe(tSocket); | ||
telnetOutput.writeWill(NAWS); | ||
}); | ||
Run this program and it should immediately send the current | ||
size of the terminal window to the server. After that, you | ||
can resize your terminal window in order to make the client | ||
program to send the new window size to the server. | ||
## Limitations | ||
## Advanced Use Cases | ||
This section covers advanced use-cases. If you need to use the | ||
TELNET protocol outside of a Socket context, or if you need to | ||
modify some aspects of the protocol handling, this is the | ||
section for you. | ||
telnet-stream is not 100% faithful to the TELNET specification. | ||
### TelnetSocket options | ||
The TelnetSocket constructor takes an optional `options` parameter: | ||
### Subnegotiations | ||
`new TelnetSocket(socket, [options])` | ||
* `socket` - Required: net.Socket to be decorated as a TELNET socket | ||
* `options` - Optional: Options configuration | ||
* `bufferSize` - The size of the subnegotiation buffer | ||
* `errorPolicy` - How to handle subnegotiation command errors | ||
#### Buffer Size | ||
#### bufferSize | ||
After a TELNET option is negotiated between local and remote, | ||
either side may send subnegotiation data to the other. The | ||
TELNET protocol itself specifies no limit to this data. | ||
The input buffer size for TELNET subnegotiations is 8192 bytes. For most | ||
cases, this buffer should suffice. If you are running millions of | ||
simultaneous connections, you may need to reduce it. If your subnegotiations | ||
are HUGE then you may need to increase it. | ||
Practical considerations dictate placing a reasonable limit | ||
on the amount of data buffered. Most services should NOT buffer | ||
an unlimited amount of data. Malicious clients may be able to | ||
cause a Denial of Service attack by forcing the server to | ||
allocate too much memory in response their requests. | ||
The size of the buffer is defined at the top of telnetInput.coffee: | ||
By default, TelnetSocket will buffer to up 8192 (8K = 8 * 1024) | ||
bytes of subnegotiation data. After this, it will emit an `error` | ||
event to indicate an overflow in the subnegotiation buffer. These | ||
additional bytes will be discarded. | ||
SUBNEG_BUFFER_SIZE = 8192 | ||
In order to modify the size of the buffer, one can specify an | ||
options object with the `bufferSize` option: | ||
#### Interpret As Command (IAC) | ||
// this TelnetSocket can handle 16K subnegotiations! | ||
var tSocket = new TelnetSocket(socket, { bufferSize: 16384 }); | ||
The default of 8K should sufficient for most use-cases. | ||
#### errorPolicy | ||
During a subnegotiation, there are two valid sequences that begin with | ||
IAC. One is to escape another IAC intended as a literal data octet: | ||
IAC. One is to escape another IAC intended as a literal data byte: | ||
IAC IAC // this is a literal IAC [Hex 0xFF, Dec 255] octet | ||
IAC IAC // this is a literal IAC [Hex 0xFF, Dec 255] byte | ||
@@ -368,16 +385,37 @@ The other is to end the ongoing subnegotiation: | ||
telnet-stream will silently consume both the IAC and following | ||
octet and return to consuming subnegotiation data. The final | ||
effect is as if those two octets never existed in the stream. | ||
If an unknown sequence is detected; IAC followed by something | ||
that isn't IAC or SE, then an `error` event will be emitted. | ||
The `errorPolicy` option can set a policy for what will happen | ||
to the two erroneous bytes. | ||
This error might occur when talking to faulty TELNET implementations | ||
that fail to properly escape IAC bytes as IAC IAC in subnegotiations. | ||
##### "keepBoth" | ||
By default, it is assumed that a faulty sequence starting with | ||
IAC is a failure to properly escape a data IAC byte as IAC IAC. | ||
TelnetSocket will keep both bytes (the IAC and the following | ||
data byte) and continue the subnegotiation. | ||
##### "keepData" | ||
If you want TelnetSocket to keep the data byte (the byte | ||
following the IAC), but discard the IAC, the error policy | ||
`keepData` will do this. The data byte will be added to the | ||
subnegotiation and the subnegotiation will continue. | ||
// filter out erroneous IAC bytes | ||
var tSocket = new TelnetSocket(socket, { errorPolicy: "keepData" }); | ||
##### "discardBoth" | ||
If you want TelnetSocket to discard both the IAC and the | ||
data byte that follows it, the error policy "discardBoth" | ||
will do this. The subnegotiation will continue, containing | ||
neither of the two erroneous bytes. | ||
// filter out erroneous IAC <data> bytes | ||
var tSocket = new TelnetSocket(socket, { errorPolicy: "discardBoth" }); | ||
### Network Virtual Terminal (NVT) | ||
In addition to TELNET negotiation, RFC 854 specifies a Network Virtual | ||
Terminal (NVT). Among other things in the NVT specification, | ||
a Carriage Return (CR) [Hex 0x0C, Dec 13] octet must be followed by | ||
either a Line Feed (LF) [Hex 0x0A, Dec 10] octet or a Null (NUL) [Hex 0x00, | ||
Dec 0] octet. It says "the CR character must be avoided in other contexts". | ||
a Carriage Return (CR) [Hex 0x0C, Dec 13] byte must be followed by | ||
either a Line Feed (LF) [Hex 0x0A, Dec 10] byte or a Null (NUL) [Hex 0x00, | ||
Dec 0] byte. It says "the CR character must be avoided in other contexts". | ||
@@ -389,8 +427,63 @@ Furthermore, it goes on to specify: "Even though it may be known in some | ||
telnet-stream DOES NOT respect this part of the specification. The character | ||
following a CR in the data stream is not modified in any way. If you want | ||
or need this behavior, you will need to modify telnet-stream. | ||
telnet-stream DOES NOT respect this part of the specification. The | ||
character following a CR in the data stream is never modified in any | ||
way. If you want or need this behavior, please open an issue on GitHub. | ||
The author would be very curious to discover a use-case where this | ||
behavior is both expected and necessary. | ||
### TelnetInput and TelnetOutput | ||
TelnetSocket is built on lower level Transform streams. These | ||
transform streams do the real work of managing the TELNET protocol, | ||
where TelnetSocket is simply a convenience wrapper over a Socket. | ||
If you need TELNET handling outside of a Socket context; for example | ||
filtering TELNET codes from a raw log, you may be interested in these | ||
transform stream components. | ||
Because TelnetInput and TelnetOutput are Node.js Transform streams, | ||
they support all the same operations that regular streams do. See the | ||
[Node.js Stream API](http://nodejs.org/api/stream.html) for more details. | ||
#### TelnetInput | ||
TelnetInput is a Transform stream for the input side of TELNET. | ||
TELNET commands, options, and negotiations are emitted as events. | ||
Non-TELNET data is passed transparently as input data. | ||
See: Event handlers ('command', 'do', 'dont', 'sub', 'will', 'wont') | ||
Like TelnetSocket, the TelnetInput constructor takes an optional | ||
`options` object supporting options `bufferSize` and `errorPolicy`. | ||
`new TelnetInput([options])` | ||
* `options` - Optional: Options configuration | ||
* `bufferSize` - The size of the subnegotiation buffer | ||
* `errorPolicy` - How to handle subnegotiation command errors | ||
#### TelnetOutput | ||
TelnetOutput is a Transform stream for the output side of TELNET. | ||
Data written to TelnetOutput is properly escaped to ensure that it | ||
isn't interpreted as a TELNET command. It also has methods for sending | ||
TELNET option negotiations and subnegotiations. | ||
See: Helper functions (`writeCommand`, `writeDo`, `writeDont`, | ||
`writeSub`, `writeWill`, `writeWont`) | ||
#### Example 0 rewritten with transform streams | ||
This code is equivalent to Example 0, but instead of using TelnetSocket | ||
to decorate the provided Socket, the readable and writable sides | ||
are handled individually by Transform stream objects. | ||
var net = require('net'); | ||
var TelnetInput = require('telnet-stream').TelnetInput; | ||
var TelnetOutput = require('telnet-stream').TelnetOutput; | ||
var socket = net.createConnection(3000, 'godwars2.org', function() { | ||
var telnetInput = new TelnetInput(); | ||
var telnetOutput = new TelnetOutput(); | ||
socket.pipe(telnetInput).pipe(process.stdout); | ||
process.stdin.pipe(telnetOutput).pipe(socket); | ||
}); | ||
## Development | ||
In order to make modifications to telnet-stream, you'll need to | ||
@@ -400,11 +493,17 @@ establish a development environment: | ||
git clone https://github.com/blinkdog/telnet-stream.git | ||
cd telnet-stream | ||
npm install | ||
cake rebuild | ||
node_modules/.bin/cake rebuild | ||
The source files are located in src/coffee | ||
The source files are located in `src/main/coffee`. | ||
The test source files are located in `src/test/coffee`. | ||
You can see a coverage report by invoking the `coverage` target: | ||
node_modules/.bin/cake coverage | ||
## License | ||
telnet-stream | ||
Copyright 2013-2017 Patrick Meade. | ||
telnet-stream is Copyright 2013 Patrick Meade. | ||
This program is free software: you can redistribute it and/or modify | ||
@@ -420,3 +519,3 @@ it under the terms of the GNU Affero General Public License as | ||
You should have received a copy of the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.txt) | ||
You should have received a copy of the GNU Affero General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
70511
9
374
0
501
4
2