ssh_tunnel_proxy 1.2.8 → 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 CHANGED
@@ -62,6 +62,49 @@ 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.js:
66
+
67
+ ```js
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
+ }
91
+
92
+ async function runTests() {
93
+
94
+ const sshTunnelProxy = new SSHTunnelProxy();
95
+
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);
101
+
102
+ process.exit();
103
+ }
104
+
105
+ runTests();
106
+ ```
107
+
65
108
  The following code is an example of use of the api with electronjs.
66
109
 
67
110
  main.js:
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];
@@ -102,63 +102,72 @@ class SSHTunnelProxy extends Client {
102
102
  }
103
103
 
104
104
  // function to setup proxy forwards for ssh tunnel
105
- on_ssh_client_ready(server_name, proxy_ports, resolve) {
105
+ setupProxyPorts(server_name, proxy_ports) {
106
106
 
107
- var listeners = 0;
108
- if (this.listener[server_name]) this.listener[server_name].isConnected = true;
109
- const _this = this;
107
+ return new Promise((resolve, reject) => {
108
+
109
+ if (!proxy_ports) {
110
+ resolve();
111
+ return;
112
+ }
110
113
 
111
- // iterate through a list of local forward ports and create a local proxy port
112
- proxy_ports.forEach(proxy_port => {
114
+ var listeners = 0;
115
+ if (this.listener[server_name]) this.listener[server_name].isConnected = true;
116
+ const _this = this;
113
117
 
114
- const [local_port, remote_hostname, remote_port] = proxy_port.split(':');
118
+ // iterate through a list of local forward ports and create a local proxy port
119
+ proxy_ports.forEach(proxy_port => {
115
120
 
116
- // create local socket server
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 => {
121
+ const [local_port, remote_hostname, remote_port] = proxy_port.split(':');
124
122
 
125
- if (this.debug_en) {
126
- var debug_msg = 'SSH Server :: connection on ' + local_port + ' ' + socket.remotePort;
127
- _this.emit('debug', debug_msg);
128
- _this.debug(debug_msg);
123
+ // create local socket server
124
+ const server = this.listener[server_name][proxy_port];
125
+ if (server && server.listening) {
126
+ this.debug_en && this.debug('SSH Server :: closing forward 2:', proxy_port)
127
+ server.close();
129
128
  }
129
+ this.listener[server_name][proxy_port] = null;
130
+ this.listener[server_name][proxy_port] = net.createServer({ keepAlive: true, allowHalfOpen: false }, socket => {
130
131
 
131
- // create a proxy forward between local and remote ports
132
- _this.setup_ssh_forward(socket, remote_hostname, remote_port);
133
- });
132
+ if (this.debug_en) {
133
+ var debug_msg = 'SSH Server :: connection on ' + local_port + ' ' + socket.remotePort;
134
+ _this.emit('debug', debug_msg);
135
+ _this.debug(debug_msg);
136
+ }
134
137
 
135
- // start listening on port
136
- try {
137
- var status_msg = 'SSH Server :: before listen on ' + local_port;
138
- _this.emit('debug', status_msg);
139
- _this.listener[server_name][proxy_port].listen(local_port, () => {
138
+ // create a proxy forward between local and remote ports
139
+ _this.setup_ssh_forward(socket, remote_hostname, remote_port);
140
+ });
140
141
 
141
- // emit server listening on port message
142
- var status_msg = 'SSH Server :: bound on ' + local_port;
142
+ // start listening on port
143
+ try {
144
+ var status_msg = 'SSH Server :: before listen on ' + local_port;
143
145
  _this.emit('debug', status_msg);
144
-
145
- // if all listeners have been successfully established, resolve setup connection
146
- if (listeners++ >= proxy_ports.length - 1) {
147
- _this.emit('ssh_tunnel_ready', {});
148
- if (_this._tunnelReadyTimeout) {
149
- clearTimeout(this._tunnelReadyTimeout);
150
- _this._tunnelReadyTimeout = undefined
146
+ _this.listener[server_name][proxy_port].listen(local_port, () => {
147
+
148
+ // emit server listening on port message
149
+ var status_msg = 'SSH Server :: bound on ' + local_port;
150
+ _this.emit('debug', status_msg);
151
+
152
+ // if all listeners have been successfully established, resolve setup connection
153
+ if (listeners++ >= proxy_ports.length - 1) {
154
+ _this.emit('ssh_tunnel_ready', {});
155
+ if (_this._tunnelReadyTimeout) {
156
+ clearTimeout(this._tunnelReadyTimeout);
157
+ _this._tunnelReadyTimeout = undefined
158
+ }
159
+ resolve();
151
160
  }
152
- resolve();
153
- }
154
- });
155
- } catch (err) {
156
- _this.debug_en && _this.debug('listen err:', err);
157
- }
161
+ });
162
+ } catch (err) {
163
+ _this.debug_en && _this.debug('listen err:', err);
164
+ reject(err);
165
+ }
166
+ });
158
167
  });
159
168
  }
160
169
 
161
- exec_cmd(cmd, dataStream, errStream) {
170
+ execCmd(cmd, dataStream, errStream) {
162
171
 
163
172
  return new Promise((resolve, reject) => {
164
173
  var bufs = [];
@@ -172,24 +181,19 @@ class SSHTunnelProxy extends Client {
172
181
  stream.on('close', () => {
173
182
  var buf = Buffer.concat(bufs);
174
183
  var err = Buffer.concat(errs);
184
+ //stream.removeListener('exit', onExit);
175
185
  resolve(buf, err);
176
186
  })
177
187
 
178
- if (dataStream) {
179
- stream.pipe(dataStream);
180
- } else {
181
- stream.on('data', (data) => {
182
- bufs.push(data);
183
- })
184
- }
188
+ stream.on('data', (data) => {
189
+ if (dataStream) dataStream.write(data);
190
+ else bufs.push(data);
191
+ })
185
192
 
186
- if (errStream) {
187
- stream.stderr.pipe(errStream);
188
- } else {
189
- stream.stderr.on('data', (data) => {
190
- errs.push(data);
191
- });
192
- }
193
+ stream.stderr.on('data', (data) => {
194
+ if (errStream) errStream.write(data);
195
+ else errs.push(data);
196
+ });
193
197
 
194
198
  }
195
199
  this.exec(cmd, remote_exec);
@@ -261,7 +265,7 @@ class SSHTunnelProxy extends Client {
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.exec_cmd(cmd);
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.on_ssh_client_ready(opts.server_name, opts.proxy_ports, resolve);
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.8",
3
+ "version": "1.2.10",
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
- "sshpk": "^1.17.0"
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,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
+