Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

telnet-client

Package Overview
Dependencies
Maintainers
1
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

telnet-client - npm Package Compare versions

Comparing version 1.4.11 to 2.0.0-alpha.0

lib/index.js.map

128

lib/index.d.ts

@@ -0,69 +1,87 @@

/// <reference types="node" />
import { EventEmitter } from 'events';
import { Stream } from 'stream';
import { Socket, SocketConnectOpts } from 'net';
declare interface ConnectOptions {
host?: string;
port?: number;
localAddress?: string;
socketConnectOptions?: SocketConnectOpts;
timeout?: number;
shellPrompt?: string|RegExp;
loginPrompt?: string|RegExp;
passwordPrompt?: string|RegExp;
failedLoginMatch?: string|RegExp;
initialLFCR?: boolean;
username?: string;
password?: string;
sock?: Socket;
irs?: string;
ors?: string;
import { Callback, Stream } from './utils';
export declare type TelnetState = null | 'end' | 'failedlogin' | 'getprompt' | 'login' | 'ready' | 'response' | 'standby' | 'start';
export declare type EscapeHandler = (escapeSequence: string) => string | null;
export interface ExecOptions {
echoLines?: number;
pageSeparator?: string|RegExp;
negotiationMandatory?: boolean;
execTimeout?: number;
sendTimeout?: number;
sendTmaxBufferLengthimeout?: number;
debug?: boolean;
}
declare interface ExecOptions {
shellPrompt?: string;
loginPrompt?: string;
failedLoginMatch?: string;
timeout?: number;
execTimeout?: number;
irs?: string;
loginPrompt?: string;
maxBufferLength?: number;
newlineReplace?: string;
ors?: string;
echoLines?: number;
shellPrompt?: string;
stripControls?: boolean;
timeout?: number;
}
export interface SendOptions {
maxBufferLength?: number;
}
declare interface SendOptions {
newlineReplace?: string;
ors?: string;
waitfor?: string|RegExp;
shellPrompt?: string | RegExp;
stripControls?: boolean;
timeout?: number;
maxBufferLength?: number;
waitFor?: string | RegExp | false;
/** @deprecated */
waitfor?: string | RegExp | false;
}
export default class telnet_client extends EventEmitter {
export interface ConnectOptions extends SendOptions {
debug?: boolean;
echoLines?: number;
encoding?: BufferEncoding;
escapeHandler?: EscapeHandler;
execTimeout?: number;
extSock?: any;
failedLoginMatch?: string | RegExp;
host?: string;
/** @deprecated */
initialCTRLC?: boolean;
initialCtrlC?: boolean;
initialLFCR?: boolean;
irs?: string;
localAddress?: string;
loginPrompt?: string | RegExp;
maxEndWait?: number;
negotiationMandatory?: boolean;
pageSeparator?: string | RegExp;
password?: string;
passwordPrompt?: string | RegExp;
port?: number;
sendTimeout?: number;
sock?: Socket;
socketConnectOptions?: SocketConnectOpts;
stripShellPrompt?: boolean;
terminalHeight?: number;
terminalWidth?: number;
username?: string;
}
export declare class Telnet extends EventEmitter {
private dataResolver;
private endEmitted;
private inputBuffer;
private loginPromptReceived;
private opts;
private pendingData;
private response;
private socket;
private state;
constructor();
connect(params: ConnectOptions): Promise<void>;
private pushNextData;
nextData(): Promise<string | null>;
connect(opts: any): Promise<void>;
shell(callback?: Callback<Stream>): Promise<Stream>;
exec(cmd: string, opts?: ExecOptions | Callback<string>, callback?: Callback<string>): Promise<string>;
send(data: Buffer | string, opts?: SendOptions | Callback<string>, callback?: Callback<string>): Promise<string>;
write(data: Buffer | string, opts?: SendOptions, callback?: Callback<string>): Promise<string>;
getSocket(): Socket;
end(): Promise<void>;
destroy(): Promise<void>;
end(): Promise<void>;
exec(cmd: string, options?: ExecOptions): Promise<string>;
getSocket(): Socket;
send(cmd: string, options?: SendOptions): Promise<string>;
shell(): Promise<Stream>;
public socket: Socket;
public state: 'ready'|'start'|'standby'|'response'|'getprompt'|'login'|'failedlogin'|null;
parseData(chunk: Buffer, isReady?: boolean[]): Buffer;
private login;
negotiate(chunk: Buffer): Buffer;
private static checkSocket;
private decode;
}

@@ -1,449 +0,495 @@

'use strict'
const events = require('events')
const net = require('net')
const Promise = require('bluebird')
const Duplex = require('stream').Duplex
const utils = require('./utils')
module.exports = class Telnet extends events.EventEmitter {
constructor() {
super()
this.socket = null
this.state = null
}
connect(opts) {
let promise
return promise = new Promise((resolve, reject) => {
const host = (typeof opts.host !== 'undefined' ? opts.host : '127.0.0.1')
const port = (typeof opts.port !== 'undefined' ? opts.port : 23)
const localAddress = (typeof opts.localAddress !== 'undefined' ? opts.localAddress : '')
const socketConnectOptions = (typeof opts.socketConnectOptions !== 'undefined' ? opts.socketConnectOptions : {})
this.timeout = (typeof opts.timeout !== 'undefined' ? opts.timeout : 500)
// Set prompt regex defaults
this.shellPrompt = (typeof opts.shellPrompt !== 'undefined' ? opts.shellPrompt : /(?:\/ )?#\s/)
this.loginPrompt = (typeof opts.loginPrompt !== 'undefined' ? opts.loginPrompt : /login[: ]*$/i)
this.passwordPrompt = (typeof opts.passwordPrompt !== 'undefined' ? opts.passwordPrompt : /Password[: ]*$/i)
this.failedLoginMatch = opts.failedLoginMatch
this.loginPromptReceived = false
this.extSock = (typeof opts.sock !== 'undefined' ? opts.sock : undefined)
this.debug = (typeof opts.debug !== 'undefined' ? opts.debug : false)
this.username = (typeof opts.username !== 'undefined' ? opts.username : 'root')
this.password = (typeof opts.password !== 'undefined' ? opts.password : 'guest')
this.irs = (typeof opts.irs !== 'undefined' ? opts.irs : '\r\n')
this.ors = (typeof opts.ors !== 'undefined' ? opts.ors : '\n')
this.echoLines = (typeof opts.echoLines !== 'undefined' ? opts.echoLines : 1)
this.stripShellPrompt = (typeof opts.stripShellPrompt !== 'undefined' ? opts.stripShellPrompt : true)
this.pageSeparator = (typeof opts.pageSeparator !== 'undefined'
? opts.pageSeparator : '---- More')
this.negotiationMandatory = (typeof opts.negotiationMandatory !== 'undefined'
? opts.negotiationMandatory : true)
this.initialLFCR = (typeof opts.initialLFCR !== 'undefined' ? opts.initialLFCR : false)
this.initialCTRLC = (typeof opts.initialCTRLC !== 'undefined' ? opts.initialCTRLC : false)
this.execTimeout = (typeof opts.execTimeout !== 'undefined' ? opts.execTimeout : 2000)
this.sendTimeout = (typeof opts.sendTimeout !== 'undefined' ? opts.sendTimeout : 2000)
this.maxBufferLength = (typeof opts.maxBufferLength !== 'undefined' ? opts.maxBufferLength : 1048576)
/* if socket is provided and in good state, just reuse it */
if (this.extSock) {
if (!this._checkSocket(this.extSock))
return reject(new Error('socket invalid'))
this.socket = this.extSock
this.state = 'ready'
this.emit('ready')
resolve(this.shellPrompt)
}
else {
this.socket = net.createConnection({
port,
host,
localAddress,
...socketConnectOptions
}, () => {
this.state = 'start'
this.emit('connect')
if (this.initialCTRLC === true) this.socket.write(Buffer.from('03', 'hex'))
if (this.initialLFCR === true) this.socket.write('\r\n')
if (this.negotiationMandatory === false) resolve()
})
}
this.inputBuffer = ''
this.socket.setTimeout(this.timeout, () => {
if (promise.isPending()) {
/* if cannot connect, emit error and destroy */
if (this.listeners('error').length > 0)
this.emit('error', 'Cannot connect')
this.socket.destroy()
return reject(new Error('Cannot connect'))
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Telnet = void 0;
const events_1 = require("events");
const net_1 = require("net");
const utils_1 = require("./utils");
const defaultOptions = {
debug: false,
echoLines: 1,
encoding: 'ascii',
execTimeout: 2000,
host: '127.0.0.1',
initialCtrlC: false,
initialLFCR: false,
irs: '\r\n',
localAddress: '',
loginPrompt: /login[: ]*$/i,
maxBufferLength: 1048576,
maxEndWait: 250,
negotiationMandatory: true,
ors: '\n',
pageSeparator: '---- More',
password: 'guest',
passwordPrompt: /password[: ]*$/i,
port: 23,
sendTimeout: 2000,
shellPrompt: /(?:\/ )?#\s/,
stripControls: false,
stripShellPrompt: true,
timeout: 2000,
username: 'root',
waitFor: false
};
Object.freeze(defaultOptions);
// Convert various options which can be provided as strings into regexes.
function stringToRegex(opts) {
['failedLoginMatch', 'loginPrompt', 'passwordPrompt', 'shellPrompt', 'waitFor'].forEach(key => {
const value = opts[key];
opts[key] = typeof value === 'string' ? new RegExp(value) : value;
});
}
class Telnet extends events_1.EventEmitter {
constructor() {
super();
this.endEmitted = false;
this.inputBuffer = '';
this.loginPromptReceived = false;
this.opts = Object.assign({}, defaultOptions);
this.pendingData = [];
this.response = undefined;
this.state = null;
this.on('data', data => this.pushNextData(data));
this.on('end', () => {
this.pushNextData(null);
this.state = 'end';
});
}
pushNextData(data) {
if (data instanceof Buffer)
data = data.toString(this.opts.encoding);
else if (data != null)
data = data.toString();
const chunks = data ? data.split(/(?<=\r\r\n|\r?\n)/) : [data];
if (this.dataResolver) {
this.dataResolver(chunks[0]);
this.dataResolver = undefined;
}
this.emit('timeout')
return reject(new Error('timeout'))
})
this.socket.on('data', data => {
if (this.state === 'standby')
return this.emit('data', data)
this._parseData(data, (event, parsed) => {
if (promise.isPending() && event === 'ready') {
resolve(parsed)
}
})
})
this.socket.on('error', error => {
if (this.listeners('error').length > 0)
this.emit('error', error)
if (promise.isPending())
reject(error)
})
this.socket.on('end', () => {
this.emit('end')
if (promise.isPending())
reject(new Error('Socket ends'))
})
this.socket.on('close', () => {
this.emit('close')
if (promise.isPending())
reject(new Error('Socket closes'))
})
})
}
shell(callback) {
return new Promise((resolve, reject) => {
resolve(new Stream(this.socket))
}).asCallback(callback)
}
exec(cmd, opts, callback) {
if (opts && opts instanceof Function) callback = opts
return new Promise((resolve, reject) => {
if (opts && opts instanceof Object) {
this.shellPrompt = opts.shellPrompt || this.shellPrompt
this.loginPrompt = opts.loginPrompt || this.loginPrompt
this.failedLoginMatch = opts.failedLoginMatch || this.failedLoginMatch
this.timeout = opts.timeout || this.timeout
this.execTimeout = opts.execTimeout || this.execTimeout
this.irs = opts.irs || this.irs
this.ors = opts.ors || this.ors
this.echoLines = (typeof opts.echoLines !== 'undefined' ? opts.echoLines : this.echoLines)
this.maxBufferLength = opts.maxBufferLength || this.maxBufferLength
}
cmd += this.ors
if (!this.socket.writable)
return reject(new Error('socket not writable'))
this.socket.write(cmd, () => {
let execTimeout = null
this.state = 'response'
this.emit('writedone')
this.once('responseready', responseHandler)
this.once('bufferexceeded', buffExcHandler)
if (this.execTimeout) {
execTimeout = setTimeout(() => {
execTimeout = null
this.removeListener('responseready', responseHandler)
this.removeListener('bufferexceeded', buffExcHandler)
reject(new Error('response not received'))
}, this.execTimeout)
else
this.pendingData.push(chunks[0]);
if (chunks.length > 1)
this.pendingData.push(...chunks.slice(1));
}
nextData() {
return __awaiter(this, void 0, void 0, function* () {
if (this.pendingData.length > 0)
return this.pendingData.splice(0, 1)[0];
else if (this.state === 'end')
return null;
return new Promise(resolve => this.dataResolver = resolve);
});
}
connect(opts) {
return new Promise((resolve, reject) => {
var _a;
let connectionPending = true;
const rejectIt = (reason) => { connectionPending = false; reject(reason); };
const resolveIt = () => { connectionPending = false; resolve(); };
Object.assign(this.opts, opts !== null && opts !== void 0 ? opts : {});
this.opts.initialCtrlC = opts.initialCtrlC && this.opts.initialCTRLC;
this.opts.extSock = (_a = opts === null || opts === void 0 ? void 0 : opts.sock) !== null && _a !== void 0 ? _a : this.opts.extSock;
stringToRegex(this.opts);
// If socket is provided and in good state, just reuse it.
if (this.opts.extSock) {
if (!Telnet.checkSocket(this.opts.extSock))
return rejectIt(new Error('socket invalid'));
this.socket = this.opts.extSock;
this.state = 'ready';
this.emit('ready');
resolveIt();
}
else {
this.socket = (0, net_1.createConnection)(Object.assign({ port: this.opts.port, host: this.opts.host, localAddress: this.opts.localAddress }, this.opts.socketConnectOptions), () => {
this.state = 'start';
this.emit('connect');
if (this.opts.initialCtrlC === true)
this.socket.write('\x03');
if (this.opts.initialLFCR === true)
this.socket.write('\r\n');
if (!this.opts.negotiationMandatory)
resolveIt();
});
}
this.socket.setMaxListeners(Math.max(15, this.socket.getMaxListeners()));
this.socket.setTimeout(this.opts.timeout, () => {
if (connectionPending) {
// If cannot connect, emit error and destroy.
if (this.listeners('error').length > 0)
this.emit('error', 'Cannot connect');
this.socket.destroy();
return reject(new Error('Cannot connect'));
}
this.emit('timeout');
return reject(new Error('timeout'));
});
this.socket.on('connect', () => {
if (!this.opts.shellPrompt) {
this.state = 'standby';
resolveIt();
}
});
this.socket.on('data', data => {
let emitted = false;
if (this.state === 'standby' || !this.opts.negotiationMandatory) {
this.emit('data', this.opts.newlineReplace ? Buffer.from(this.decode(data), this.opts.encoding) : data);
emitted = true;
}
const isReady = [];
if ((data = this.parseData(data, isReady)) && connectionPending && (isReady[0] || !this.opts.shellPrompt)) {
resolveIt();
if (!this.opts.shellPrompt && !emitted)
this.emit('data', data);
}
});
this.socket.on('error', error => {
if (this.listeners('error').length > 0)
this.emit('error', error);
if (connectionPending)
rejectIt(error);
});
this.socket.on('end', () => {
if (!this.endEmitted) {
this.endEmitted = true;
this.emit('end');
}
if (connectionPending) {
if (this.state === 'start')
resolveIt();
else
rejectIt(new Error('Socket ends'));
}
});
this.socket.on('close', () => {
this.emit('close');
if (connectionPending) {
if (this.state === 'start')
resolveIt();
else
rejectIt(new Error('Socket closes'));
}
});
this.once('failedlogin', () => {
if (connectionPending)
rejectIt(new Error('Failed login'));
});
});
}
shell(callback) {
return __awaiter(this, void 0, void 0, function* () {
return (0, utils_1.asCallback)(new Promise(resolve => {
resolve(new utils_1.Stream(this.socket));
}), callback);
});
}
exec(cmd, opts, callback) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof opts === 'function') {
callback = opts;
opts = undefined;
}
return (0, utils_1.asCallback)(new Promise((resolve, reject) => {
Object.assign(this.opts, opts || {});
cmd += this.opts.ors;
if (!this.socket.writable)
return reject(new Error('socket not writable'));
this.socket.write(cmd, () => {
let execTimeout;
this.state = 'response';
this.emit('writedone');
const buffExecHandler = () => {
if (execTimeout)
clearTimeout(execTimeout);
if (!this.inputBuffer)
return reject(new Error('response not received'));
resolve(this.inputBuffer);
// Reset stored response.
this.inputBuffer = '';
// Set state back to 'standby' for possible telnet server push data.
this.state = 'standby';
};
const responseHandler = () => {
if (execTimeout)
clearTimeout(execTimeout);
if (this.response)
resolve(this.response.join(this.opts.newlineReplace || '\n'));
else
reject(new Error('invalid response'));
// Reset stored response.
this.inputBuffer = '';
// Set state back to 'standby' for possible telnet server push data.
this.state = 'standby';
this.removeListener('bufferexceeded', buffExecHandler);
};
this.once('responseready', responseHandler);
this.once('bufferexceeded', buffExecHandler);
if (this.opts.execTimeout) {
execTimeout = setTimeout(() => {
execTimeout = undefined;
this.removeListener('responseready', responseHandler);
this.removeListener('bufferexceeded', buffExecHandler);
reject(new Error('response not received'));
}, this.opts.execTimeout);
}
});
}), callback);
});
}
send(data, opts, callback) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (typeof opts === 'function') {
callback = opts;
opts = undefined;
}
this.opts.ors = ((_a = opts) === null || _a === void 0 ? void 0 : _a.ors) || this.opts.ors;
data += this.opts.ors;
return this.write(data, opts, callback);
});
}
write(data, opts, callback) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof opts === 'function') {
callback = opts;
opts = undefined;
}
return (0, utils_1.asCallback)(new Promise((resolve, reject) => {
var _a, _b;
Object.assign(this.opts, opts || {});
this.opts.waitFor = (_b = (_a = opts === null || opts === void 0 ? void 0 : opts.waitFor) !== null && _a !== void 0 ? _a : opts === null || opts === void 0 ? void 0 : opts.waitfor) !== null && _b !== void 0 ? _b : false;
stringToRegex(this.opts);
if (this.socket.writable) {
let response = '';
let sendTimer;
const sendHandler = (data) => {
response += this.decode(data);
if (sendTimer)
clearTimeout(sendTimer);
if (this.opts.waitFor instanceof RegExp) {
if (this.opts.waitFor.test(response)) {
this.removeListener('data', sendHandler);
resolve(response);
}
}
else
resolve(response);
};
this.socket.on('data', sendHandler);
try {
this.socket.write(data, () => {
if (!this.opts.waitFor || !opts) {
sendTimer = setTimeout(() => {
sendTimer = undefined;
if (response === '') {
this.socket.removeListener('data', sendHandler);
reject(new Error('response not received'));
return;
}
this.socket.removeListener('data', sendHandler);
resolve(response);
}, this.opts.sendTimeout);
}
});
}
catch (e) {
this.socket.removeListener('data', sendHandler);
reject(new Error('send data failed'));
}
}
else {
reject(new Error('socket not writable'));
}
}), callback);
});
}
getSocket() {
return this.socket;
}
end() {
return new Promise(resolve => {
let timer = setTimeout(() => {
timer = undefined;
if (!this.endEmitted) {
this.endEmitted = true;
this.emit('end');
}
resolve();
}, this.opts.maxEndWait);
this.socket.end(() => {
if (timer) {
clearTimeout(timer);
timer = undefined;
resolve();
}
});
});
}
destroy() {
return new Promise(resolve => {
this.socket.destroy();
resolve();
});
}
parseData(chunk, isReady) {
if (chunk[0] === 255 && chunk[1] !== 255)
chunk = this.negotiate(chunk);
if (this.state === 'start')
this.state = 'getprompt';
if (this.state === 'getprompt') {
const stringData = this.decode(chunk);
const promptIndex = (0, utils_1.search)(stringData, this.opts.shellPrompt);
if ((0, utils_1.search)(stringData, this.opts.loginPrompt) >= 0) {
// Make sure we don't end up in an infinite loop.
if (!this.loginPromptReceived) {
this.state = 'login';
this.login('username');
this.loginPromptReceived = true;
}
}
else if ((0, utils_1.search)(stringData, this.opts.passwordPrompt) >= 0) {
this.state = 'login';
this.login('password');
}
else if ((0, utils_1.search)(stringData, this.opts.failedLoginMatch) >= 0) {
this.state = 'failedlogin';
this.emit('failedlogin', stringData);
this.destroy().finally();
}
else if (promptIndex >= 0) {
const shellPrompt = this.opts.shellPrompt instanceof RegExp ?
stringData.substring(promptIndex) : this.opts.shellPrompt;
this.state = 'standby';
this.inputBuffer = '';
this.loginPromptReceived = false;
this.emit('ready', shellPrompt);
isReady === null || isReady === void 0 ? void 0 : isReady.push(true);
}
}
function responseHandler() {
if (execTimeout !== null) {
clearTimeout(execTimeout)
}
if (this.response !== 'undefined') {
resolve(this.response.join('\n'))
}
else reject(new Error('invalid response'))
/* reset stored response */
this.inputBuffer = ''
/* set state back to 'standby' for possible telnet server push data */
this.state = 'standby'
this.removeListener('bufferexceeded', buffExcHandler)
}
function buffExcHandler() {
if (execTimeout !== null) {
clearTimeout(execTimeout)
}
if (!this.inputBuffer) return reject(new Error('response not received'))
resolve(this.inputBuffer)
/* reset stored response */
this.inputBuffer = ''
/* set state back to 'standby' for possible telnet server push data */
this.state = 'standby'
}
})
}).asCallback(callback)
}
send(data, opts, callback) {
if (opts && opts instanceof Function) callback = opts
return new Promise((resolve, reject) => {
if (opts && opts instanceof Object) {
this.ors = opts.ors || this.ors
this.sendTimeout = opts.timeout || this.sendTimeout
this.maxBufferLength = opts.maxBufferLength || this.maxBufferLength
this.waitfor = (opts.waitfor ? (opts.waitfor instanceof RegExp ? opts.waitfor : RegExp(opts.waitfor)) : false)
}
data += this.ors
if (this.socket.writable) {
this.on('data', sendHandler)
let response = ''
try
{
this.socket.write(data, () => {
this.state = 'standby'
if (!this.waitfor || !opts) {
setTimeout(() => {
if (response === '') {
this.removeListener('data', sendHandler)
reject(new Error('response not received'))
return
else if (this.state === 'response') {
if (this.inputBuffer.length >= this.opts.maxBufferLength) {
this.emit('bufferexceeded');
return Buffer.from(this.inputBuffer, this.opts.encoding);
}
const stringData = this.decode(chunk);
this.inputBuffer += stringData;
const promptIndex = (0, utils_1.search)(this.inputBuffer, this.opts.shellPrompt);
if (promptIndex < 0 && stringData.length > 0) {
if ((0, utils_1.search)(stringData, this.opts.pageSeparator) >= 0)
this.socket.write(Buffer.from('20', 'hex'));
return null;
}
const response = this.inputBuffer.split(this.opts.irs);
for (let i = response.length - 1; i >= 0; --i) {
if ((0, utils_1.search)(response[i], this.opts.pageSeparator) >= 0) {
response[i] = response[i].replace(this.opts.pageSeparator, '');
if (response[i].length === 0)
response.splice(i, 1);
}
this.removeListener('data', sendHandler)
resolve(response)
}, this.sendTimeout)
}
})
if (this.opts.echoLines === 1)
response.shift();
else if (this.opts.echoLines > 1)
response.splice(0, this.opts.echoLines);
else if (this.opts.echoLines < 0)
response.splice(0, response.length - 2);
// Remove prompt.
if (this.opts.stripShellPrompt && response.length > 0) {
const idx = response.length - 1;
response[idx] = (0, utils_1.search)(response[idx], this.opts.shellPrompt) >= 0
? response[idx].replace(this.opts.shellPrompt, '')
: '';
}
this.response = response;
chunk = null;
this.emit('responseready');
}
catch(e)
{
this.removeListener('data', sendHandler)
reject(new Error('send data failed'))
}
const self = this
function sendHandler(data) {
response += data.toString()
if (self.waitfor) {
if (!self.waitfor.test(response)) return
self.removeListener('data', sendHandler)
resolve(response)
}
}
} else {
reject(new Error('socket not writable'))
}
}).asCallback(callback)
}
getSocket() {
return this.socket
}
end() {
return new Promise(resolve => {
this.socket.end()
resolve()
})
}
destroy() {
return new Promise(resolve => {
this.socket.destroy()
resolve()
})
}
_parseData(chunk, callback) {
let promptIndex = ''
if (chunk[0] === 255 && chunk[1] !== 255) {
this.inputBuffer = ''
const negReturn = this._negotiate(chunk)
if (negReturn == undefined) return
else chunk = negReturn
return chunk;
}
if (this.state === 'start') {
this.state = 'getprompt'
}
if (this.state === 'getprompt') {
const stringData = chunk.toString()
let promptIndex = utils.search(stringData, this.shellPrompt)
if (utils.search(stringData, this.loginPrompt) !== -1) {
/* make sure we don't end up in an infinite loop */
if (!this.loginPromptReceived) {
this.state = 'login'
this._login('username')
this.loginPromptReceived = true
login(handle) {
if ((handle === 'username' || handle === 'password') && this.socket.writable) {
this.socket.write(this.opts[handle] + this.opts.ors, () => {
this.state = 'getprompt';
});
}
}
else if (utils.search(stringData, this.passwordPrompt) !== -1) {
this.state = 'login'
this._login('password')
}
else if (typeof this.failedLoginMatch !== 'undefined' && utils.search(stringData, this.failedLoginMatch) !== -1) {
this.state = 'failedlogin'
this.emit('failedlogin', stringData)
this.destroy()
}
else if (promptIndex !== -1) {
if (!(this.shellPrompt instanceof RegExp))
this.shellPrompt = stringData.substring(promptIndex)
this.state = 'standby'
this.inputBuffer = ''
this.loginPromptReceived = false
this.emit('ready', this.shellPrompt)
if (callback) callback('ready', this.shellPrompt)
}
else return
}
else if (this.state === 'response') {
if (this.inputBuffer.length >= this.maxBufferLength) {
return this.emit('bufferexceeded')
}
const stringData = chunk.toString()
this.inputBuffer += stringData
promptIndex = utils.search(this.inputBuffer, this.shellPrompt)
if (promptIndex === -1 && stringData.length !== 0) {
if (utils.search(stringData, this.pageSeparator) !== -1) {
this.socket.write(Buffer.from('20', 'hex'))
negotiate(chunk) {
/* info: http://tools.ietf.org/html/rfc1143#section-7
* Refuse to start performing and ack the start of performance
* DO -> WONT WILL -> DO */
const packetLength = chunk.length;
let negData = chunk;
let cmdData = null;
for (let i = 0; i < packetLength; i += 3) {
if (chunk[i] !== 255) {
negData = chunk.slice(0, i);
cmdData = chunk.slice(i);
break;
}
}
return
}
let response = this.inputBuffer.split(this.irs)
for (let i = response.length - 1; i >= 0; --i) {
if (utils.search(response[i], this.pageSeparator) !== -1) {
response[i] = response[i].replace(this.pageSeparator, '')
if (response[i].length === 0)
response.splice(i, 1)
const chunkHex = chunk.toString('hex');
const defaultResponse = negData.toString('hex').replace(/fd/g, 'fc').replace(/fb/g, 'fd');
let negResp = '';
if (this.opts.terminalHeight && this.opts.terminalWidth) {
for (let i = 0; i < chunkHex.length; i += 6) {
let w, h;
switch (chunkHex.substr(i + 2, 4)) {
case 'fd18':
negResp += 'fffb18';
break;
case 'fd1f':
w = this.opts.terminalWidth.toString(16).padStart(4, '0');
h = this.opts.terminalHeight.toString(16).padStart(4, '0');
negResp += `fffb1ffffa1f${w}${h}fff0`;
break;
default:
negResp += defaultResponse.substr(i, 6);
}
}
}
}
if (this.echoLines === 1) response.shift()
else if (this.echoLines > 1) response.splice(0, this.echoLines)
else if (this.echoLines === -1) response.splice(0, response.length - 2)
/* remove prompt */
if (this.stripShellPrompt) {
const idx = response.length - 1;
response[idx] = utils.search(response[idx], this.shellPrompt) > -1
? response[idx].replace(this.shellPrompt, '')
: '';
}
this.response = response;
this.emit('responseready')
else
negResp = defaultResponse;
if (this.socket.writable)
this.socket.write(Buffer.from(negResp, 'hex'));
return cmdData;
}
}
_login(handle) {
if ((handle === 'username' || handle === 'password') && this.socket.writable) {
this.socket.write(this[handle] + this.ors, () => {
this.state = 'getprompt'
})
static checkSocket(sock) {
return sock !== null &&
typeof sock === 'object' &&
typeof sock.pipe === 'function' &&
sock.writable !== false &&
typeof sock._write === 'function' &&
typeof sock._writableState === 'object' &&
sock.readable !== false &&
typeof sock._read === 'function' &&
typeof sock._readableState === 'object';
}
}
_negotiate(chunk) {
/* info: http://tools.ietf.org/html/rfc1143#section-7
* refuse to start performing and ack the start of performance
* DO -> WONT WILL -> DO */
const packetLength = chunk.length
let negData = chunk
let cmdData = null
let negResp = null
for (let i = 0; i < packetLength; i+=3) {
if (chunk[i] != 255) {
negData = chunk.slice(0, i)
cmdData = chunk.slice(i)
break
}
decode(chunk) {
if (chunk instanceof Buffer)
chunk = chunk.toString(this.opts.encoding);
if (this.opts.escapeHandler) {
chunk === null || chunk === void 0 ? void 0 : chunk.replace(/\x1B((\[.*?[a-z])|.)/i, seq => {
const response = this.opts.escapeHandler(seq);
if (response)
this.socket.write(response);
return seq;
});
}
if (this.opts.stripControls) {
chunk = chunk === null || chunk === void 0 ? void 0 : chunk.replace(/\x1B((\[.*?[a-z])|.)/i, ''); // Escape sequences
chunk = chunk === null || chunk === void 0 ? void 0 : chunk.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ''); // All controls except tab, lf, and cr.
}
if (this.opts.newlineReplace)
chunk = chunk === null || chunk === void 0 ? void 0 : chunk.replace(/\r\r\n|\r\n?/g, this.opts.newlineReplace);
return chunk;
}
negResp = negData.toString('hex').replace(/fd/g, 'fc').replace(/fb/g, 'fd')
if (this.socket.writable) this.socket.write(Buffer.from(negResp, 'hex'))
if (cmdData != undefined) return cmdData
else return
}
_checkSocket(sock) {
return this.extSock !== null &&
typeof this.extSock === 'object' &&
typeof this.extSock.pipe === 'function' &&
this.extSock.writable !== false &&
typeof this.extSock._write === 'function' &&
typeof this.extSock._writableState === 'object' &&
this.extSock.readable !== false &&
typeof this.extSock._read === 'function' &&
typeof this.extSock._readableState === 'object'
}
}
class Stream extends Duplex {
constructor(source, options) {
super(options)
this.source = source
this.source.on('data', (data) => this.push(data))
}
_write(data, encoding, cb) {
if (!this.source.writable) {
cb(new Error('socket not writable'))
}
this.source.write(data, encoding, cb)
}
_read() {}
}
exports.Telnet = Telnet;
//# sourceMappingURL=index.js.map

@@ -1,4 +0,34 @@

module.exports.search = function(str, pattern) {
if (pattern instanceof RegExp) return str.search(pattern)
else return str.indexOf(pattern)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Stream = exports.search = exports.asCallback = void 0;
const stream_1 = require("stream");
function asCallback(promise, callback) {
if (typeof callback === 'function')
promise.then(result => callback(null, result)).catch(err => callback(err));
return promise;
}
exports.asCallback = asCallback;
function search(str, pattern) {
if (!str || !pattern)
return -1;
else if (pattern instanceof RegExp)
return str.search(pattern);
else
return str.indexOf(pattern);
}
exports.search = search;
class Stream extends stream_1.Duplex {
constructor(source, options) {
super(options);
this.source = source;
this.source.on('data', data => this.push(data));
}
_write(data, encoding, callback) {
if (!this.source.writable)
callback(new Error('socket not writable'));
this.source.write(data, encoding, callback);
}
_read() { }
}
exports.Stream = Stream;
//# sourceMappingURL=utils.js.map

@@ -8,3 +8,3 @@ {

},
"version": "1.4.11",
"version": "2.0.0-alpha.0",
"main": "./lib/index.js",

@@ -14,4 +14,9 @@ "types": "./lib/index.d.ts",

"license": "MIT",
"scripts": {
"prepack": "tsc",
"test": "nyc --reporter=html mocha --timeout 3000 --require ts-node/register test/**/*.ts",
"coveralls": "tsc && (nyc report --reporter=text-lcov | coveralls)",
"lint": "eslint 'src/**/*.ts' 'test/**/*.ts'"
},
"dependencies": {
"bluebird": "^3.5.4",
"net": "^1.0.2",

@@ -21,14 +26,26 @@ "stream": "^0.0.2"

"devDependencies": {
"coveralls": "^3.0.3",
"@types/chai": "^4.3.0",
"@types/mocha": "^9.0.0",
"@types/node": "^17.0.5",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"chai": "^4.3.4",
"coveralls": "^3.1.1",
"eslint": "^8.5.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-chai-friendly": "^0.7.2",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "^5.0.0",
"extend": "^3.0.2",
"nodeunit": "^0.11.3",
"telnet": "0.0.1"
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"telnet": "0.0.1",
"ts-node": "^10.4.0",
"typescript": "^4.5.4"
},
"scripts": {
"test": "nodeunit test",
"coveralls": "jscoverage lib && NODETELNETCLIENT_COV=1 nodeunit --reporter=lcov test | coveralls"
},
"repository": {
"type": "git",
"url": "http://github.com/mkozjak/node-telnet-client.git"
"url": "github:mkozjak/node-telnet-client.git"
},

@@ -80,2 +97,6 @@ "contributors": [

"email": "ivan@sieder.xyz"
},
{
"name": "Kerry Shetline",
"email": "kerry@shetline.com"
}

@@ -82,0 +103,0 @@ ],

[![GitHub license](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://github.com/mkozjak/node-telnet-client/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/mkozjak/node-telnet-client.svg?branch=master)](https://travis-ci.org/mkozjak/node-telnet-client)
[![npm](https://img.shields.io/npm/dm/telnet-client.svg?maxAge=2592000)](https://www.npmjs.com/package/telnet-client)
[![Donate Bitcoin/Altcoins](https://img.shields.io/badge/donate-coins-blue.svg)](https://paypal.me/kozjak)
[![Donate Bitcoin/Altcoins](https://img.shields.io/badge/donate-coins-blue.svg)](https://paypal.me/kozjak)
[![npm version](https://img.shields.io/npm/v/telnet-client.svg?style=flat)](https://www.npmjs.com/package/telnet-client)

@@ -15,3 +15,3 @@

```
```bash
npm install telnet-client

@@ -22,14 +22,17 @@ npm install -g telnet-client

## Quick start
### Async/Await (Node.js >= 7.6.0)
_Note: As of version 2.0.0 of this API, native ES6 promises are returned, not Bluebird promises._
```js
'use strict'
const Telnet = require('telnet-client')
const { Telnet } = require('telnet-client')
async function run() {
let connection = new Telnet()
(async function () {
const connection = new Telnet()
// these parameters are just examples and most probably won't work for your use-case.
let params = {
const params = {
host: '127.0.0.1',

@@ -43,11 +46,9 @@ port: 23,

await connection.connect(params)
} catch(error) {
} catch (error) {
// handle the throw (timeout)
}
let res = await connection.exec('uptime')
const res = await connection.exec('uptime')
console.log('async result:', res)
}
run()
})()
```

@@ -58,16 +59,15 @@

```js
var Telnet = require('telnet-client')
var connection = new Telnet()
const { Telnet } = require('telnet-client')
const connection = new Telnet()
// these parameters are just examples and most probably won't work for your use-case.
var params = {
const params = {
host: '127.0.0.1',
port: 23,
shellPrompt: '/ # ', // or negotiationMandatory: false
timeout: 1500,
// removeEcho: 4
timeout: 1500
}
connection.on('ready', function(prompt) {
connection.exec(cmd, function(err, response) {
connection.on('ready', prompt => {
connection.exec(cmd, (err, response) => {
console.log(response)

@@ -77,3 +77,3 @@ })

connection.on('timeout', function() {
connection.on('timeout', () => {
console.log('socket timeout!')

@@ -83,3 +83,3 @@ connection.end()

connection.on('close', function() {
connection.on('close', () => {
console.log('connection closed')

@@ -93,27 +93,28 @@ })

_Note: As of version 2.0.0 of this API, native ES6 promises are returned, not Bluebird promises._
```js
var Telnet = require('telnet-client')
var connection = new Telnet()
const { Telnet } = require('telnet-client')
const connection = new Telnet()
// these parameters are just examples and most probably won't work for your use-case.
var params = {
const params = {
host: '127.0.0.1',
port: 23,
shellPrompt: '/ # ', // or negotiationMandatory: false
timeout: 1500,
// removeEcho: 4
timeout: 1500
}
connection.connect(params)
.then(function(prompt) {
connection.exec(cmd)
.then(function(res) {
console.log('promises result:', res)
.then(prompt => {
connection.exec(cmd)
.then(res => {
console.log('promises result:', res)
})
}, error => {
console.log('promises reject:', error)
})
}, function(error) {
console.log('promises reject:', error)
})
.catch(function(error) {
// handle the throw (timeout)
})
.catch(error => {
// handle the throw (timeout)
})
```

@@ -124,9 +125,9 @@

```js
var co = require('co')
var bluebird = require('bluebird')
var Telnet = require('telnet-client')
var connection = new Telnet()
const co = require('co')
const toBluebird = require("to-bluebird")
const { Telnet } = require('telnet-client')
const connection = new Telnet()
// these parameters are just examples and most probably won't work for your use-case.
var params = {
const params = {
host: '127.0.0.1',

@@ -147,10 +148,10 @@ port: 23,

let res = yield connection.exec(cmd)
console.log('coroutine result:', res)
const res = yield connection.exec(cmd)
console.log('coroutine result:', const)
})
// using 'bluebird'
// using Promise
bluebird.coroutine(function*() {
try {
yield connection.connect(params)
yield toBluebird(connection.connect(params))
} catch (error) {

@@ -160,3 +161,3 @@ // handle the throw (timeout)

let res = yield connection.exec(cmd)
let res = yield toBluebird(connection.exec(cmd))
console.log('coroutine result:', res)

@@ -166,3 +167,3 @@ })()

### Async/Await (using babeljs)
### Async/Await

@@ -172,13 +173,8 @@ ```js

const Promise = require('bluebird')
const telnet = require('telnet-client')
const { Telnet } = require('telnet-client')
require('babel-runtime/core-js/promise').default = Promise
Promise.onPossiblyUnhandledRejection(function(error) {
process.on('unhandledRejection', error => {
throw error
})
// also requires additional babeljs setup
async function run() {

@@ -219,4 +215,4 @@ let connection = new Telnet()

```js
var Telnet = require('telnet-client')
var connection = new Telnet()
const { Telnet } = require('telnet-client')
const connection = new Telnet()
```

@@ -235,7 +231,7 @@

of inactivity on the socket.
* `shellPrompt`: Shell prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex '/(?:\/ )?#\s/'. Use `negotiationMandatory: false` if you don't need this.
* `loginPrompt`: Username/login prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex '/login[: ]*$/i'.
* `passwordPrompt`: Password/login prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex '/Password: /i'.
* `shellPrompt`: Shell prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex `/(?:\/ )?#\s/`. Use `negotiationMandatory: false` if you don't need this.<br><br>Set `shellPrompt` to `null` if you wish to use the `send(…)` or `write(…)` methods, ignoring the returned values, and instead relying on `nextData()` or `on('data'…` for feedback.
* `loginPrompt`: Username/login prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex `/login[: ]*$/i`.
* `passwordPrompt`: Password/login prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex `/Password: /i`.
* `failedLoginMatch`: String or regex to match if your host provides login failure messages. Defaults to undefined.
* `initialCTRLC`: Flag used to determine if an initial 0x03 (CTRL+C) should be sent when connected to server.
* `initialCtrlC`: Flag used to determine if an initial 0x03 (CTRL+C) should be sent when connected to server.
* `initialLFCR`: Flag used to determine if an initial '\r\n' (CR+LF) should be sent when connected to server.

@@ -247,3 +243,3 @@ * `username`: Username used to login. Defaults to 'root'.

* `ors`: Output record separator. A separator used to execute commands (break lines on input). Defaults to '\n'.
* `echoLines`: The number of lines used to cut off the response. Defaults to 1.
* `echoLines`: The number of lines used to cut off the response. Defaults to 1. With a value of 0, no lines are cut off.
* `stripShellPrompt`: Whether shell prompt should be excluded from the results. Defaults to true.

@@ -256,3 +252,8 @@ * `pageSeparator`: The pattern used (and removed from final output) for breaking the number of lines on output. Defaults to '---- More'.

* `maxBufferLength`: Maximum buffer length in bytes which can be filled with response data. Defaults to 1M.
* `debug`: Enable/disable debug logs on console. Defaults to false.
* `terminalWidth`, `terminalHeight`: When set to non-zero values, `telnet-client` will respond to the host command `IAC DO 0x18` (Terminal Type) with `IAC WILL 0x18`, and it will respond to `IAC DO 0x1F` with the given terminal width and height.
* `newlineReplace`: If provided, incoming line breaks will be normalized to the provided character/string of characters.
* `escapeHandler`: An optional function that receives escape sequences (either `'0x1B'` and the next non-`[` character, or `'0x1B['` followed by every subsequent character up to and including the first ASCII letter) from the host. The function can either return `null`, which means to take no action, or a string value to be sent to the host as a response.
* `stripControls`: If set to `true`, escape sequences and control characters (except for `\t`, `\n`, and `\r`) will be stripped from incoming data. `escapeHandler` is not affected.
* `maxEndWait`: The maximum time, in milliseconds, to wait for a callback from `socket.end(…)` after calling `end()`. Defaults to 250 milliseconds.
* `encoding`: _(Experimental)_ The telnet protocol is designed mainly for 7-bit ASCII characters, and a default encoding used is `'ascii'`. You can attempt to use other encodings, however, such as `'utf8'` and `'latin1'`. Since the character values 0xF0-0xFF are used for telnet commands, not all characters for many encodings can be properly conveyed. `'utf8'` can work, however, for the roughly 64K characters in Unicode Basic Multilingual Plane (BMP).

@@ -265,13 +266,16 @@ Resolves once the connection is ready (analogous to the ```ready``` event).

Sends data on the socket (should be a compatible remote host's command if sane information is wanted).
The optional callback parameter will be executed with an error and response when the command is finally written out and the response data has been received.
The optional callback parameter will be executed with an error and response when the command is finally written out and the response data has been received.
If there was no error when executing the command, 'error' as the first argument to the callback will be undefined.
Command result will be passed as the second argument to the callback.
__*** important notice/API change from 0.3.0 ***__
The callback argument is now called with a signature of (error, [response])
Command result will be passed as the second argument to the callback.
__*** Important notice/API change from 0.3.0 ***__
The callback argument is now called with a signature of (error, [response])
Options:
* `shellPrompt`: Shell prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex '/(?:\/ )?#\s/'.
* `loginPrompt`: Username/login prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex '/login[: ]*$/i'.
* `shellPrompt`: Shell prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex `/(?:\/ )?#\s/`.
* `loginPrompt`: Username/login prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex `/login[: ]*$/i`.
* `failedLoginMatch`: String or regex to match if your host provides login failure messages. Defaults to undefined.

@@ -282,5 +286,5 @@ * `timeout`: Sets the socket to timeout after the specified number of milliseconds

* `maxBufferLength`: Maximum buffer length in bytes which can be filled with response data. Defaults to 1M.
* `irs`: Input record separator. A separator used to distinguish between lines of the response. Defaults to '\r\n'.
* `ors`: Output record separator. A separator used to execute commands (break lines on input). Defaults to '\n'.
* `echoLines`: The number of lines used to cut off the response. Defaults to 1.
* `irs`: Input record separator. A separator used to distinguish between lines of the response. Defaults to `'\r\n'`.
* `ors`: Output record separator. A separator used to execute commands (break lines on input). Defaults to `'\n'`.
* `echoLines`: The number of lines used to cut off the response. Defaults to 1. With a value of 0, no lines are cut off.

@@ -293,7 +297,16 @@ ### connection.send(data, [options], [callback]) -> Promise

* `shellPrompt`: Shell prompt that the host is using. Can be a string or an instance of RegExp. Defaults to regex `/(?:\/ )?#\s/`. Use `negotiationMandatory: false` if you don't need this.<br><br>Set `shellPrompt` to `null` if you wish to use the `send(…)` or `write(…)` methods, ignoring the returned values, and instead relying on `nextData()` or `on('data'…` for feedback.
* `ors`: Output record separator. A separator used to execute commands (break lines on input). Defaults to '\n'.
* `waitfor`: Wait for the given string or RegExp before returning a response. If not defined, the timeout value will be used.
* `waitFor`: Wait for the given string or RegExp before returning a response. If not defined, the timeout value will be used.
* `timeout`: A timeout used to wait for a server reply when the 'send' method is used. Defaults to 2000 (ms) or to sendTimeout ('connect' method) if set.
* `maxBufferLength`: Maximum buffer length in bytes which can be filled with response data. Defaults to 1M.
### connection.write(data, [options], [callback]) -> Promise
Same as `send(…)`, but `data` is sent without appending an output record separator.
### connection.nextData() -> Promise
Waits for and returns the next available data from the host, as a string value, either one line at a time, or the last-sent incomplete line. When the telnet session has ended, `nextData()` always returns a Promise that resolves to `null`.
### connection.shell(callback) -> Promise

@@ -326,3 +339,3 @@

Emitted when the write of given data is sent to the socket.
Emitted when a write operation for given data is sent to the socket.

@@ -355,5 +368,7 @@ ### Event: 'data'

## Professional support
I offer professional support for node-telnet-client and beyond. I have many years of expertise on building robust, scalable Node.js applications and can help you overcome issues and challenges preventing you to ship your great products. I also excel in software architecture and implementation, being able to provide you with development, planning, consulting, training and customization services. Feel free to [contact me](mailto:mario.kozjak@elpheria.com?subject=Professional%20Support) so we can discuss how to help you finish your products!
## Sponsors
Become a sponsor and get your logo on project's README on GitHub with a link to your site. Feel free to [contact me](mailto:mario.kozjak@elpheria.com?subject=Sponsors) for the arrangement!

@@ -360,0 +375,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc