Create a Node SSH2 Server with ability to treat Remote Forwarding - node.js

After some research on staskoverflow, Google and official Node SSH2 repository, I still can not create a Node SSH2 server which work with remote port forwarding...
Actually I can do what I want with standard SSH daemon on distant server and this command on client side :
ssh -R 8100:localsite.tld:80 sub.distantserver.com
All traffic from sub.distantserver.com:8100 is redirect to localsite.tld:80. (port 22, traditional SSH daemon).
My only goal is to achieve this with a Node SSH2 Server on port 21 :
ssh -R 8100:localsite.tld:80 foo#sub.distantserver.com -p 21
password: bar
When it'll work, I can do some check on the user and start some other process ;)
Basing on official Github issues example I try something like this, just like a POC to catch stream but it fail on forwardIn that does not exists.
var fs = require('fs');
var crypto = require('crypto');
var inspect = require('util').inspect;
var buffersEqual = require('buffer-equal-constant-time');
var ssh2 = require('ssh2');
var utils = ssh2.utils;
new ssh2.Server({
hostKeys: [fs.readFileSync('/etc/ssh/ssh_host_rsa_key')]
}, function(client) {
console.log('Client connected!');
client.on('authentication', function(ctx) {
if (ctx.method === 'password'
&& ctx.username === 'foo'
&& ctx.password === 'bar')
ctx.accept();
else
ctx.reject();
}).on('ready', function() {
console.log('Client authenticated!');
client.on('session', function(accept, reject) {
var session = accept();
session.once('exec', function(accept, reject, info) {
console.log('Client wants to execute: ' + inspect(info.command));
var stream = accept();
stream.stderr.write('Oh no, the dreaded errors!\n');
stream.write('Just kidding about the errors!\n');
stream.exit(0);
stream.end();
});
});
client.on('request', function(accept, reject, name, info) {
console.log(info);
if (name === 'tcpip-forward') {
accept();
setTimeout(function() {
console.log('Sending incoming tcpip forward');
client.forwardIn(info.bindAddr,
info.bindPort,
function(err, stream) {
if (err)
return;
stream.end('hello world\n');
});
}, 1000);
} else {
reject();
}
});
});
}).listen(21, '0.0.0.0', function() {
console.log('Listening on port ' + this.address().port);
});
Does anybody know how to achieve a simple conventional SSH forward server side ?
Thanks !

Found a solution with author's help :
Official Github solution on issue
let fs = require('fs'),
inspect = require('util').inspect,
ssh2 = require('ssh2'),
net = require('net');
new ssh2.Server({
hostKeys: [fs.readFileSync('/etc/ssh/ssh_host_rsa_key')]
}, client => {
console.log('Client connected!');
client
.on('authentication', ctx => {
if (
ctx.method === 'password'
&& ctx.username === 'foo'
&& ctx.password === 'bar'
) {
ctx.accept();
} else {
ctx.reject();
}
})
.on('ready', () => {
console.log('Client authenticated!');
client
.on('session', (accept, reject) => {
let session = accept();
session.on('shell', function(accept, reject) {
let stream = accept();
});
})
.on('request', (accept, reject, name, info) => {
if (name === 'tcpip-forward') {
accept();
net.createServer(function(socket) {
socket.setEncoding('utf8');
client.forwardOut(
info.bindAddr, info.bindPort,
socket.remoteAddress, socket.remotePort,
(err, upstream) => {
if (err) {
socket.end();
return console.error('not working: ' + err);
}
upstream.pipe(socket).pipe(upstream);
});
}).listen(info.bindPort);
} else {
reject();
}
});
});
}).listen(21, '0.0.0.0', function() {
console.log('Listening on port ' + server.address().port);
});

Related

How to establish TCP hole punching with node.js?

