UNPKG

telnetlib

Version:

A simple Node.js telnet server/client library.

303 lines (278 loc) 9.32 kB
const Stream = require('stream'); const { optionState, q, where } = require('../constants'); const TelnetReader = require('./TelnetReader'); const TelnetWriter = require('./TelnetWriter'); const options = require('../options'); function expose(object, method, on) { on[method] = (...args) => { return object[method](...args); }; } function reemit(emitter, name, on) { emitter.on(name, (...args) => { on.emit(name, ...args); }); } class TelnetSocket extends Stream.Stream { isTTY = true; isRaw = true; columns = 80; rows = 24; constructor(socket, options = {}) { super(); this.options = new Map(); this.remoteOptions = new Set(options.remoteOptions || []); this.localOptions = new Set(options.localOptions || []); this.socket = socket; this.reader = new TelnetReader(this, options); this.writer = new TelnetWriter(this, options); this.socket.pipe(this.reader); this.writer.pipe(this.socket); expose(this.socket, 'setEncoding', this); expose(this.writer, 'write', this); expose(this.writer, 'end', this); expose(this.writer, 'destroy', this); expose(this.reader, 'resume', this); expose(this.reader, 'pause', this); reemit(this.socket, 'error', this); reemit(this.writer, 'drain', this); reemit(this.writer, 'error', this); reemit(this.reader, 'data', this); reemit(this.reader, 'close', this); reemit(this.reader, 'end', this); reemit(this.reader, 'error', this); this.writable = this.writer.writable; this.readable = this.reader.readable; } // If we decide to ask him to enable: using him and himq // NO him=WANTYES, send DO. // YES Error: Already enabled. // WANTNO EMPTY If we are queueing requests, himq=OPPOSITE; // otherwise, Error: Cannot initiate new request // in the middle of negotiation. // OPPOSITE Error: Already queued an enable request. // WANTYES EMPTY Error: Already negotiating for enable. // OPPOSITE himq=EMPTY. enableRemote(option, timeout = 1000) { return new Promise((resolve, reject) => { this.remoteOptions.add(option); const o = this.getOption(option); switch (o.him) { case optionState.NO: o.him = optionState.WANTYES; this.writer.writeDo(option); break; case optionState.YES: // error - already enabled resolve(); return; case optionState.WANTNO: switch (o.himq) { case q.EMPTY: o.himq = q.OPPOSITE; break; case q.OPPOSITE: // error - already queued an enable request } break; case optionState.WANTYES: switch (o.himq) { case q.EMPTY: // error - already negotiating enable break; case q.OPPOSITE: o.himq = q.EMPTY; } } function enableListener(optionCode, location) { if (optionCode == option && location == where.REMOTE) { resolve(); this.removeListener('enable', enableListener); } } this.on('enable', enableListener); setTimeout(() => { reject(); this.removeListener('enable', enableListener); }, timeout); }); } // If we decide to ask him to disable: using him and himq // NO Error: Already disabled. // YES him=WANTNO, send DONT. // WANTNO EMPTY Error: Already negotiating for disable. // OPPOSITE himq=EMPTY. // WANTYES EMPTY If we are queueing requests, himq=OPPOSITE; // otherwise, Error: Cannot initiate new request // in the middle of negotiation. // OPPOSITE Error: Already queued a disable request. disableRemote(option, timeout = 1000) { return new Promise((resolve, reject) => { this.remoteOptions.delete(option); const o = this.getOption(option); switch (o.him) { case optionState.NO: // error - already disabled resolve(); return; case optionState.YES: o.him = optionState.WANTNO; this.writer.writeDont(option); break; case optionState.WANTNO: switch (o.himq) { case q.EMPTY: // error - already negotiating disable break; case q.OPPOSITE: o.himq = q.EMPTY; } break; case optionState.WANTYES: switch (o.himq) { case q.EMPTY: o.himq = q.OPPOSITE; break; case q.OPPOSITE: // error - already disabling } } function disableListener(optionCode, location) { if (optionCode == option && location == where.REMOTE) { resolve(); this.removeListener('disable', disableListener); } } this.on('disable', disableListener); setTimeout(() => { reject(); this.removeListener('disable', disableListener); }, timeout); }); } // If we decide to enable: using us and usq // NO us=WANTYES, send WILL. // YES Error: Already enabled. // WANTNO EMPTY If we are queueing requests, usq=OPPOSITE; // otherwise, Error: Cannot initiate new request // in the middle of negotiation. // OPPOSITE Error: Already queued an enable request. // WANTYES EMPTY Error: Already negotiating for enable. // OPPOSITE usq=EMPTY. enableLocal(option, timeout = 1000) { return new Promise((resolve, reject) => { this.localOptions.add(option); let o = this.getOption(option); switch (o.us) { case optionState.NO: o.us = optionState.WANTYES; this.writer.writeWill(option); break; case optionState.YES: // error - already enabled resolve(); return; case optionState.WANTNO: switch (o.usq) { case q.EMPTY: o.usq = q.OPPOSITE; break; case q.OPPOSITE: // error - already queued an enable request break; } break; case optionState.WANTYES: switch (o.usq) { case q.EMPTY: // error - already negotiating enable break; case q.OPPOSITE: o.usq = q.EMPTY; } } function enableListener(optionCode, location) { if (optionCode == option && location == where.LOCAL) { resolve(); this.removeListener('enable', enableListener); } } this.on('enable', enableListener); setTimeout(() => { reject(); this.removeListener('enable', enableListener); }, timeout); }); } // If we decide to disable: using us and usq // NO Error: Already disabled. // YES us=WANTNO, send WONT. // WANTNO EMPTY Error: Already negotiating for disable. // OPPOSITE usq=EMPTY. // WANTYES EMPTY If we are queueing requests, usq=OPPOSITE; // otherwise, Error: Cannot initiate new request // in the middle of negotiation. // OPPOSITE Error: Already queued a disable request. disableLocal(option, timeout = 1000) { return new Promise((resolve, reject) => { this.localOptions.delete(option); let o = this.getOption(option); switch (o.us) { case optionState.NO: // error - already disabled resolve(); return; case optionState.YES: o.us = optionState.WANTNO; this.writer.writeWont(option); break; case optionState.WANTNO: switch (o.usq) { case q.EMPTY: // error - already negotiating disable break; case q.OPPOSITE: o.usq = q.EMPTY; } break; case optionState.WANTYES: switch (o.usq) { case q.EMPTY: o.usq = q.OPPOSITE; // error - already negotiating break; case q.OPPOSITE: // error - already disabling } } function disableListener(optionCode, location) { if (optionCode == option && location == where.LOCAL) { resolve(); this.removeListener('disable', disableListener); } } this.on('disable', disableListener); setTimeout(() => { reject(); this.removeListener('disable', disableListener); }, timeout); }); } getOption(code) { if (!this.options.has(code)) { this.options.set(code, new (options.getOption(code))(this, code)); } return this.options.get(code); } negotiate() { const promises = []; for (const option of this.localOptions) { promises.push(this.enableLocal(option)); } for (const option of this.remoteOptions) { promises.push(this.enableRemote(option)); } return Promise.allSettled(promises).then(() => this.emit('negotiated')); } } module.exports = TelnetSocket;