ssh_tunnel_proxy 1.2.9 → 1.2.10
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 +33 -16
- package/lib/index.js +16 -16
- package/package.json +1 -1
- package/test/test_remote_exec.js +219 -0
- package/test/test_remote_exec_streams.js +0 -140
package/README.md
CHANGED
|
@@ -62,30 +62,47 @@ 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/
|
|
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.js:
|
|
66
66
|
|
|
67
67
|
```js
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
// lsLongShellProc to json stream test
|
|
69
|
+
async function lsTest(sshTunnelProxy) {
|
|
70
|
+
return new Promise(async (resolve) => {
|
|
71
|
+
|
|
72
|
+
// exec remote command and pipe data through tunnel until end of data
|
|
73
|
+
const tunnel = new PassThrough();
|
|
74
|
+
pipeline(tunnel,
|
|
75
|
+
split(),
|
|
76
|
+
to_lsParse(),
|
|
77
|
+
to_JSONString(),
|
|
78
|
+
process.stdout,
|
|
79
|
+
() => { }
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const lscmd = 'ls -all';
|
|
83
|
+
console.log('\ninvoking ' + lscmd + ' on remote host:\n');
|
|
84
|
+
await sshTunnelProxy.execCmd(lscmd, tunnel);
|
|
85
|
+
|
|
86
|
+
// stream processing complete
|
|
87
|
+
console.log('ls -all completed');
|
|
88
|
+
resolve();
|
|
89
|
+
})
|
|
90
|
+
}
|
|
71
91
|
|
|
72
|
-
|
|
73
|
-
const uptime_result = await sshTunnelProxy.execCmd('uptime');
|
|
74
|
-
console.log('uptime:' + uptime_result.toString());
|
|
92
|
+
async function runTests() {
|
|
75
93
|
|
|
76
|
-
|
|
77
|
-
const tunnel = new PassThrough();
|
|
78
|
-
tunnel.pipe(split())
|
|
79
|
-
.pipe(to_ls_JSON)
|
|
80
|
-
.pipe(to_JSON_string)
|
|
81
|
-
.pipe(process.stdout);
|
|
94
|
+
const sshTunnelProxy = new SSHTunnelProxy();
|
|
82
95
|
|
|
83
|
-
|
|
96
|
+
// connect to remote host
|
|
97
|
+
await sshTunnelProxy.connectSSH(opts);
|
|
98
|
+
|
|
99
|
+
// invoke ls -all on remote host and parse result to json object string
|
|
100
|
+
await lsTest(sshTunnelProxy);
|
|
84
101
|
|
|
85
|
-
// stream processing complete
|
|
86
|
-
console.log('done');
|
|
87
102
|
process.exit();
|
|
88
103
|
}
|
|
104
|
+
|
|
105
|
+
runTests();
|
|
89
106
|
```
|
|
90
107
|
|
|
91
108
|
The following code is an example of use of the api with electronjs.
|
package/lib/index.js
CHANGED
|
@@ -40,7 +40,7 @@ class SSHTunnelProxy extends Client {
|
|
|
40
40
|
// function to close all active sockets for tunnel restart and exception handling
|
|
41
41
|
close_sockets(server_name, proxy_ports) {
|
|
42
42
|
|
|
43
|
-
if (!this.listener[server_name]) return;
|
|
43
|
+
if (!this.listener[server_name] || !proxy_ports) return;
|
|
44
44
|
|
|
45
45
|
proxy_ports.forEach(proxy_port => {
|
|
46
46
|
const server = this.listener[server_name][proxy_port];
|
|
@@ -105,6 +105,11 @@ class SSHTunnelProxy extends Client {
|
|
|
105
105
|
setupProxyPorts(server_name, proxy_ports) {
|
|
106
106
|
|
|
107
107
|
return new Promise((resolve, reject) => {
|
|
108
|
+
|
|
109
|
+
if (!proxy_ports) {
|
|
110
|
+
resolve();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
108
113
|
|
|
109
114
|
var listeners = 0;
|
|
110
115
|
if (this.listener[server_name]) this.listener[server_name].isConnected = true;
|
|
@@ -176,24 +181,19 @@ class SSHTunnelProxy extends Client {
|
|
|
176
181
|
stream.on('close', () => {
|
|
177
182
|
var buf = Buffer.concat(bufs);
|
|
178
183
|
var err = Buffer.concat(errs);
|
|
184
|
+
//stream.removeListener('exit', onExit);
|
|
179
185
|
resolve(buf, err);
|
|
180
186
|
})
|
|
181
187
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
bufs.push(data);
|
|
187
|
-
})
|
|
188
|
-
}
|
|
188
|
+
stream.on('data', (data) => {
|
|
189
|
+
if (dataStream) dataStream.write(data);
|
|
190
|
+
else bufs.push(data);
|
|
191
|
+
})
|
|
189
192
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
errs.push(data);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
193
|
+
stream.stderr.on('data', (data) => {
|
|
194
|
+
if (errStream) errStream.write(data);
|
|
195
|
+
else errs.push(data);
|
|
196
|
+
});
|
|
197
197
|
|
|
198
198
|
}
|
|
199
199
|
this.exec(cmd, remote_exec);
|
|
@@ -259,7 +259,7 @@ class SSHTunnelProxy extends Client {
|
|
|
259
259
|
if (opts.shell) {
|
|
260
260
|
_this.shell(_this.remote_shell);
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
// if exec requested, exec series of cmds
|
|
263
263
|
} else {
|
|
264
264
|
if (opts.exec && opts.exec.length > 0) {
|
|
265
265
|
for (var i = 0; i < opts.exec.length; i++) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/*
|
|
2
|
+
test exec cmd on remote server, parse to json object stream
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
//const assert = require('assert');
|
|
6
|
+
//const { describe, it } = require('mocha');
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const process = require('node:process');
|
|
10
|
+
const { SSHTunnelProxy } = require('..');
|
|
11
|
+
const { PassThrough, pipeline } = require("stream");
|
|
12
|
+
const through = require('through');
|
|
13
|
+
const split = require('split');
|
|
14
|
+
|
|
15
|
+
// get config file containing api keys
|
|
16
|
+
const homedir = require('os').homedir();
|
|
17
|
+
var config = null;
|
|
18
|
+
try {
|
|
19
|
+
config = JSON.parse(fs.readFileSync(homedir + '/.config/ssh_tunnel_proxy/config.json'), 'utf8');
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.log('Error reading config:' + err);
|
|
22
|
+
process.exit();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!config && !config[1]) {
|
|
26
|
+
console.log('Specified config not found in configs');
|
|
27
|
+
process.exit();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const opts = config[1];
|
|
31
|
+
|
|
32
|
+
const sshTunnelProxy = new SSHTunnelProxy();
|
|
33
|
+
|
|
34
|
+
// parse shell command result into array of strings
|
|
35
|
+
function parse_cmd(str) {
|
|
36
|
+
|
|
37
|
+
let result = [];
|
|
38
|
+
|
|
39
|
+
let regex = /(([\w-/_~.\:\[\]]+)|("(.*?)")|('(.*?)'))/g;
|
|
40
|
+
let groups = [2, 4, 6];
|
|
41
|
+
let match;
|
|
42
|
+
|
|
43
|
+
while ((match = regex.exec(str)) !== null) {
|
|
44
|
+
// This is necessary to avoid infinite loops
|
|
45
|
+
// with zero-width matches
|
|
46
|
+
if (match.index === regex.lastIndex) {
|
|
47
|
+
regex.lastIndex++;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// For this to work the regex groups need to
|
|
51
|
+
// be mutually exclusive
|
|
52
|
+
groups.forEach(function (group) {
|
|
53
|
+
if (match[group]) {
|
|
54
|
+
result.push(match[group]);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
/*
|
|
58
|
+
let log_matches = false;
|
|
59
|
+
// show matches for debugging
|
|
60
|
+
log_matches && match.forEach(function (m, group) {
|
|
61
|
+
if (m) {
|
|
62
|
+
console.log(`Match '${m}' found in group: ${group}`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
*/
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// generate object from arrays of names/values
|
|
71
|
+
function toObj(names, values) {
|
|
72
|
+
const obj = {};
|
|
73
|
+
for (var i = 0; i < names.length; i++) {
|
|
74
|
+
obj[names[i]] = values[i];
|
|
75
|
+
}
|
|
76
|
+
return obj;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// merge array items beginning at start into single item and append back to array
|
|
81
|
+
function mergeArray(ar, start) {
|
|
82
|
+
const ar2 = ar.slice(start);
|
|
83
|
+
const ar1 = ar.slice(0, start);
|
|
84
|
+
const item = ar2.join(' ');
|
|
85
|
+
ar1.push(item);
|
|
86
|
+
return ar1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
Feb 4 21:34:17 tim-ThinkPad-P50s systemd[1]: Started Run anacron jobs.
|
|
91
|
+
*/
|
|
92
|
+
function parseSyslog(line) {
|
|
93
|
+
const format = ['M', 'D', 'T', 'host', 'proc', 'msg'];
|
|
94
|
+
const values = parse_cmd(line);
|
|
95
|
+
const log = mergeArray(values, 5);
|
|
96
|
+
const obj = toObj(format, log);
|
|
97
|
+
if (obj.M && obj.D && obj.T) {
|
|
98
|
+
obj.date = obj.M + ' ' + obj.D + ' ' + obj.T;
|
|
99
|
+
delete obj.M;
|
|
100
|
+
delete obj.D;
|
|
101
|
+
delete obj.T;
|
|
102
|
+
}
|
|
103
|
+
return obj;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// parse ls long line output into json object
|
|
107
|
+
function processLSLong(line) {
|
|
108
|
+
const format10 = ['pm', 'links', 'user', 'group', 'size', 'M', 'D', 'H', 'MM', 'name'];
|
|
109
|
+
const format9 = ['pm', 'links', 'user', 'group', 'size', 'M', 'D', 'Y', 'name'];
|
|
110
|
+
const values = parse_cmd(line);
|
|
111
|
+
const obj = toObj((values.length < 10) ? format9 : format10, values);
|
|
112
|
+
if (obj.pm) {
|
|
113
|
+
obj.type = obj.pm.substring(0, 1);
|
|
114
|
+
obj.pm = obj.pm.substring(1);
|
|
115
|
+
}
|
|
116
|
+
if (obj.M && obj.D) {
|
|
117
|
+
obj.date = obj.M + ' ' + obj.D;
|
|
118
|
+
delete obj.M;
|
|
119
|
+
delete obj.D;
|
|
120
|
+
if (obj.Y) {
|
|
121
|
+
obj.date += ' ' + obj.Y;
|
|
122
|
+
delete obj.Y;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (obj.H && obj.MM) {
|
|
126
|
+
obj.time = obj.H + ':' + obj.MM;
|
|
127
|
+
delete obj.H;
|
|
128
|
+
delete obj.MM;
|
|
129
|
+
}
|
|
130
|
+
return obj;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function to_lsParse() {
|
|
134
|
+
return new through(function (data) {
|
|
135
|
+
const obj = processLSLong(data);
|
|
136
|
+
if (obj.name) this.queue(obj);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function to_syslogParse() {
|
|
141
|
+
return new through(function (data) {
|
|
142
|
+
const obj = parseSyslog(data);
|
|
143
|
+
if (obj.proc) this.queue(obj);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function to_JSONString() {
|
|
148
|
+
return new through(function (data) {
|
|
149
|
+
this.queue(JSON.stringify(data) + '\n');
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// lsLongShellProc conversion tests
|
|
154
|
+
async function lsTest(sshTunnelProxy) {
|
|
155
|
+
return new Promise(async (resolve) => {
|
|
156
|
+
|
|
157
|
+
// exec remote command and pipe data through tunnel until end of data
|
|
158
|
+
const tunnel = new PassThrough();
|
|
159
|
+
pipeline(tunnel,
|
|
160
|
+
split(),
|
|
161
|
+
to_lsParse(),
|
|
162
|
+
to_JSONString(),
|
|
163
|
+
process.stdout,
|
|
164
|
+
() => { }
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const lscmd = 'ls -all';
|
|
168
|
+
//const lscmd = 'ls -all | column --table --table-columns pm,links,user,group,size,month,day,time,name -J';
|
|
169
|
+
console.log('\ninvoking ' + lscmd + ' on remote host:\n');
|
|
170
|
+
await sshTunnelProxy.execCmd(lscmd, tunnel);
|
|
171
|
+
|
|
172
|
+
// stream processing complete
|
|
173
|
+
console.log('ls -all completed');
|
|
174
|
+
resolve();
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// lsLongShellProc conversion tests
|
|
179
|
+
async function syslogTest(sshTunnelProxy) {
|
|
180
|
+
return new Promise(async (resolve) => {
|
|
181
|
+
|
|
182
|
+
// exec remote command and pipe data through tunnel until end of data
|
|
183
|
+
const tunnel = new PassThrough();
|
|
184
|
+
pipeline(tunnel,
|
|
185
|
+
split(),
|
|
186
|
+
to_syslogParse(),
|
|
187
|
+
to_JSONString(),
|
|
188
|
+
process.stdout,
|
|
189
|
+
() => { }
|
|
190
|
+
);
|
|
191
|
+
//const syslogcmd = 'tail /var/log/syslog | column --table --table-columns-limit 6 --table-columns month,day,time,host,proc,msg -J';
|
|
192
|
+
const syslogcmd = 'tail /var/log/syslog';
|
|
193
|
+
console.log('\ninvoking ' + syslogcmd + ' on remote host:\n');
|
|
194
|
+
await sshTunnelProxy.execCmd(syslogcmd, tunnel);
|
|
195
|
+
|
|
196
|
+
// stream processing complete
|
|
197
|
+
console.log(syslogcmd+' completed');
|
|
198
|
+
resolve();
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function runTests() {
|
|
203
|
+
|
|
204
|
+
// connect to remote host
|
|
205
|
+
sshTunnelProxy.debug_en = true;
|
|
206
|
+
await sshTunnelProxy.connectSSH(opts);
|
|
207
|
+
|
|
208
|
+
// invoke ls -all on remote host and parse result to json object string
|
|
209
|
+
await lsTest(sshTunnelProxy);
|
|
210
|
+
|
|
211
|
+
// invoke tail /var/log/syslog on remote host and parse result to json object string
|
|
212
|
+
await syslogTest(sshTunnelProxy);
|
|
213
|
+
|
|
214
|
+
process.exit();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
runTests();
|
|
218
|
+
|
|
219
|
+
|
|
@@ -1,140 +0,0 @@
|
|
|
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();
|