I have been trying to establish TCP hole punching with Node.js, and I am not sure if it fails because of my NAT or because the code is erroneous.
The following code intends to:
let 2 clients register on a server the 4-tuple (address on client, port on client, address as seen by server, port as seen by server)
let the server signal when the 2 clients are mutually ready by sending them each other's 4-tuple (tryConnectToPeer)
let each client start a local server (listen) on the local address and port used when communicating with the server (address on client, port on client)
when the local server is running, try to establish a connection (connect) with the local port & address of the other client, as well as an external connection with the port & address of the other client, as the server was seeing them (probably the other client's router address and port then)
Client code - I would imagine the mistake is here:
import { createConnection, createServer } from 'net';
const serverPort = 9999;
const serverHost = '192.168.1.19'
const socket = createConnection(serverPort, serverHost);
socket.setEncoding('utf8');
socket.on('data', (data: string) => {
console.log('data', data);
let parsedData: any = null;
try {
parsedData = JSON.parse(data);
} catch (e) {
if (e instanceof Error) {
console.log(e.message);
} else {
throw e;
}
}
if (parsedData?.command === 'tryConnectToPeer') {
console.log('Will try to connect with peer:', parsedData);
const server = createServer(c => {
console.log('client connected');
c.setEncoding('utf8');
c.on('data', (data: string) => {
console.log('received:', data);
c.write('hi!');
});
});
server.listen(socket.localPort, socket.localAddress, () => {
console.log('server bound to ', socket.localAddress, socket.localPort);
});
server.on('listening', () => {
console.log('Attempting local connection', parsedData.localAddress, parsedData.localPort);
const localSocket = createConnection({ port: parsedData.localPort, host: parsedData.localAddress });
localSocket.on('error', (e) => {
console.error('Failed to connect with peer locally');
console.error(e);
});
localSocket.setEncoding('utf8');
localSocket.on('data', (data: string) => {
console.log(data);
localSocket.write('ho! on local')
})
console.log('Attempting external connection', parsedData.externalAddress, parsedData.externalPort);
const externalSocket = createConnection({ port: parsedData.externalPort, host: parsedData.externalAddress});
externalSocket.on('error', (e) => {
console.error('Failed to connect with peer externally');
console.error(e);
});
externalSocket.setEncoding('utf8');
externalSocket.on('data', (data: string) => {
console.log(data);
externalSocket.write('ho! on external')
})
localSocket.on('connect', () => {
externalSocket.end();
localSocket.write('start from localsocket');
console.log('connected to peer locally!');
})
externalSocket.on('connect', () => {
// localSocket.end();
externalSocket.write('start from externalSocket');
console.log('connected to peer externally!');
})
})
}
});
socket.on('connect', () => {
socket.write(JSON.stringify(
{
command: 'register',
localPort: socket.localPort,
localAddress: socket.localAddress
}
));
});
Server code - a tad long, but probably not the problematic piece:
import { createServer, Socket } from 'net';
type AddressAndPort = {
address: string | undefined,
port: number | undefined
}
class ConnectionDescriptor {
socket: Socket;
addressAndPortOnClient: AddressAndPort;
addressAndPortSeenByServer: AddressAndPort;
constructor({ socket, addressAndPortOnClient, addressAndPortSeenByServer } : {socket: Socket, addressAndPortOnClient: AddressAndPort, addressAndPortSeenByServer: AddressAndPort}) {
this.socket = socket;
this.addressAndPortOnClient = addressAndPortOnClient;
this.addressAndPortSeenByServer = addressAndPortSeenByServer;
}
toString() {
return JSON.stringify({
addressAndPortOnClient: this.addressAndPortOnClient,
addressAndPortSeenByServer: this.addressAndPortSeenByServer
});
}
}
class ConnectionDescriptorSet {
connectionDescriptors: ConnectionDescriptor[]
constructor() {
this.connectionDescriptors = [];
}
get full() {
return this.connectionDescriptors.length === 2;
}
add(descriptor: ConnectionDescriptor) {
if (!descriptor.addressAndPortOnClient.address || !descriptor.addressAndPortOnClient.port || !descriptor.addressAndPortSeenByServer.address || !descriptor.addressAndPortSeenByServer.port) {
throw new Error(`Cannot register incomplete connection descriptor: ${JSON.stringify(descriptor)}`);
}
const index = this.connectionDescriptors.findIndex(c => c.addressAndPortSeenByServer.address === descriptor.addressAndPortSeenByServer.address && c.addressAndPortSeenByServer.port === descriptor.addressAndPortSeenByServer.port);
if (index === -1) {
console.log('Registering new client:');
console.log(descriptor.toString());
if (this.connectionDescriptors.length === 2) {
throw new Error('Only two clients can be registered at a time!');
}
this.connectionDescriptors.push(descriptor)
} else {
console.log('Client already registered:');
console.log(descriptor.toString());
}
}
remove(addressAndPortSeenByServer: AddressAndPort) {
const index = this.connectionDescriptors.findIndex(c => c.addressAndPortSeenByServer.address === addressAndPortSeenByServer.address && c.addressAndPortSeenByServer.port === addressAndPortSeenByServer.port);
if (index === -1) {
console.log('Client with following connectionDescriptors was not found for removal:');
console.log(JSON.stringify(addressAndPortSeenByServer));
} else if (index === 0) {
console.log('Removing client:');
console.log(this.connectionDescriptors[0].toString());
this.connectionDescriptors.shift();
} else if (index === 1) {
console.log('Removing client:');
console.log(this.connectionDescriptors[1].toString());
this.connectionDescriptors.pop();
} else {
throw new Error('No more than 2 clients should have been registered.');
}
}
}
const connectionDescriptorSet = new ConnectionDescriptorSet();
const server = createServer((c) => {
console.log('client connected');
// Optional - useful when logging data
c.setEncoding('utf8');
c.on('end', () => {
connectionDescriptorSet.remove({ address: c.remoteAddress, port: c.remotePort })
console.log('client disconnected');
});
c.on('data', (data: string) => {
console.log('I received:', data);
try {
const parsedData = JSON.parse(data);
if (parsedData.command === 'register') {
connectionDescriptorSet.add(new ConnectionDescriptor({
socket: c,
addressAndPortOnClient: {
address: parsedData.localAddress,
port: parsedData.localPort
},
addressAndPortSeenByServer: {
address: c.remoteAddress,
port: c.remotePort
}
}));
if (connectionDescriptorSet.full) {
console.log('connectionDescriptorSet full, broadcasting tryConnectToPeer command');
connectionDescriptorSet.connectionDescriptors[0].socket.write(
JSON.stringify({
command: 'tryConnectToPeer',
localPort: connectionDescriptorSet.connectionDescriptors[1].addressAndPortOnClient.port,
localAddress: connectionDescriptorSet.connectionDescriptors[1].addressAndPortOnClient.address,
externalAddress: connectionDescriptorSet.connectionDescriptors[1].addressAndPortSeenByServer.address,
externalPort: connectionDescriptorSet.connectionDescriptors[1].addressAndPortSeenByServer.port,
})
);
connectionDescriptorSet.connectionDescriptors[1].socket.write(
JSON.stringify({
command: 'tryConnectToPeer',
localPort: connectionDescriptorSet.connectionDescriptors[0].addressAndPortOnClient.port,
localAddress: connectionDescriptorSet.connectionDescriptors[0].addressAndPortOnClient.address,
externalAddress: connectionDescriptorSet.connectionDescriptors[0].addressAndPortSeenByServer.address,
externalPort: connectionDescriptorSet.connectionDescriptors[0].addressAndPortSeenByServer.port,
})
);
}
}
} catch (e) {
if (e instanceof Error) {
console.error(e);
c.write(e.message);
} else {
throw e;
}
}
})
});
server.on('error', (err) => {
throw err;
});
server.listen(9999, () => {
console.log('server bound');
});
This code works when the two clients are on the same local network, but fails when they are on different networks.
There is in fact no need to run a server on each client once they got each other's credentials. Having a server may increase the chances of EADDRINUSE - unsure.
A simple connect to each other suffices. First one to try will fail, second may succeed.
It's important to specify the origination port of the socket making the call to the other peer (both for the private and public calls). I believe this was the issue in the above code.
Network topology is also tricky. It's best to have the server and each client each behind a different NAT.
Working proof of concept can be found here:
https://github.com/qbalin/tcp_hole_punching_node_js/tree/main

Redis giving error even after redis connected in nodejs

I have redis installed on my system and its running as well.
from node application, im using below code to work with redis.
redis.js
const redis = require("redis");
let client = redis.createClient(6379, '127.0.0.1', {});
let isRedis = false;
client.on("connect", function () {
console.log(`connected to redis`);
isRedis = true;
});
client.on("error", function (err) {
console.log("redis connection error " + err);
throw err;
});
client.on("end", function (err) {
console.log("redis connection end " + err);
});
module.exports = {
SetRedis,
GetKeys,
GetRedis,
GetKeyRedis,
delRedis
};
im using node index.js command to run the application which should also give me "connected to redis" when the connection is established, but i'm not getting this message on my console .
the npm package is also present in package.json
Node Redis 4.x doesn't allow you to pass in discrete arguments for the host and port. The canonical example of connecting to Redis with Node Redis is this:
import { createClient } from 'redis';
(async () => {
const client = createClient();
client.on('error', (err) => console.log('Redis Client Error', err));
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
})();
If you want to connect to somewhere other than localhost on port 6379, I recommend using a URL. Like this:
createClient({ url: 'redis://awesome.redis.server:6380' });
But if you want finer control, you can find all the gritty configuration options in the documentation on GitHub.
I guess you are making mistake while making connection.
It should have been
let client = redis.createClient('127.0.0.1', 6379, {});
rather than
let client = redis.createClient(6379, '127.0.0.1', {});
Working redis.js,
const redis = require("redis");
let isRedis = false;
(async () => {
let client = redis.createClient(6379, '127.0.0.1', {});// create config
client.on("connect", function () {
console.log(`connected to redis`);
isRedis = true;
});
client.on("error", function (err) {
console.log("redis connection error " + err);
throw err;
});
client.on("end", function (err) {
console.log("redis connection end " + err);
});
function GetKeyRedis(key) {
return new Promise(function (resolve, reject) {
console.log("dd----",key,isRedis);
if (isRedis) {
client.get(key).then((data,err) => {
if(err){
reject(err);
}
if(data){
resolve(data)
} else {
resolve(false);
}
});
} else {
resolve(false);
}
});
}
module.exports = {
GetKeyRedis
};
await client.connect();
})();

SSH server using NodeJs which accepts forwarded Port

I have a client SSH sever written in Java using JSCH lib which is forwarding Port from client to ssh server like ThisJSCH client , Now I want a ssh server which will accept the Port forwarded from client in NODEJS!(I have read documentation on SSH2 and SSH modules but there is nothing regarding server which accepts the port), I am able to create a server(using ssh2 module Nodejs) and client also connecting but not accepting the forwarded Port.Below is the Code for server.
var webSocketPort=20;
var fs = require('fs'),
crypto = require('crypto'),
inspect = require('util').inspect;
var buffersEqual = require('buffer-equal-constant-time'),
ssh2 = require('ssh2'),
utils = ssh2.utils;
var pubKey = utils.genPublicKey(utils.parseKey(fs.readFileSync('C:\\Program Files\\OpenSSH\\etc\\ssh_host_rsa_key.pub')));
new ssh2.Server({
hostKeys: [fs.readFileSync('C:\\Program Files\\OpenSSH\\etc\\ssh_host_rsa_key')]
}, function(client) {
console.log('Client connected!',client);
client.on('authentication', function(ctx) {
if (ctx.method === 'password'
|| ctx.username === '418374'
|| ctx.password === 'hiandroid8#3') {
ctx.accept();
console.log("inside userpwd")
}
else if (ctx.method === 'publickey'
&& ctx.key.algo === pubKey.fulltype
&& buffersEqual(ctx.key.data, pubKey.public)) {
console.log("inside publicKey")
if (ctx.signature) {
console.log("inside signature")
var verifier = crypto.createVerify(ctx.sigAlgo);
verifier.update(ctx.blob);
if (verifier.verify(pubKey.publicOrig, ctx.signature))
ctx.accept();
else
ctx.reject();
} else {
console.log("inside nthing")
// if no signature present, that means the client is just checking
// the validity of the given public key
ctx.accept();
}
} else
ctx.reject();
}).on('ready', function() {
console.log('Client authenticated!');
client.on('session', function(accept, reject) {
console.log('Client Sssio!');
var session = accept();
session.once('exec', function(accept, reject, info) {
console.log('Client wants to execute: ' + inspect(info.command));
var stream = accept();
stream.stderr.write('Oh no, the dreaded errors!\n');
stream.write('Just kidding about the errors!\n');
stream.exit(0);
stream.end();
});
});
client.on('request', function(accept, reject, name,info,a) {
console.log('accept',accept)
console.log('reject',reject)
console.log('info',info)
console.log('name',name)
if(name==="tcpip-forward"){
//info.bindAddr='localhost';
}
console.log('infoafgter',info)
var session = accept();
console.log('tcpIp');
})
function reExec(i) {
if (i === 3)
return;
client.forwardOut('0.0.0.0', 3000, 'localhost', 8080, function(err, stream) {
if (err)
console.log(err);
else
stream.end();
reExec(++i);
});
}
reExec(0);
}).on('error',function(e){
console.log("error occcured",e)
}).on('end', function() {
console.log('Client disconnected');
});
}).listen(webSocketPort, '0.0.0.0', function() {
console.log('Listening on port ' + webSocketPort);
});
Answer here :
Create a Node SSH2 Server with ability to treat Remote Forwarding
let fs = require('fs'),
inspect = require('util').inspect,
ssh2 = require('ssh2'),
net = require('net');
new ssh2.Server({
hostKeys: [fs.readFileSync('/etc/ssh/ssh_host_rsa_key')]
}, client => {
console.log('Client connected!');
client
.on('authentication', ctx => {
if (
ctx.method === 'password'
&& ctx.username === 'foo'
&& ctx.password === 'bar'
) {
ctx.accept();
} else {
ctx.reject();
}
})
.on('ready', () => {
console.log('Client authenticated!');
client
.on('session', (accept, reject) => {
let session = accept();
session.on('shell', function(accept, reject) {
let stream = accept();
});
})
.on('request', (accept, reject, name, info) => {
if (name === 'tcpip-forward') {
accept();
net.createServer(function(socket) {
socket.setEncoding('utf8');
client.forwardOut(
info.bindAddr, info.bindPort,
socket.remoteAddress, socket.remotePort,
(err, upstream) => {
if (err) {
socket.end();
return console.error('not working: ' + err);
}
upstream.pipe(socket).pipe(upstream);
});
}).listen(info.bindPort);
} else {
reject();
}
});
});
}).listen(21, '0.0.0.0', function() {
console.log('Listening on port ' + this.address().port);
});

How can i handle multiple imap connections in node.js?

how can i monitor multiple email accounts using imap at the same time using node.js?
I have a program to get notifications for single account using node-imap module and parsed emails using mail-parser.
var Imap = require('imap'),
inspect = require('util').inspect;
var MailParser = require('mailparser').MailParser;
var fs = require('fs');
var imap = new Imap(
{
user: 'any_email_address',
password: 'password',
host: 'imap.host.com',
port: 993,
tls: true,
tlsOptions:
{
rejectUnauthorized: false
}
});
function openInbox(cb)
{
imap.openBox('INBOX', true, cb);
}
var messages = []
imap.once('ready', function ()
{
openInbox(function (err, box)
{
console.log("open")
if (err) throw err;
imap.search(['ALL', []], function (err, results)
{
if (err) throw err;
var f = imap.fetch(results,
{
bodies: ''
});
f.on('message', function (msg, seqno)
{
var mailparser = new MailParser()
msg.on('body', function (stream, info)
{
stream.pipe(mailparser);
mailparser.on("end", function (mail)
{
fs.writeFile('msg-' + seqno + '-body.html', mail.html, function (err)
{
if (err) throw err;
console.log(seqno + 'saved!');
});
})
});
msg.once('end', function ()
{
console.log(seqno + 'Finished');
});
});
f.once('error', function (err)
{
console.log('Fetch error: ' + err);
});
f.once('end', function ()
{
console.log('Done fetching all messages!');
imap.end();
});
});
});
});
imap.once('error', function (err)
{
console.log(err);
});
imap.once('end', function ()
{
console.log('Connection ended');
});
imap.connect();
this is simple
first, make a function that makes your connection and Global variable to put your connection on that and handle theme where ever you want
var Connection = [];
function connectImap(username, password, address, port, tls) {
if (typeof Connection[username] != typeof undefined &&
typeof Connection[username].state == typeof '' &&
Connection[username].state == 'authenticated' &&
Connection[username]._config.user == username &&
Connection[username]._config.password == password) {
console.log('IMAP-CLIENT-USE-AUTHENTICATED-CONNECTION ' + username);
} else {
port = port || 993;
tls = tls || true;
Connection[username] = new Imap({
user : username,
password : password,
host : address,
port : port,
authTimeout : 10000,
connTimeout : 10000,
keepalive : true,
tls : tls
});
console.log('IMAP-CLIENT-CONNECTED : ' + username);
}
}
now you have an array of different connection that means you can find the one you wanted.
i hope it helps
You have to create separate connections to monitor multiple accounts.

How to SSH into a server from a node.js application?

var exec = require('child_process').exec;
exec('ssh my_ip',function(err,stdout,stderr){
console.log(err,stdout,stderr);
});
This just freezes - I guess, because ssh my_ip asks for password, is interactive, etc. How to do it correctly?
There's a node.js module written to perform tasks in SSH using node called ssh2 by mscdex. It can be found here. An example for what you're wanting (from the readme) would be:
var Connection = require('ssh2');
var c = new Connection();
c.on('connect', function() {
console.log('Connection :: connect');
});
c.on('ready', function() {
console.log('Connection :: ready');
c.exec('uptime', function(err, stream) {
if (err) throw err;
stream.on('data', function(data, extended) {
console.log((extended === 'stderr' ? 'STDERR: ' : 'STDOUT: ')
+ data);
});
stream.on('end', function() {
console.log('Stream :: EOF');
});
stream.on('close', function() {
console.log('Stream :: close');
});
stream.on('exit', function(code, signal) {
console.log('Stream :: exit :: code: ' + code + ', signal: ' + signal);
c.end();
});
});
});
c.on('error', function(err) {
console.log('Connection :: error :: ' + err);
});
c.on('end', function() {
console.log('Connection :: end');
});
c.on('close', function(had_error) {
console.log('Connection :: close');
});
c.connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
privateKey: require('fs').readFileSync('/here/is/my/key')
});
The other library on this page has a lower level API.
So I've wrote a lightweight wrapper for it. node-ssh which is also available on GitHub under the MIT license.
Here's an example on how to use it.
var driver, ssh;
driver = require('node-ssh');
ssh = new driver();
ssh.connect({
host: 'localhost',
username: 'steel',
privateKey: '/home/steel/.ssh/id_rsa'
})
.then(function() {
// Source, Target
ssh.putFile('/home/steel/.ssh/id_rsa', '/home/steel/.ssh/id_rsa_bkp').then(function() {
console.log("File Uploaded to the Remote Server");
}, function(error) {
console.log("Error here");
console.log(error);
});
// Command
ssh.exec('hh_client', ['--check'], { cwd: '/var/www/html' }).then(function(result) {
console.log('STDOUT: ' + result.stdout);
console.log('STDERR: ' + result.stderr);
});
});
The best way is to use promisify and async/await. Example:
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
export default async function (req, res) {
const { username, host, password } = req.query;
const { command } = req.body;
let output = {
stdout: '',
stderr: '',
};
try {
output = await exec(`sshpass -p ${password} ssh -o StrictHostKeyChecking=no ${username}#${host} ${command}`);
} catch (error) {
output.stderr = error.stderr;
}
return res.status(200).send({ data: output, message: 'Output from the command' });
}
Check my code:
// redirect to https:
app.use((req, res, next) => {
if(req.connection.encrypted === true) // or (req.protocol === 'https') for express
return next();
console.log('redirect to https => ' + req.url);
res.redirect("https://" + req.headers.host + req.url);
});
pure js/node way to ssh into hosts. Special thanks to
ttfreeman. assumes ssh keys are on host. No need for request object.
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
require('dotenv').config()
//SSH into host and run CMD
const ssh = async (command, host) => {
let output ={};
try {
output['stdin'] = await exec(`ssh -o -v ${host} ${command}`)
} catch (error) {
output['stderr'] = error.stderr
}
return output
}
exports.ssh = ssh

Resources