ssh_tunnel_proxy 1.2.8 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/lib/index.js +51 -48
- package/package.json +4 -2
- package/test/test_remote_exec_streams.js +140 -0
package/README.md
CHANGED
|
@@ -62,6 +62,32 @@ Start a terminal session on the remote host:
|
|
|
62
62
|
node main.js -c -S
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
Example use of api to exec remote commands using async await and processing result through streams. Complete example is in test/test_remote_exec_streams.js:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
async function test_exec_stream(opts) {
|
|
69
|
+
sshTunnelProxy.debug_en = true;
|
|
70
|
+
await sshTunnelProxy.connectSSH(opts);
|
|
71
|
+
|
|
72
|
+
// exec remote command and save result to string
|
|
73
|
+
const uptime_result = await sshTunnelProxy.execCmd('uptime');
|
|
74
|
+
console.log('uptime:' + uptime_result.toString());
|
|
75
|
+
|
|
76
|
+
// exec remote command and pipe data through tunnel until end of data
|
|
77
|
+
const tunnel = new PassThrough();
|
|
78
|
+
tunnel.pipe(split())
|
|
79
|
+
.pipe(to_ls_JSON)
|
|
80
|
+
.pipe(to_JSON_string)
|
|
81
|
+
.pipe(process.stdout);
|
|
82
|
+
|
|
83
|
+
await sshTunnelProxy.execCmd('ls -all', tunnel);
|
|
84
|
+
|
|
85
|
+
// stream processing complete
|
|
86
|
+
console.log('done');
|
|
87
|
+
process.exit();
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
65
91
|
The following code is an example of use of the api with electronjs.
|
|
66
92
|
|
|
67
93
|
main.js:
|
package/lib/index.js
CHANGED
|
@@ -102,63 +102,67 @@ class SSHTunnelProxy extends Client {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// function to setup proxy forwards for ssh tunnel
|
|
105
|
-
|
|
105
|
+
setupProxyPorts(server_name, proxy_ports) {
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
if (this.listener[server_name]) this.listener[server_name].isConnected = true;
|
|
109
|
-
const _this = this;
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
110
108
|
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
var listeners = 0;
|
|
110
|
+
if (this.listener[server_name]) this.listener[server_name].isConnected = true;
|
|
111
|
+
const _this = this;
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
// iterate through a list of local forward ports and create a local proxy port
|
|
114
|
+
proxy_ports.forEach(proxy_port => {
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
const server = this.listener[server_name][proxy_port];
|
|
118
|
-
if (server && server.listening) {
|
|
119
|
-
this.debug_en && this.debug('SSH Server :: closing forward 2:', proxy_port)
|
|
120
|
-
server.close();
|
|
121
|
-
}
|
|
122
|
-
this.listener[server_name][proxy_port] = null;
|
|
123
|
-
this.listener[server_name][proxy_port] = net.createServer({ keepAlive: true, allowHalfOpen: false }, socket => {
|
|
116
|
+
const [local_port, remote_hostname, remote_port] = proxy_port.split(':');
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
// create local socket server
|
|
119
|
+
const server = this.listener[server_name][proxy_port];
|
|
120
|
+
if (server && server.listening) {
|
|
121
|
+
this.debug_en && this.debug('SSH Server :: closing forward 2:', proxy_port)
|
|
122
|
+
server.close();
|
|
129
123
|
}
|
|
124
|
+
this.listener[server_name][proxy_port] = null;
|
|
125
|
+
this.listener[server_name][proxy_port] = net.createServer({ keepAlive: true, allowHalfOpen: false }, socket => {
|
|
130
126
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
if (this.debug_en) {
|
|
128
|
+
var debug_msg = 'SSH Server :: connection on ' + local_port + ' ' + socket.remotePort;
|
|
129
|
+
_this.emit('debug', debug_msg);
|
|
130
|
+
_this.debug(debug_msg);
|
|
131
|
+
}
|
|
134
132
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
_this.emit('debug', status_msg);
|
|
139
|
-
_this.listener[server_name][proxy_port].listen(local_port, () => {
|
|
133
|
+
// create a proxy forward between local and remote ports
|
|
134
|
+
_this.setup_ssh_forward(socket, remote_hostname, remote_port);
|
|
135
|
+
});
|
|
140
136
|
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
// start listening on port
|
|
138
|
+
try {
|
|
139
|
+
var status_msg = 'SSH Server :: before listen on ' + local_port;
|
|
143
140
|
_this.emit('debug', status_msg);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
141
|
+
_this.listener[server_name][proxy_port].listen(local_port, () => {
|
|
142
|
+
|
|
143
|
+
// emit server listening on port message
|
|
144
|
+
var status_msg = 'SSH Server :: bound on ' + local_port;
|
|
145
|
+
_this.emit('debug', status_msg);
|
|
146
|
+
|
|
147
|
+
// if all listeners have been successfully established, resolve setup connection
|
|
148
|
+
if (listeners++ >= proxy_ports.length - 1) {
|
|
149
|
+
_this.emit('ssh_tunnel_ready', {});
|
|
150
|
+
if (_this._tunnelReadyTimeout) {
|
|
151
|
+
clearTimeout(this._tunnelReadyTimeout);
|
|
152
|
+
_this._tunnelReadyTimeout = undefined
|
|
153
|
+
}
|
|
154
|
+
resolve();
|
|
151
155
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
156
|
+
});
|
|
157
|
+
} catch (err) {
|
|
158
|
+
_this.debug_en && _this.debug('listen err:', err);
|
|
159
|
+
reject(err);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
158
162
|
});
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
|
|
165
|
+
execCmd(cmd, dataStream, errStream) {
|
|
162
166
|
|
|
163
167
|
return new Promise((resolve, reject) => {
|
|
164
168
|
var bufs = [];
|
|
@@ -255,13 +259,13 @@ class SSHTunnelProxy extends Client {
|
|
|
255
259
|
if (opts.shell) {
|
|
256
260
|
_this.shell(_this.remote_shell);
|
|
257
261
|
|
|
258
|
-
|
|
262
|
+
// if exec requested, exec series of cmds
|
|
259
263
|
} else {
|
|
260
264
|
if (opts.exec && opts.exec.length > 0) {
|
|
261
265
|
for (var i = 0; i < opts.exec.length; i++) {
|
|
262
266
|
var cmd = opts.exec[i];
|
|
263
267
|
console.log(opts.username + '@' + opts.hostname + ':' + cmd);
|
|
264
|
-
var result = await _this.
|
|
268
|
+
var result = await _this.execCmd(cmd);
|
|
265
269
|
console.log(result.toString());
|
|
266
270
|
}
|
|
267
271
|
process.exit();
|
|
@@ -270,10 +274,9 @@ class SSHTunnelProxy extends Client {
|
|
|
270
274
|
|
|
271
275
|
// if local forward requested, setup local forwarding ports to remote host
|
|
272
276
|
if (opts.proxy_ports) {
|
|
273
|
-
_this.
|
|
274
|
-
} else {
|
|
275
|
-
resolve();
|
|
277
|
+
await _this.setupProxyPorts(opts.server_name, opts.proxy_ports);
|
|
276
278
|
}
|
|
279
|
+
resolve();
|
|
277
280
|
});
|
|
278
281
|
|
|
279
282
|
_this.on('end', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ssh_tunnel_proxy",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "obtain ngrok host port from api and establish a ssh connection",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"net": "^1.0.2",
|
|
19
19
|
"ngrok": "^4.3.3",
|
|
20
20
|
"socksv5": "^0.0.6",
|
|
21
|
-
"
|
|
21
|
+
"split": "^1.0.1",
|
|
22
|
+
"sshpk": "^1.17.0",
|
|
23
|
+
"through": "^2.3.8"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
26
|
"eslint": "^8.33.0",
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
test remote exec streams
|
|
4
|
+
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
//const assert = require('assert');
|
|
8
|
+
//const { describe, it } = require('mocha');
|
|
9
|
+
const process = require('process');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const { PassThrough } = require("stream");
|
|
12
|
+
const through = require('through');
|
|
13
|
+
const split = require('split');
|
|
14
|
+
|
|
15
|
+
const { SSHTunnelProxy } = require('../lib/index.js');
|
|
16
|
+
|
|
17
|
+
// get config file containing api keys
|
|
18
|
+
const homedir = require('os').homedir();
|
|
19
|
+
var config = null;
|
|
20
|
+
try {
|
|
21
|
+
config = JSON.parse(fs.readFileSync(homedir + '/.config/ssh_tunnel_proxy/config.json'), 'utf8');
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.log('Error reading config');
|
|
24
|
+
process.exit();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!config && !config[1]) {
|
|
28
|
+
console.log('Specified config not found in configs');
|
|
29
|
+
process.exit();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const opts = config[1];
|
|
33
|
+
|
|
34
|
+
const sshTunnelProxy = new SSHTunnelProxy();
|
|
35
|
+
|
|
36
|
+
// parse shell command result into array of strings
|
|
37
|
+
function parse_cmd(str) {
|
|
38
|
+
|
|
39
|
+
let result = [];
|
|
40
|
+
let log_matches = false;
|
|
41
|
+
|
|
42
|
+
let regex = /(([\w-/_~]+)|("(.*?)")|('(.*?)'))/g;
|
|
43
|
+
let groups = [2, 4, 6];
|
|
44
|
+
let match;
|
|
45
|
+
|
|
46
|
+
while ((match = regex.exec(str)) !== null) {
|
|
47
|
+
// This is necessary to avoid infinite loops
|
|
48
|
+
// with zero-width matches
|
|
49
|
+
if (match.index === regex.lastIndex) {
|
|
50
|
+
regex.lastIndex++;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// For this to work the regex groups need to
|
|
54
|
+
// be mutually exclusive
|
|
55
|
+
groups.forEach(function (group) {
|
|
56
|
+
if (match[group]) {
|
|
57
|
+
result.push(match[group]);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// show matches for debugging
|
|
62
|
+
log_matches && match.forEach(function (m, group) {
|
|
63
|
+
if (m) {
|
|
64
|
+
console.log(`Match '${m}' found in group: ${group}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// generate object from arrays of names/values
|
|
72
|
+
function toObj(names, values) {
|
|
73
|
+
const obj = {};
|
|
74
|
+
for (var i = 0; i < names.length; i++) {
|
|
75
|
+
obj[names[i]] = values[i];
|
|
76
|
+
}
|
|
77
|
+
return obj;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// stream that receives single line of ls long data and converts to json object
|
|
81
|
+
const to_ls_JSON = new through(function (data) {
|
|
82
|
+
const file = processLSLong(data);
|
|
83
|
+
if (file.name) this.queue(file);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// stream that receives objects and converts to JSON strings
|
|
87
|
+
const to_JSON_string = new through(function (data) {
|
|
88
|
+
this.queue(JSON.stringify(data) + '\n');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// parse ls long line output into json object
|
|
92
|
+
function processLSLong(line) {
|
|
93
|
+
const format10 = ['pm', 'links', 'user', 'group', 'size', 'M', 'D', 'H', 'MM', 'name'];
|
|
94
|
+
const format9 = ['pm', 'links', 'user', 'group', 'size', 'M', 'D', 'Y', 'name'];
|
|
95
|
+
const values = parse_cmd(line);
|
|
96
|
+
const obj = toObj((values.length < 10) ? format9 : format10, values);
|
|
97
|
+
if (obj.pm) {
|
|
98
|
+
obj.type = obj.pm.substring(0, 1);
|
|
99
|
+
obj.pm = obj.pm.substring(1);
|
|
100
|
+
}
|
|
101
|
+
if (obj.M && obj.D) {
|
|
102
|
+
obj.date = obj.M + ' ' + obj.D;
|
|
103
|
+
delete obj.M;
|
|
104
|
+
delete obj.D;
|
|
105
|
+
if (obj.Y) {
|
|
106
|
+
obj.date += ' ' + obj.Y;
|
|
107
|
+
delete obj.Y;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (obj.H && obj.MM) {
|
|
111
|
+
obj.time = obj.H + ':' + obj.MM;
|
|
112
|
+
delete obj.H;
|
|
113
|
+
delete obj.MM;
|
|
114
|
+
}
|
|
115
|
+
return obj;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function test_exec_stream() {
|
|
119
|
+
sshTunnelProxy.debug_en = true;
|
|
120
|
+
await sshTunnelProxy.connectSSH(opts);
|
|
121
|
+
|
|
122
|
+
// exec remote command and save result to string
|
|
123
|
+
const uptime_result = await sshTunnelProxy.execCmd('uptime');
|
|
124
|
+
console.log('uptime:' + uptime_result.toString());
|
|
125
|
+
|
|
126
|
+
// exec remote command and pipe data through tunnel until no data
|
|
127
|
+
const tunnel = new PassThrough();
|
|
128
|
+
tunnel.pipe(split())
|
|
129
|
+
.pipe(to_ls_JSON)
|
|
130
|
+
.pipe(to_JSON_string)
|
|
131
|
+
.pipe(process.stdout);
|
|
132
|
+
|
|
133
|
+
await sshTunnelProxy.execCmd('ls -all', tunnel);
|
|
134
|
+
|
|
135
|
+
// stream processing complete
|
|
136
|
+
console.log('done');
|
|
137
|
+
process.exit();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
test_exec_stream();
|