ssh2shell 2.0.1 → 2.0.4
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/Lots of commands.log +445 -0
- package/README.md +637 -763
- package/first.log +30 -0
- package/lib/ssh2shell.js +44 -17
- package/package.json +1 -1
- package/second.log +30 -0
- package/src/ssh2shell.coffee +36 -24
- package/test/.ignore +0 -0
- package/test/Multiple primary hosts.js +0 -66
- package/test/devtest.js +0 -51
- package/test/issue69.js +0 -57
- package/test/keyboard-interactivetest.js +0 -26
- package/test/lots of commands.js +0 -87
- package/test/notificationstest.js +0 -36
- package/test/pipe.js +0 -30
- package/test/simple.js +0 -26
- package/test/sshUserCommandsPosition.js +0 -26
- package/test/sudo no pass test.js +0 -40
- package/test/sudosutest.js +0 -77
- package/test/timeouttest.js +0 -97
- package/test/tunneltest.js +0 -71
- package/test/tunneltestSshUserCommands.js +0 -71
package/README.md
CHANGED
|
@@ -2,44 +2,33 @@ ssh2shell
|
|
|
2
2
|
=========
|
|
3
3
|
[](https://nodei.co/npm/ssh2shell/)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
character.
|
|
33
|
-
* Use a callback function to handle final session text. The callback function can be defined in the host object or
|
|
34
|
-
passed to SSH2shell.connect(callback). The callback function has access to the `this` keyword.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Code:
|
|
38
|
-
-----
|
|
39
|
-
The Class is written in coffee script which can be found here: `./src/ssh2shell.coffee`.
|
|
40
|
-
It has comments not found in the build output JavaScript file `./lib/ssh2shell.js`.
|
|
41
|
-
|
|
42
|
-
|
|
5
|
+
Ssh2shell uses [ssh2](https://www.npmjs.org/package/ssh2) to open a ssh shell session to a host/s to run multiple commands and process the responses.
|
|
6
|
+
|
|
7
|
+
Index:
|
|
8
|
+
------------
|
|
9
|
+
* [Instalation](https://github.com/cmp-202/ssh2shell#installation)
|
|
10
|
+
* [Minimal Example](https://github.com/cmp-202/ssh2shell#minimal-example)
|
|
11
|
+
* [Host Configuration](https://github.com/cmp-202/ssh2shell#host-configuration)
|
|
12
|
+
* [ssh2shell API](https://github.com/cmp-202/ssh2shell#ssh2shell-api)
|
|
13
|
+
* [Connecting to a single host](https://github.com/cmp-202/ssh2shell#connecting-to-a-single-host)
|
|
14
|
+
* [Authentication](https://github.com/cmp-202/ssh2shell#authentication)
|
|
15
|
+
* [Default Cyphers](https://github.com/cmp-202/ssh2shell#default-cyphers)
|
|
16
|
+
* [Fingerprint Validation](https://github.com/cmp-202/ssh2shell#fingerprint-validation)
|
|
17
|
+
* [Keyboard-interactive](https://github.com/cmp-202/ssh2shell#keyboard-interactive)
|
|
18
|
+
* [Trouble shooting](https://github.com/cmp-202/ssh2shell#trouble-shooting)
|
|
19
|
+
* [Verbose and Debug](https://github.com/cmp-202/ssh2shell#verbose-and-debug)
|
|
20
|
+
* [Notification commands](https://github.com/cmp-202/ssh2shell#notification-commands)
|
|
21
|
+
* [Sudo and su Commands](https://github.com/cmp-202/ssh2shell#sudo-and-su-commands)
|
|
22
|
+
* [Prompt detection override](https://github.com/cmp-202/ssh2shell#prompt-detection-override)
|
|
23
|
+
* [Text regular expression filters](https://github.com/cmp-202/ssh2shell#text-regular-expression-filters)
|
|
24
|
+
* [Responding to non-standard command prompts](https://github.com/cmp-202/ssh2shell#responding-to-non-standard-command-prompts)
|
|
25
|
+
* [Command Time-out event](https://github.com/cmp-202/ssh2shell#command-time-out-event)
|
|
26
|
+
* [Multiple Primary Hosts](https://github.com/cmp-202/ssh2shell#multiple-primary-hosts)
|
|
27
|
+
* [Tunnelling nested host objects](https://github.com/cmp-202/ssh2shell#tunnelling-nested-host-objects)
|
|
28
|
+
* [Event Handlers](https://github.com/cmp-202/ssh2shell#event-handlers)
|
|
29
|
+
* [Bash scripts on the fly](https://github.com/cmp-202/ssh2shell#bash-scripts-on-the-fly)
|
|
30
|
+
|
|
31
|
+
|
|
43
32
|
Installation:
|
|
44
33
|
------------
|
|
45
34
|
```
|
|
@@ -52,18 +41,16 @@ Minimal Example:
|
|
|
52
41
|
```javascript
|
|
53
42
|
//host configuration with connection settings and commands
|
|
54
43
|
var host = {
|
|
55
|
-
server: {
|
|
44
|
+
server: {
|
|
56
45
|
host: "127.0.0.1",
|
|
57
46
|
userName: "test",
|
|
58
|
-
password: "
|
|
47
|
+
password: "password",
|
|
59
48
|
},
|
|
60
49
|
commands: [ "echo $(pwd)", "ls -l" ]
|
|
61
50
|
};
|
|
62
51
|
|
|
63
52
|
var SSH2Shell = require ('ssh2shell'),
|
|
64
|
-
|
|
65
|
-
SSH = new SSH2Shell(host),
|
|
66
|
-
//Use a callback function to process the full session text
|
|
53
|
+
SSH = new SSH2Shell(host),
|
|
67
54
|
callback = function(sessionText){
|
|
68
55
|
console.log(sessionText)
|
|
69
56
|
}
|
|
@@ -72,73 +59,60 @@ var SSH2Shell = require ('ssh2shell'),
|
|
|
72
59
|
SSH.connect(callback);
|
|
73
60
|
```
|
|
74
61
|
|
|
62
|
+
|
|
75
63
|
Host Configuration:
|
|
76
64
|
------------
|
|
77
|
-
SSH2Shell
|
|
65
|
+
SSH2Shell requires an object with the following structure:
|
|
78
66
|
```javascript
|
|
79
|
-
//Host
|
|
67
|
+
//Host cobfiguration
|
|
80
68
|
host = {
|
|
81
69
|
server: {
|
|
82
|
-
//
|
|
83
|
-
host: "
|
|
84
|
-
port: "
|
|
85
|
-
userName: "
|
|
86
|
-
//optional or required settings subject to authentication method
|
|
87
|
-
password: "
|
|
70
|
+
//host connection settings
|
|
71
|
+
host: "192.168.0.2",
|
|
72
|
+
port: "22", //optional
|
|
73
|
+
userName: "username",
|
|
74
|
+
//optional or required settings subject to authentication method
|
|
75
|
+
password: "password",
|
|
88
76
|
passPhrase: "privateKeyPassphrase",
|
|
89
77
|
privateKey: require('fs').readFileSync('/path/to/private/key/id_rsa'),
|
|
90
|
-
//Optional: ssh2.connect
|
|
78
|
+
//Optional: ssh2.connect options
|
|
91
79
|
//See https://github.com/mscdex/ssh2#client-methods
|
|
92
|
-
//ssh2.connect parameters are only valid for the first host connection.
|
|
93
|
-
//Other host connections use the ssh command/s to connect not ssh2.
|
|
94
80
|
|
|
95
|
-
//Optional: ssh options only for making secondary host ssh connections
|
|
96
|
-
//see http://man.openbsd.org/ssh for definitions of options
|
|
97
|
-
ssh: {
|
|
98
|
-
//Optional
|
|
99
|
-
forceProtocolVersion: true/false,
|
|
100
|
-
forceAddressType: true/false,
|
|
101
|
-
disablePseudoTTY: true/false,
|
|
102
|
-
forcePseudoTTY: true/false,
|
|
103
|
-
verbose: true/false,
|
|
104
|
-
cipherSpec: "",
|
|
105
|
-
escape: "",
|
|
106
|
-
logFile: "",
|
|
107
|
-
configFile: "",
|
|
108
|
-
identityFile: "",
|
|
109
|
-
loginName: "",
|
|
110
|
-
macSpec: "",
|
|
111
|
-
Options: {}
|
|
112
|
-
}
|
|
81
|
+
//Optional: ssh options only for making secondary host ssh connections.
|
|
82
|
+
//see http://man.openbsd.org/ssh for definitions of options.
|
|
83
|
+
ssh: { //Options }
|
|
113
84
|
},
|
|
114
85
|
//Optional: Array of host objects for multiple host connections
|
|
115
|
-
hosts: [
|
|
86
|
+
hosts: [],
|
|
116
87
|
|
|
117
88
|
//Optional: Characters used in prompt detection
|
|
89
|
+
//defaults
|
|
118
90
|
standardPrompt: ">$%#",
|
|
119
91
|
passwordPrompt: ":",
|
|
120
92
|
passphrasePrompt: ":",
|
|
121
93
|
passPromptText: "Password",
|
|
122
94
|
|
|
123
|
-
//Optional: exclude or include host banner after connection
|
|
95
|
+
//Optional: exclude or include host banner after connection
|
|
124
96
|
showBanner: false,
|
|
125
|
-
|
|
97
|
+
//https://github.com/mscdex/ssh2#pseudo-tty-settings
|
|
98
|
+
window: false,
|
|
126
99
|
|
|
127
100
|
//Optional: Enter key character to send as end of line.
|
|
128
|
-
enter: "\n",
|
|
101
|
+
enter: "\n",
|
|
129
102
|
|
|
130
103
|
//Optional: stream encoding
|
|
131
104
|
streamEncoding: "utf8",
|
|
132
105
|
|
|
133
|
-
//Optional: Regular expressions to
|
|
106
|
+
//Optional: Regular expressions to simplify response
|
|
107
|
+
//defaults:
|
|
134
108
|
asciiFilter: "[^\r\n\x20-\x7e]", //removes non-standard ASCII
|
|
135
109
|
disableColorFilter: false, //turns colour filtering on and off
|
|
136
110
|
textColorFilter: "(\[{1}[0-9;]+m{1})", //removes colour formatting codes
|
|
137
111
|
|
|
138
112
|
//Required: array of commands
|
|
139
|
-
commands: ["cd /var/logs", "ls -al"
|
|
113
|
+
commands: ["cd /var/logs", "ls -al"],
|
|
140
114
|
|
|
141
|
-
//Optional: Used by this.emit("msg", "my message")
|
|
115
|
+
//Optional: Used by this.emit("msg", "my message")
|
|
142
116
|
msg: function( message ) {
|
|
143
117
|
console.log(message);
|
|
144
118
|
}
|
|
@@ -148,121 +122,99 @@ host = {
|
|
|
148
122
|
verbose: false, //outputs all received content
|
|
149
123
|
debug: false, //outputs information about each process step
|
|
150
124
|
|
|
151
|
-
//Optional: Command time-out
|
|
125
|
+
//Optional: Command time-out to stop a command hanging and not returning to a command prompt
|
|
152
126
|
idleTimeOut: 5000, //integer
|
|
153
127
|
|
|
154
|
-
//Optional: timeout
|
|
128
|
+
//Optional: timeout interval when waiting for a host response.
|
|
155
129
|
dataIdleTimeOut: 500, //integer
|
|
156
130
|
|
|
157
131
|
//Optional: Messages returned on each connection event.
|
|
132
|
+
//default:
|
|
158
133
|
connectedMessage: "Connected",
|
|
159
134
|
readyMessage: "Ready",
|
|
160
135
|
closedMessage: "Closed",
|
|
161
136
|
|
|
162
|
-
//Optional: Host event handlers
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//in the class instead of adding another handler to the listeners.
|
|
166
|
-
//`this.` is correctly linked to the instance within host defined event handlers.
|
|
137
|
+
//Optional: Host event handlers.
|
|
138
|
+
//Host defined event handlers are cleared with each change of host.
|
|
139
|
+
//Default and instance event handlers are global to all hosts and are not cleared.
|
|
167
140
|
|
|
168
|
-
//Optional:
|
|
169
|
-
//This event is only used for the first host connecting through ssh2.connect
|
|
170
|
-
//Required if the first host.server.tryKeyboard is set to true.
|
|
141
|
+
//Optional:
|
|
171
142
|
onKeyboardInteractive: function(name, instructions, instructionsLang, prompts, finish){
|
|
172
|
-
//See https://github.com/mscdex/ssh2#client-events
|
|
173
|
-
//name, instructions, instructionsLang don't seem to be of interest for authenticating
|
|
174
|
-
//prompts is an object of expected prompts and if they are to be shown to the user
|
|
175
|
-
//finish is the function to be called with an array of responses in the same order as
|
|
176
|
-
//the prompts parameter defined them.
|
|
177
|
-
//See [Client events](https://github.com/mscdex/ssh2#client-events) for more information
|
|
178
|
-
//if a non-standard prompt results from a successful connection then handle its
|
|
179
|
-
//detection and response in onCommandProcessing or commandTimeout.
|
|
180
|
-
//see text/keyboard-interactivetest.js
|
|
143
|
+
//See https://github.com/mscdex/ssh2#client-events
|
|
181
144
|
},
|
|
182
145
|
|
|
183
|
-
//Optional:
|
|
184
|
-
//without SSH2shell interacting with it.
|
|
146
|
+
//Optional:
|
|
185
147
|
onData: function( data ) {
|
|
186
|
-
//data is
|
|
148
|
+
//data is the raw response from the host
|
|
187
149
|
},
|
|
188
150
|
|
|
189
|
-
//Optional:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
151
|
+
//Optional:
|
|
152
|
+
onPipe: function( stream ) {
|
|
153
|
+
//a read stream that will receive raw resonse data
|
|
154
|
+
},
|
|
194
155
|
|
|
195
|
-
//Optional:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
//source is the read stream to remove from being able to write its output.
|
|
156
|
+
//Optional:
|
|
157
|
+
onUnpipe: function( steam ) {
|
|
158
|
+
//removes the steam added in onPipe.
|
|
199
159
|
},
|
|
200
160
|
|
|
201
|
-
//Optional:
|
|
202
|
-
//until prompt is detected.
|
|
161
|
+
//Optional:
|
|
203
162
|
onCommandProcessing: function( command, response, sshObj, stream ) {
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
163
|
+
//Event rasised when data is received from the host before the command prompt is detected.
|
|
164
|
+
//command: is the last command run.
|
|
165
|
+
//response: is the buffer that is being loaded with each data event.
|
|
166
|
+
//sshObj: is the current host object.
|
|
167
|
+
//stream: gives stream.write() access if a response is required outside the normal command flow.
|
|
208
168
|
},
|
|
209
169
|
|
|
210
|
-
//Optional:
|
|
170
|
+
//Optional:
|
|
211
171
|
onCommandComplete: function( command, response, sshObj ) {
|
|
212
|
-
//
|
|
213
|
-
//
|
|
172
|
+
//Event raised when a standard prompt is detected after a command is run.
|
|
173
|
+
//response: is the full response from the host for the last command.
|
|
174
|
+
//sshObj: is the current host object.
|
|
214
175
|
},
|
|
215
176
|
|
|
216
|
-
//Optional:
|
|
217
|
-
//received from the host after host.idleTimeout value.
|
|
218
|
-
//This stops the connection from hanging when no prompt is detected usually because the
|
|
219
|
-
// host is requiring a response it will never get. This event
|
|
177
|
+
//Optional:
|
|
220
178
|
onCommandTimeout: function( command, response, stream, connection ) {
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//close the connection
|
|
179
|
+
//Event is raised when a standard prompt is not detected and no data is
|
|
180
|
+
//received from the host within the host.idleTimeout value.
|
|
181
|
+
//command: is the last command run or "" when first connected.
|
|
182
|
+
//response: is the text received up to the time out.
|
|
183
|
+
//stream: gives stream.write() access if a response is required outside the normal command flow.
|
|
184
|
+
//connection: gives access to close the connection if all else fails
|
|
227
185
|
//The timer can be reset from within this event in case a stream write gets no response.
|
|
228
|
-
//See test/timeouttest.js for and example of multiple commandTimeout triggers.
|
|
229
186
|
},
|
|
230
187
|
|
|
231
|
-
//
|
|
232
|
-
//connection is closed or when any nested host connection is closed.
|
|
233
|
-
//This is where you handle the full session text for a host connection.
|
|
234
|
-
//When connecting to multiple hosts the primary host end event receives the full concatenated
|
|
235
|
-
//session text for all hosts, including its own, to process.
|
|
188
|
+
//Optional:
|
|
236
189
|
onEnd: function( sessionText, sshObj ) {
|
|
190
|
+
//is raised when the stream.on ("finish") event is triggered
|
|
237
191
|
//SessionText is the full text for this hosts session
|
|
238
192
|
//sshObj is the host object
|
|
239
193
|
},
|
|
240
194
|
|
|
241
|
-
//Optional:
|
|
195
|
+
//Optional:
|
|
242
196
|
onError: function( err, type, close = false, callback ) {
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
197
|
+
//Run when an error event is raised.
|
|
198
|
+
//err: is either an Error object or a string containing the error message
|
|
199
|
+
//type: is a string containing the error type
|
|
200
|
+
//close: is a Boolean value indicating if the connection should be closed or not
|
|
201
|
+
//callback: is the function to run when handling the error defined as function(err,type){}
|
|
202
|
+
//if using this remember to handle closing the connection based on the close parameter.
|
|
248
203
|
//To close the connection us this.connection.close()
|
|
249
204
|
},
|
|
250
205
|
|
|
251
|
-
//Optional:
|
|
206
|
+
//Optional:
|
|
252
207
|
callback: function( sessionText ){
|
|
253
|
-
//sessionText is the full session response
|
|
208
|
+
//sessionText: is the full session response
|
|
254
209
|
//Is overridden by SSH2shell.connect(callback)
|
|
255
210
|
}
|
|
256
211
|
};
|
|
257
212
|
```
|
|
258
213
|
* Host.server will accept current [SSH2.client.connect parameters](https://github.com/mscdex/ssh2#client-methods).
|
|
259
214
|
* Optional host properties or event handlers do not need to be included if you are not changing them.
|
|
260
|
-
* See the end of the readme for event handlers available to be added to the instance listeners.
|
|
261
215
|
* Host event handlers completely replace the default event handler definitions in the class when defined.
|
|
262
|
-
* The instance `this` keyword is available within host event handlers to give access to ssh2shell object api like
|
|
263
|
-
this.emit() and other functions.
|
|
264
216
|
* `this.sshObj` or sshObj variable passed into a function provides access to all the host config, some instance
|
|
265
|
-
variables and the current array of commands for the host. The sshObj.commands array changes with host connection.
|
|
217
|
+
variables and the current array of commands for the host. The sshObj.commands array changes with each host connection.
|
|
266
218
|
|
|
267
219
|
|
|
268
220
|
ssh2shell API
|
|
@@ -271,15 +223,15 @@ SSH2Shell extends events.EventEmitter
|
|
|
271
223
|
|
|
272
224
|
*Methods*
|
|
273
225
|
|
|
274
|
-
* .connect(callback(sessionText)) Is the main function to establish the connection and handle data events from the server
|
|
275
|
-
|
|
226
|
+
* .connect(callback(sessionText)) Is the main function to establish the connection and handle data events from the server.
|
|
227
|
+
It takes in an optional callback function for processing the full session text.
|
|
276
228
|
|
|
277
229
|
* .emit("eventName", function, parms,... ). Raises the event based on the name in the first string and takes input
|
|
278
230
|
parameters based on the handler function definition.
|
|
279
231
|
|
|
280
|
-
* .pipe(
|
|
232
|
+
* .pipe(stream) binds a writable stream to the output of the read stream but only after the connection is established.
|
|
281
233
|
|
|
282
|
-
* .unpipe(
|
|
234
|
+
* .unpipe(stream) removes a piped stream but can only be called after a connection has been made.
|
|
283
235
|
|
|
284
236
|
*Variables*
|
|
285
237
|
|
|
@@ -288,362 +240,209 @@ SSH2Shell extends events.EventEmitter
|
|
|
288
240
|
* .command is the current command being run until a new prompt is detected and the next command replaces it.
|
|
289
241
|
|
|
290
242
|
|
|
291
|
-
Test Files:
|
|
292
|
-
-----
|
|
293
|
-
```javascript
|
|
294
|
-
//single host test
|
|
295
|
-
cp .env-example .env
|
|
296
|
-
//set the env variables to match your test host/vm username and password.
|
|
297
|
-
node text/simple.js
|
|
298
|
-
|
|
299
|
-
//simple pipe example
|
|
300
|
-
node test/pipe.js
|
|
301
|
-
|
|
302
|
-
//multiple nested hosts
|
|
303
|
-
//requires the additional details added to .env file for each server
|
|
304
|
-
//my tests were done using three VM hosts
|
|
305
|
-
node test/tunneltest.js
|
|
306
|
-
|
|
307
|
-
//test the command idle time out timer and provide an example of a more complicated timeout handler
|
|
308
|
-
node test/timeouttest.js
|
|
309
|
-
|
|
310
|
-
//Test multiple sudo and su combinations for changing user
|
|
311
|
-
//Issue #10
|
|
312
|
-
//Also test Prompt detection with no password requested
|
|
313
|
-
//Issue #14
|
|
314
|
-
node test/sudosutest.js
|
|
315
|
-
|
|
316
|
-
//Test using notification commands as the last command
|
|
317
|
-
//Issue #11
|
|
318
|
-
node test/notificationstest.js
|
|
319
|
-
|
|
320
|
-
//Test keyboard-interactive authentication on the host that has it enabled
|
|
321
|
-
node test/keyboard-interactivetest.js
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
|
|
325
243
|
Usage:
|
|
326
244
|
======
|
|
327
245
|
Connecting to a single host:
|
|
328
246
|
----------------------------
|
|
329
247
|
|
|
330
|
-
*How to:*
|
|
331
|
-
* Use an .env file for server values loaded by dotenv from the root of the project.
|
|
332
|
-
* Connect using a key pair with pass phrase.
|
|
333
|
-
* Use sudo su with user password.
|
|
334
|
-
* Set commands.
|
|
335
|
-
* Test the response of a command and add more commands and notifications in the host.onCommandComplete event handler.
|
|
336
|
-
* Use the two notification types in the commands array.
|
|
337
|
-
* Use msg: notifications to track progress in the console as the process completes.
|
|
338
|
-
* Email the final full session text to yourself.
|
|
339
|
-
|
|
340
|
-
(will require a package json with ssh2shell, dotenv and email defined as dependencies)
|
|
341
|
-
|
|
342
|
-
*.env*
|
|
343
|
-
```
|
|
344
|
-
HOST=192.168.0.1
|
|
345
|
-
PORT=22
|
|
346
|
-
USER_NAME=myuser
|
|
347
|
-
PASSWORD=mypassword
|
|
348
|
-
PRIV_KEY_PATH=~/.ssh/id_rsa
|
|
349
|
-
PASS_PHRASE=myPassPhrase
|
|
350
|
-
```
|
|
351
248
|
|
|
352
249
|
*app.js*
|
|
353
250
|
```javascript
|
|
354
|
-
var dotenv = require('dotenv').config(),
|
|
355
|
-
Email = require('email');
|
|
356
|
-
|
|
357
251
|
var host = {
|
|
358
|
-
server:
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
passPhrase: process.env.PASS_PHRASE,
|
|
364
|
-
privateKey: require('fs').readFileSync(process.env.PRIV_KEY_PATH)
|
|
252
|
+
server: {
|
|
253
|
+
host: "192.168.0.1",
|
|
254
|
+
userName: "myuser",
|
|
255
|
+
passPhrase: "myPassPhrase",
|
|
256
|
+
privateKey: require('fs').readFileSync("~/.ssh/id_rsa")
|
|
365
257
|
},
|
|
366
258
|
commands: [
|
|
367
259
|
"`This is a message that will be added to the full sessionText`",
|
|
368
|
-
"msg:This is a message that will be
|
|
369
|
-
"echo $(pwd)",
|
|
370
|
-
"sudo su",
|
|
371
|
-
"msg:changing directory",
|
|
260
|
+
"msg:This is a message that will be displayed during the process",
|
|
372
261
|
"cd ~/",
|
|
373
|
-
"ls -l"
|
|
374
|
-
|
|
375
|
-
"echo $(pwd)",
|
|
376
|
-
"msg:Getting the directory listing to confirm the extra command was added",
|
|
377
|
-
"ls -l",
|
|
378
|
-
"`All done!`"
|
|
379
|
-
],
|
|
380
|
-
|
|
381
|
-
onCommandComplete: function( command, response, sshObj ) {
|
|
382
|
-
//confirm it is the root home dir and change to root's .ssh folder
|
|
383
|
-
if(sshObj.debug){
|
|
384
|
-
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete event, command: " + command);
|
|
385
|
-
}
|
|
386
|
-
if (command === "echo $(pwd)" && response.indexOf("/root") != -1 ) {
|
|
387
|
-
//unshift will add the command as the next command, use push to add command as the last command
|
|
388
|
-
sshObj.commands.unshift("msg:The command and response check worked. Added another cd command.");
|
|
389
|
-
sshObj.commands.unshift("cd .ssh");
|
|
390
|
-
}
|
|
391
|
-
//we are listing the dir so output it to the msg handler
|
|
392
|
-
else if (command === "ls -l"){
|
|
393
|
-
this.emit("msg", response);
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
|
|
397
|
-
onEnd: function( sessionText, sshObj ) {
|
|
398
|
-
//email the session text instead of outputting it to the console
|
|
399
|
-
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": host.onEnd event");}
|
|
400
|
-
var sessionEmail = new Email({
|
|
401
|
-
from: "me@example.com",
|
|
402
|
-
to: "me@example.com",
|
|
403
|
-
subject: "Automated SSH Session Response",
|
|
404
|
-
body: "\nThis is the full session responses for " + sshObj.server.host + ":\n\n" + sessionText
|
|
405
|
-
});
|
|
406
|
-
this.emit("msg", "Sending session response email");
|
|
407
|
-
|
|
408
|
-
// if callback is provided, errors will be passed into it
|
|
409
|
-
// else errors will be thrown
|
|
410
|
-
sessionEmail.send(function(err){ sshObj.msg.send('error', err, 'Email'); });
|
|
411
|
-
}
|
|
262
|
+
"ls -l"
|
|
263
|
+
]
|
|
412
264
|
};
|
|
413
265
|
|
|
414
|
-
|
|
415
|
-
//Create a new instance
|
|
416
266
|
var SSH2Shell = require ('ssh2shell'),
|
|
417
267
|
SSH = new SSH2Shell(host);
|
|
418
268
|
|
|
419
|
-
//Start the process
|
|
420
|
-
SSH.connect();
|
|
421
|
-
|
|
422
|
-
or
|
|
423
|
-
//use a callback
|
|
424
269
|
var callback = function (sessionText){
|
|
425
270
|
console.log (sessionText);
|
|
426
271
|
}
|
|
272
|
+
|
|
427
273
|
SSH.connect(callback);
|
|
428
274
|
```
|
|
429
275
|
|
|
430
|
-
Tunnelling nested host objects:
|
|
431
|
-
---------------------------------
|
|
432
|
-
SSH tunnelling has been incorporated into core of the class process enabling nested host objects.
|
|
433
|
-
The new `hosts: [ host1, host2]` setting can make multiple host connections possible.
|
|
434
|
-
Each host config object has its own server settings, commands, command handlers and
|
|
435
|
-
event handlers. Each host object also has its own hosts array that other host objects can be added to.
|
|
436
|
-
This provides for different host connection sequences to any depth of recursion.
|
|
437
276
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
277
|
+
Authentication:
|
|
278
|
+
---------------
|
|
279
|
+
* Each host authenticates with its own host.server parameters.
|
|
280
|
+
* When using key authentication you may require a valid pass phrase if your key was created with one.
|
|
281
|
+
* When using fingerprint validation both host.server.hashMethod property and host.server.hostVerifier function must be
|
|
282
|
+
set.
|
|
283
|
+
* When using keyboard-interactive authentication both host.server.tryKeyboard and instance.on ("keayboard-interactive",
|
|
284
|
+
function...) or host.onKeyboardInteractive() must be defined.
|
|
285
|
+
* Set the default cyphers and keys.
|
|
443
286
|
|
|
444
|
-
|
|
287
|
+
Default Cyphers:
|
|
288
|
+
---------------
|
|
289
|
+
Default Cyphers and Keys used in the initial ssh connection can be redefined by setting the ssh2.connect.algorithms through the host.server.algorithms option.
|
|
290
|
+
As with this property all ssh2.connect properties are set in the host.server object.
|
|
291
|
+
|
|
292
|
+
*Example:*
|
|
293
|
+
```javascript
|
|
445
294
|
var host = {
|
|
446
|
-
server:
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
295
|
+
server: {
|
|
296
|
+
host: "<host IP>",
|
|
297
|
+
port: "22",
|
|
298
|
+
userName: "<username>",
|
|
299
|
+
password: "<password>",
|
|
300
|
+
hashMethod: "md5", //optional "md5" or "sha1" default is "md5"
|
|
301
|
+
//other ssh2.connect options
|
|
302
|
+
algorithms: {
|
|
303
|
+
kex: [
|
|
304
|
+
'diffie-hellman-group1-sha1',
|
|
305
|
+
'ecdh-sha2-nistp256',
|
|
306
|
+
'ecdh-sha2-nistp384',
|
|
307
|
+
'ecdh-sha2-nistp521',
|
|
308
|
+
'diffie-hellman-group-exchange-sha256',
|
|
309
|
+
'diffie-hellman-group14-sha1'],
|
|
310
|
+
cipher: [
|
|
311
|
+
'aes128-ctr',
|
|
312
|
+
'aes192-ctr',
|
|
313
|
+
'aes256-ctr',
|
|
314
|
+
'aes128-gcm',
|
|
315
|
+
'aes128-gcm@openssh.com',
|
|
316
|
+
'aes256-gcm',
|
|
317
|
+
'aes256-gcm@openssh.com',
|
|
318
|
+
'aes256-cbc'
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
},
|
|
323
|
+
......
|
|
463
324
|
}
|
|
464
325
|
```
|
|
465
326
|
|
|
327
|
+
Fingerprint Validation:
|
|
328
|
+
---------------
|
|
329
|
+
At connection time the hash of the server’s public key can be compared with the hash the client had previously recorded
|
|
330
|
+
for that server. This stops "man in the middle" attacks where you are redirected to a different server as you connect
|
|
331
|
+
to the server you expected to. This hash only changes with a reinstall of SSH, a key change on the server or a load
|
|
332
|
+
balancer is now in place.
|
|
466
333
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
to
|
|
470
|
-
|
|
471
|
-
```
|
|
472
|
-
server1.hosts = [server2, server3]
|
|
473
|
-
server2.hosts = []
|
|
474
|
-
server3.hosts = []
|
|
475
|
-
```
|
|
476
|
-
*The following would also be valid:*
|
|
477
|
-
```
|
|
478
|
-
server1.hosts = [server2]
|
|
479
|
-
server2.hosts = [server3]
|
|
480
|
-
server3.hosts = []
|
|
481
|
-
```
|
|
334
|
+
__*Note:*
|
|
335
|
+
Fingerprint check doesn't work the same way for tunnelling. The first host will validate using this method but the
|
|
336
|
+
subsequent connections would have to be handled by your commands. Only the first host uses the SSH2 connection method
|
|
337
|
+
that does the validation.__
|
|
482
338
|
|
|
483
|
-
|
|
339
|
+
To use fingerprint validation you first need the server hash string which can be obtained using ssh2shell as follows:
|
|
340
|
+
* Set host.verbose to true then set host.server.hashKey to any non-empty string (say "1234").
|
|
341
|
+
* Validation will be checked and fail causing the connection to terminate.
|
|
342
|
+
* A verbose message will return both the server hash and client hash values that failed comparison.
|
|
343
|
+
* This is also what will happen if your hash fails the comparison with the server in the normal verification process.
|
|
344
|
+
* Turn on verbose in the host object, run your script with hashKey unset and check the very start of the text returned
|
|
345
|
+
for the servers hash value.
|
|
346
|
+
* The servers hash value can be saved to a variable outside the host or class so you can access it without having to
|
|
347
|
+
parse response text.
|
|
484
348
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
7. Server1's host object is stored again and server3's host object is loaded as the current host.
|
|
494
|
-
8. Server3 is connected to and it completes its process.
|
|
495
|
-
9. Server3.hosts is checked and with no hosts found the connection is closed and the end event is triggered.
|
|
496
|
-
9. Server1 is loaded for the last time.
|
|
497
|
-
10. The session text for each connection is appended to the session text for the primary host.
|
|
498
|
-
11. With no further hosts to load the connection is closed triggering an end event for the last time.
|
|
499
|
-
12. As all sessions are closed the process ends.
|
|
349
|
+
*Example:*
|
|
350
|
+
```javascript
|
|
351
|
+
//Define the hostValidation function in the host.server config.
|
|
352
|
+
//hashKey needs to be defined at the top level if you want to access the server hash at run time
|
|
353
|
+
var serverHash, host;
|
|
354
|
+
//don't set expectedHash if you want to know the server hash
|
|
355
|
+
var expectedHash
|
|
356
|
+
expectedHash = "85:19:8a:fb:60:4b:94:13:5c:ea:fe:3b:99:c7:a5:4e";
|
|
500
357
|
|
|
358
|
+
host = {
|
|
359
|
+
server: {
|
|
360
|
+
//other normal connection params,
|
|
361
|
+
hashMethod: "md5", //"md5" or "sha1"
|
|
362
|
+
//hostVerifier function must be defined and return true for match of false for failure.
|
|
363
|
+
hostVerifier: function(hashedKey) {
|
|
364
|
+
var recievedHash;
|
|
365
|
+
|
|
366
|
+
expectedHash = expectedHash + "".replace(/[:]/g, "").toLowerCase();
|
|
367
|
+
recievedHash = hashedKey + "".replace(/[:]/g, "").toLowerCase();
|
|
368
|
+
if (expectedHash === "") {
|
|
369
|
+
//No expected hash so save save what was received from the host (hashedKey)
|
|
370
|
+
//serverHash needs to be defined before host object
|
|
371
|
+
serverHash = hashedKey;
|
|
372
|
+
console.log("Server hash: " + serverHash);
|
|
373
|
+
return true;
|
|
374
|
+
} else if (recievedHash === expectedHash) {
|
|
375
|
+
console.log("Hash values matched");
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
//Output the failed comparison to the console if you want to see what went wrong
|
|
379
|
+
console.log("Hash values: Server = " + recievedHash + " <> Client = " + expectedHash);
|
|
380
|
+
return false;
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
//Other settings
|
|
384
|
+
};
|
|
501
385
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
386
|
+
var SSH2Shell = require ('ssh2shell'),
|
|
387
|
+
SSH = new SSH2Shell(host);
|
|
388
|
+
SSH.connect();
|
|
389
|
+
```
|
|
390
|
+
__*Note:*
|
|
391
|
+
host.server.hashMethod only supports md5 or sha1 according to the current SSH2 documentation anything else may produce
|
|
392
|
+
undesired results.__
|
|
506
393
|
|
|
507
394
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
* Sharing duplicate functions between host objects
|
|
513
|
-
* What host object attributes you can leave out of primary and secondary host objects
|
|
514
|
-
* Unique event handlers set in host objects, common event handler set on class instance
|
|
395
|
+
Keyboard-interactive
|
|
396
|
+
----------------------
|
|
397
|
+
Keyboard-interactive authentication is available when both host.server.tryKeyboard is set to true and the event handler
|
|
398
|
+
keyboard-interactive is defined as below. The keyboard-interactive event handler can only be used on the first connection.
|
|
515
399
|
|
|
516
|
-
|
|
517
|
-
* Change debug to true to see the full process for each host.
|
|
400
|
+
Also see [test/keyboard-interactivetest.js](https://github.com/cmp-202/ssh2shell/blob/master/test/keyboard-interactivetest.js) for the full example
|
|
518
401
|
|
|
402
|
+
*Defining the event handler:*
|
|
519
403
|
```javascript
|
|
520
|
-
|
|
521
|
-
|
|
404
|
+
//this is required
|
|
405
|
+
host.server.tryKeyboard = true;
|
|
522
406
|
|
|
523
|
-
|
|
524
|
-
var
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
407
|
+
var SSH2Shell = require ('../lib/ssh2shell');
|
|
408
|
+
var SSH = new SSH2Shell(host);
|
|
409
|
+
|
|
410
|
+
//Add the keyboard-interactive handler
|
|
411
|
+
//The event function must call finish() with an array of responses in the same order as prompts received
|
|
412
|
+
// in the prompts array
|
|
413
|
+
SSH.on ('keyboard-interactive', function(name, instructions, instructionsLang, prompts, finish){
|
|
414
|
+
if (this.sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Keyboard-interactive");}
|
|
415
|
+
if (this.sshObj.verbose){
|
|
416
|
+
this.emit('msg', "name: " + name);
|
|
417
|
+
this.emit('msg', "instructions: " + instructions);
|
|
418
|
+
var str = JSON.stringify(prompts, null, 4);
|
|
419
|
+
this.emit('msg', "Prompts object: " + str);
|
|
420
|
+
}
|
|
421
|
+
//The example presumes only the password is required
|
|
422
|
+
finish([this.sshObj.server.password] );
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
SSH.connect();
|
|
426
|
+
```
|
|
545
427
|
|
|
546
|
-
|
|
547
|
-
var host1 = {
|
|
548
|
-
server: conParamsHost1,
|
|
549
|
-
commands: [
|
|
550
|
-
"msg:connected to host: passed. Listing dir.",
|
|
551
|
-
"ls -l"
|
|
552
|
-
],
|
|
553
|
-
debug: false,
|
|
554
|
-
onCommandComplete: function( command, response, sshObj ) {
|
|
555
|
-
//we are listing the dir so output it to the msg handler
|
|
556
|
-
if(sshObj.debug){
|
|
557
|
-
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host1, command: " + command);
|
|
558
|
-
}
|
|
559
|
-
},
|
|
560
|
-
onEnd: function( sessionText, sshObj ) {
|
|
561
|
-
//show the full session output for all hosts.
|
|
562
|
-
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": primary host.onEnd all sessiontText");}
|
|
563
|
-
this.emit ("msg", "\nAll Hosts SessiontText ---------------------------------------\n");
|
|
564
|
-
this.emit ("msg", sshObj.server.host + ":\n" + sessionText);
|
|
565
|
-
this.emit ("msg", "\nEnd sessiontText ---------------------------------------------\n");
|
|
566
|
-
})
|
|
567
|
-
},
|
|
568
|
-
|
|
569
|
-
host2 = {
|
|
570
|
-
server: conParamsHost2,
|
|
571
|
-
commands: [
|
|
572
|
-
"msg:connected to host: passed",
|
|
573
|
-
"msg:Changing to root dir",
|
|
574
|
-
"cd ~/",
|
|
575
|
-
"msg:Listing dir",
|
|
576
|
-
"ls -l"
|
|
577
|
-
],
|
|
578
|
-
debug: false,
|
|
579
|
-
connectedMessage: "Connected to host2",
|
|
580
|
-
onCommandComplete: function( command, response, sshObj ) {
|
|
581
|
-
//we are listing the dir so output it to the msg handler
|
|
582
|
-
if(sshObj.debug){
|
|
583
|
-
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host2, command: " + command);
|
|
584
|
-
}
|
|
585
|
-
if (command.indexOf("cd") != -1){
|
|
586
|
-
this.emit("msg", this.sshObj.server.host + ": Just ran a cd command:\n");
|
|
587
|
-
this.emit("msg", response);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
},
|
|
591
|
-
|
|
592
|
-
host3 = {
|
|
593
|
-
server: conParamsHost3,
|
|
594
|
-
commands: [
|
|
595
|
-
"msg:connected to host: passed",
|
|
596
|
-
"hostname"
|
|
597
|
-
],
|
|
598
|
-
debug: false,
|
|
599
|
-
connectedMessage: "Connected to host3",
|
|
600
|
-
onCommandComplete: function( command, response, sshObj) {
|
|
601
|
-
//we are listing the dir so output it to the msg handler
|
|
602
|
-
if(sshObj.debug){
|
|
603
|
-
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host3, command: " + command);
|
|
604
|
-
}
|
|
605
|
-
if (command.indexOf("cd") != -1){
|
|
606
|
-
this.emit("msg", this.sshObj.server.host + ": Just ran hostname command:\n");
|
|
607
|
-
this.emit("msg", response);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
//Set the two hosts you are tunnelling to through host1
|
|
613
|
-
host1.hosts = [ host2, host3 ];
|
|
614
|
-
|
|
615
|
-
//or the alternative nested tunnelling method outlined above:
|
|
616
|
-
//host2.hosts = [ host3 ];
|
|
617
|
-
//host1.hosts = [ host2 ];
|
|
618
|
-
|
|
619
|
-
//Create the new instance
|
|
620
|
-
var SSH2Shell = require ('ssh2shell'),
|
|
621
|
-
SSH = new SSH2Shell(host1);
|
|
428
|
+
Or
|
|
622
429
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
430
|
+
```javascript
|
|
431
|
+
host = {
|
|
432
|
+
...,
|
|
433
|
+
onKeyboardInteractive: function(name, instructions, instructionsLang, prompts, finish){
|
|
434
|
+
if (this.sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Keyboard-interactive");}
|
|
435
|
+
if (this.sshObj.verbose){
|
|
436
|
+
this.emit('msg', "name: " + name);
|
|
437
|
+
this.emit('msg', "instructions: " + instructions);
|
|
438
|
+
var str = JSON.stringify(prompts, null, 4);
|
|
439
|
+
this.emit('msg', "Prompts object: " + str);
|
|
440
|
+
}
|
|
441
|
+
//The example presumes only the password is required
|
|
442
|
+
finish([this.sshObj.server.password] );
|
|
443
|
+
},
|
|
444
|
+
...
|
|
632
445
|
}
|
|
633
|
-
|
|
634
|
-
//Add an on end event handler used by all hosts
|
|
635
|
-
SSH.on ('end', function( sessionText, sshObj ) {
|
|
636
|
-
//show the full session output. This could be emailed or saved to a log file.
|
|
637
|
-
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": instanse.onEnd all hosts");}
|
|
638
|
-
|
|
639
|
-
this.emit ("msg", "\nSessiontText -------------------------------------------------\n");
|
|
640
|
-
this.emit ("msg", sshObj.server.host + ":\n" + sessionText);
|
|
641
|
-
this.emit ("msg", "\nEnd sessiontText ---------------------------------------------\n");
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
//Start the process
|
|
645
|
-
SSH.connect();
|
|
646
|
-
|
|
647
446
|
```
|
|
648
447
|
|
|
649
448
|
|
|
@@ -670,33 +469,113 @@ Trouble shooting:
|
|
|
670
469
|
Verbose and Debug:
|
|
671
470
|
------------------
|
|
672
471
|
* When host.verbose is set to true each command complete raises a msg event outputting the response text.
|
|
673
|
-
* When host.debug is set to true each process step raises a msg event to help identify what the internal
|
|
674
|
-
each step was.
|
|
472
|
+
* When host.debug is set to true each process step raises a msg event to help identify what the internal processes were.
|
|
675
473
|
|
|
676
474
|
__*Note:*
|
|
677
|
-
Do not add
|
|
475
|
+
Do not add debug or verbose to the onCommandProccessing event which is called every time a response is received from the host.__
|
|
678
476
|
|
|
679
477
|
Add your own verbose messages as follows:
|
|
680
|
-
`if(this.sshObj.verbose){this.emit("msg", this.sshObj.server.host + ": response: " + response);}
|
|
681
|
-
to be changed to this._buffer
|
|
478
|
+
`if(this.sshObj.verbose){this.emit("msg", this.sshObj.server.host + ": response: " + response);} //response might need to be changed to this._buffer`
|
|
682
479
|
|
|
683
480
|
Add your own debug messages as follows:
|
|
684
|
-
`if(this.sshObj.debug){this.emit("msg", this.sshObj.server.host + ": eventName");}
|
|
685
|
-
|
|
481
|
+
`if(this.sshObj.debug){this.emit("msg", this.sshObj.server.host + ": eventName");} //where eventName is the text identifying what happened`
|
|
482
|
+
|
|
483
|
+
Notification commands:
|
|
484
|
+
----------------------
|
|
485
|
+
There are two notification commands that are added to the host.commands array but are not run as a command on the host.
|
|
486
|
+
|
|
487
|
+
1. `"msg:This is a message"`. outputs to the console.
|
|
488
|
+
2. "\`SessionText notification\`" will take the text between "\` \`" and add it to the sessionText only.
|
|
489
|
+
* The reason for not using echo or printf commands as a normal command is that you see both the command and the message in the sessionText which is pointless when all you want is the message.
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
Sudo and su Commands:
|
|
493
|
+
--------------
|
|
494
|
+
It is possible to use `sudo [command]`, `sudo su`, `su [username]` and `sudo -u [username] -i`.
|
|
495
|
+
Sudo commands uses the password for the current session and is processed by ssh2shell. Su on the other hand uses the password
|
|
496
|
+
of root or an other user (`su seconduser`) and requires you detect the password prompt in `host.onCommandProcessing` and run `this.runCommand(password)`.
|
|
497
|
+
|
|
498
|
+
See: [su VS sudo su VS sudo -u -i](http://johnkpaul.tumblr.com/post/19841381351/su-vs-sudo-su-vs-sudo-u-i) for
|
|
499
|
+
clarification about the difference between the commands.
|
|
500
|
+
|
|
501
|
+
See: [test/sudosutest.js](https://github.com/cmp-202/ssh2shell/blob/master/test/sudosutest.js) for a working code example.
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
Prompt detection override:
|
|
505
|
+
-------------------------
|
|
506
|
+
Used to detect different prompts.
|
|
507
|
+
|
|
508
|
+
These are optional settings.
|
|
509
|
+
```
|
|
510
|
+
host.standardPrompt = ">$%#";
|
|
511
|
+
host.passwordPrompt = ":";
|
|
512
|
+
host.passphrasePrompt = ":";
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
Text regular expression filters:
|
|
517
|
+
-------------------------------
|
|
518
|
+
There are two regular expression filters that remove unwanted text from response data.
|
|
519
|
+
|
|
520
|
+
The first removes non-standard ascii and the second removes ANSI text formatting codes. Both of these can be modified in
|
|
521
|
+
your host object to override defaults. It is also possible to output the ANSI codes by setting disableColorFilter to true.
|
|
522
|
+
|
|
523
|
+
Defaults:
|
|
524
|
+
```javascript
|
|
525
|
+
host.asciiFilter = "[^\r\n\x20-\x7e]"
|
|
526
|
+
host.disableColorFilter = false //or true
|
|
527
|
+
host.textColorFilter = "(\[{1}[0-9;]+m{1})"
|
|
528
|
+
```
|
|
686
529
|
|
|
530
|
+
|
|
531
|
+
Responding to non-standard command prompts:
|
|
532
|
+
----------------------
|
|
533
|
+
In host.onCommandProcessing event handler the prompt can be detected and a response can be sent via `stream.write("y\n")`.
|
|
534
|
+
|
|
535
|
+
`host.onCommandProcessing` definition replaces the default handler and runs only for the current host connection
|
|
536
|
+
```javascript
|
|
537
|
+
host.onCommandProcessing = function( command, response, sshObj, stream ) {
|
|
538
|
+
//Check the command and prompt exits and respond with a 'y' but only does it once
|
|
539
|
+
if (command == "apt-get install nano" && response.indexOf("[y/N]?") != -1 && sshObj.firstRun != true) {
|
|
540
|
+
//This debug message will only run when conditions are met not on every data event so is ok here
|
|
541
|
+
sshObj.firstRun = true
|
|
542
|
+
stream.write('y\n');
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
\\sshObj.firstRun could be reset to false in host.onCommandComplete to allow for detect another non-standard prompt
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Instance definition that runs in parallel with every other commandProcessing for every host connection
|
|
549
|
+
To handle all hosts the same add an event handler to the class instance
|
|
550
|
+
Don't define an event handler in the host object with the same code, it will do it twice!
|
|
551
|
+
```javascript
|
|
552
|
+
var SSH2Shell = require ('ssh2shell');
|
|
553
|
+
var SSH = new SSH2Shell(host);
|
|
687
554
|
|
|
688
|
-
|
|
555
|
+
SSH.on ('commandProcessing', function onCommandProcessing( command, response, sshObj, stream ) {
|
|
556
|
+
|
|
557
|
+
//Check the command and prompt exits and respond with a 'y'
|
|
558
|
+
if (command == "apt-get install nano" && response.indexOf("[y/N]?") != -1 && sshObj.firstRun != true ) {
|
|
559
|
+
//This debug message will only run when conditions are met not on every data event so is ok here
|
|
560
|
+
if (sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Responding to nano install");}
|
|
561
|
+
sshObj.firstRun = true
|
|
562
|
+
stream.write('y\n');
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
__*Note:*
|
|
568
|
+
If no prompt is detected `commandTimeout` will be raised after the idleTimeOut period.__
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
Command Time-out event
|
|
689
572
|
---------------
|
|
690
|
-
When
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
the
|
|
696
|
-
enabling the process to continue. It is recommended to close the connection as a default action if all else fails so you
|
|
697
|
-
are not left with a hanging script again. The default action is to add the last response text to the session text and
|
|
698
|
-
disconnect. Enabling host.debug would also provide the process path leading up to disconnection which in conjunction with
|
|
699
|
-
the session text would clarify what command and output triggered the event.
|
|
573
|
+
When onCommandTimeout event triggers after the host.idleTimeOut is exceeded.
|
|
574
|
+
This is because there is no new data and no prompt is detected to run the next command.
|
|
575
|
+
It is recommended to close the connection as a default action if all else fails so you are not left with a hanging script.
|
|
576
|
+
The default action is to add the last response text to the session text and disconnect.
|
|
577
|
+
Enabling host.debug would also provide the process path leading up to disconnection which in conjunction with
|
|
578
|
+
the session text would clarify what command and output triggered the timeout event.
|
|
700
579
|
|
|
701
580
|
**Note: If you receive garble back before the clear response you may need to save the previous response text to the
|
|
702
581
|
sessionText and clear the buffer before using stream.write() in commandTimeout.
|
|
@@ -705,7 +584,6 @@ the session text would clarify what command and output triggered the event.
|
|
|
705
584
|
|
|
706
585
|
```javascript
|
|
707
586
|
host.onCommandTimeout = function( command, response, stream, connection ) {
|
|
708
|
-
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": host.onCommandTimeout");}
|
|
709
587
|
if (command === "atp-get install node"
|
|
710
588
|
&& response.indexOf("[Y/n]?") != -1
|
|
711
589
|
&& this.sshObj.nodePrompt != true) {
|
|
@@ -722,27 +600,11 @@ host.onCommandTimeout = function( command, response, stream, connection ) {
|
|
|
722
600
|
})
|
|
723
601
|
}
|
|
724
602
|
}
|
|
603
|
+
```
|
|
725
604
|
|
|
726
605
|
or
|
|
727
606
|
|
|
728
|
-
|
|
729
|
-
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": host.onCommandTimeout");}
|
|
730
|
-
if (command === "" && response === "Connected to port 22" && this.sshObj.noFirstPrompt != true) {
|
|
731
|
-
//Setting this.sshObj.noFirstPrompt stops a response loop
|
|
732
|
-
this.sshObj.noFirstPrompt = true
|
|
733
|
-
stream.write('\n')
|
|
734
|
-
return true
|
|
735
|
-
}
|
|
736
|
-
//emit an error that passes true for the close parameter and callback the loads the last of session text
|
|
737
|
-
this.emit ("error", this.sshObj.server.host
|
|
738
|
-
+ ": Command timed out after #{this._idleTime/1000} seconds. command: "
|
|
739
|
-
+ command, "Timeout", true, function(err, type){
|
|
740
|
-
this.sshObj.sessionText += response
|
|
741
|
-
})
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
or
|
|
745
|
-
|
|
607
|
+
```javascript
|
|
746
608
|
//reset the default handler to do nothing so it doesn't close the connection
|
|
747
609
|
host.onCommandTimeout = function( command, response, stream, connection ) {};
|
|
748
610
|
|
|
@@ -752,323 +614,307 @@ var SSH2Shell = require ('ssh2shell'),
|
|
|
752
614
|
|
|
753
615
|
//And do it all in the instance event handler
|
|
754
616
|
SSH.on ('commandTimeout',function( command, response, stream, connection ){
|
|
755
|
-
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": instance.onCommandTimeout");}
|
|
756
617
|
//first test should only pass once to stop a response loop
|
|
757
618
|
if (command === "atp-get install node"
|
|
758
619
|
&& response.indexOf("[Y/n]?") != -1
|
|
759
620
|
&& this.sshObj.nodePrompt != true) {
|
|
760
621
|
this.sshObj.nodePrompt = true;
|
|
761
622
|
stream.write('y\n');
|
|
623
|
+
|
|
762
624
|
return true;
|
|
763
625
|
}
|
|
764
626
|
this.sshObj.sessionText += response;
|
|
627
|
+
//emit an error that closes the connection
|
|
765
628
|
this.emit("error", this.sshObj.server.host + ": Command \`" + command + "\` timed out after "
|
|
766
629
|
+ (this._idleTime / 1000) + " seconds. command: " + command, "Command Timeout", true);
|
|
767
630
|
});
|
|
768
631
|
|
|
769
632
|
SSH.on ('end', function( sessionText, sshObj ) {
|
|
770
|
-
//show the full session output. This could be emailed or saved to a log file.
|
|
771
|
-
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": instance.onEnd");}
|
|
772
633
|
this.emit("msg","\nSession text for " + sshObj.server.host + ":\n" + sessionText);
|
|
773
634
|
});
|
|
774
635
|
|
|
775
636
|
SSH.connect();
|
|
776
637
|
```
|
|
777
638
|
|
|
778
|
-
Authentication:
|
|
779
|
-
---------------
|
|
780
|
-
* Each host authenticates with its own host.server parameters.
|
|
781
|
-
* When using key authentication you may require a valid pass phrase if your key was created with one.
|
|
782
|
-
* When using fingerprint validation both host.server.hashMethod property and host.server.hostVerifier function must be
|
|
783
|
-
set.
|
|
784
|
-
* When using keyboard-interactive authentication both host.server.tryKeyboard and instance.on ("keayboard-interactive",
|
|
785
|
-
function...) or host.onKeyboardInteractive() must be defined.
|
|
786
|
-
* Set the default cyphers and keys.
|
|
787
639
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
640
|
+
Multiple Primary Hosts:
|
|
641
|
+
-----------------------
|
|
642
|
+
Multiple hosts can be connected to one after the other as you would connecting to a single host.
|
|
643
|
+
Each host config is independant of each other but can share thingsa like commands and handlers.
|
|
644
|
+
Each host config is added to an array of hosts that is passed to the constructor.
|
|
645
|
+
The final `sessionText` contains the output from all the hosts.
|
|
792
646
|
|
|
793
|
-
*Example:*
|
|
794
647
|
```javascript
|
|
795
|
-
var
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
userName: "<username>",
|
|
800
|
-
password: "<password>",
|
|
801
|
-
hashMethod: "md5", //optional "md5" or "sha1" default is "md5"
|
|
802
|
-
//other ssh2.connect options
|
|
803
|
-
algorithms: {
|
|
804
|
-
kex: [
|
|
805
|
-
'diffie-hellman-group1-sha1',
|
|
806
|
-
'ecdh-sha2-nistp256',
|
|
807
|
-
'ecdh-sha2-nistp384',
|
|
808
|
-
'ecdh-sha2-nistp521',
|
|
809
|
-
'diffie-hellman-group-exchange-sha256',
|
|
810
|
-
'diffie-hellman-group14-sha1'],
|
|
811
|
-
cipher: [
|
|
812
|
-
'aes128-ctr',
|
|
813
|
-
'aes192-ctr',
|
|
814
|
-
'aes256-ctr',
|
|
815
|
-
'aes128-gcm',
|
|
816
|
-
'aes128-gcm@openssh.com',
|
|
817
|
-
'aes256-gcm',
|
|
818
|
-
'aes256-gcm@openssh.com',
|
|
819
|
-
'aes256-cbc'
|
|
820
|
-
]
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
},
|
|
824
|
-
......
|
|
825
|
-
}
|
|
826
|
-
```
|
|
827
|
-
|
|
828
|
-
Fingerprint Validation:
|
|
829
|
-
---------------
|
|
830
|
-
At connection time the hash of the server’s public key can be compared with the hash the client had previously recorded
|
|
831
|
-
for that server. This stops "man in the middle" attacks where you are redirected to a different server as you connect
|
|
832
|
-
to the server you expected to. This hash only changes with a reinstall of SSH, a key change on the server or a load
|
|
833
|
-
balancer is now in place.
|
|
834
|
-
|
|
835
|
-
__*Note:*
|
|
836
|
-
Fingerprint check doesn't work the same way for tunnelling. The first host will validate using this method but the
|
|
837
|
-
subsequent connections would have to be handled by your commands. Only the first host uses the SSH2 connection method
|
|
838
|
-
that does the validation.__
|
|
648
|
+
var dotenv = require('dotenv').config(),
|
|
649
|
+
debug = false,
|
|
650
|
+
verbose = false
|
|
651
|
+
|
|
839
652
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
653
|
+
//Host objects:
|
|
654
|
+
var host1 = {
|
|
655
|
+
server: {
|
|
656
|
+
host: process.env.HOST,
|
|
657
|
+
port: process.env.PORT,
|
|
658
|
+
userName: process.env.USER_NAME,
|
|
659
|
+
password: process.env.PASSWORD
|
|
660
|
+
},
|
|
661
|
+
commands: ["echo Host_1"],
|
|
662
|
+
connectedMessage: "Connected to host1",
|
|
663
|
+
debug: debug,
|
|
664
|
+
verbose: verbose
|
|
665
|
+
},
|
|
849
666
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
667
|
+
host2 = {
|
|
668
|
+
server: {
|
|
669
|
+
host: process.env.SERVER2_HOST,
|
|
670
|
+
port: process.env.PORT,
|
|
671
|
+
userName: process.env.SERVER2_USER_NAME,
|
|
672
|
+
password: process.env.SERVER2_PASSWORD
|
|
673
|
+
},
|
|
674
|
+
commands: ["echo Host_2"],
|
|
675
|
+
connectedMessage: "Connected to host2",
|
|
676
|
+
debug: debug,
|
|
677
|
+
verbose: verbose
|
|
678
|
+
},
|
|
858
679
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
} else if (recievedHash === expectedHash) {
|
|
876
|
-
console.log("Hash values matched");
|
|
877
|
-
return true;
|
|
878
|
-
}
|
|
879
|
-
//Output the failed comparison to the console if you want to see what went wrong
|
|
880
|
-
console.log("Hash values: Server = " + recievedHash + " <> Client = " + expectedHash);
|
|
881
|
-
return false;
|
|
882
|
-
},
|
|
883
|
-
},
|
|
884
|
-
//Other settings
|
|
885
|
-
};
|
|
680
|
+
host3 = {
|
|
681
|
+
server: {
|
|
682
|
+
host: process.env.SERVER3_HOST,
|
|
683
|
+
port: process.env.PORT,
|
|
684
|
+
userName: process.env.SERVER3_USER_NAME,
|
|
685
|
+
password: process.env.SERVER3_PASSWORD
|
|
686
|
+
},
|
|
687
|
+
commands: ["echo Host_3"],
|
|
688
|
+
connectedMessage: "Connected to host3",
|
|
689
|
+
debug: debug,
|
|
690
|
+
verbose: verbose,
|
|
691
|
+
//Event handler only used by this host
|
|
692
|
+
onCommandComplete: function( command, response, sshObj ) {
|
|
693
|
+
this.emit("msg", sshObj.server.host + ": commandComplete only used on this host");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
886
696
|
|
|
887
697
|
var SSH2Shell = require ('ssh2shell'),
|
|
888
|
-
SSH
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
undesired results.__
|
|
698
|
+
SSH = new SSH2Shell([host1,host2,host3]),
|
|
699
|
+
callback = function( sessionText ){
|
|
700
|
+
console.log ( "-----Callback session text:\n" + sessionText);
|
|
701
|
+
console.log ( "-----Callback end" );
|
|
702
|
+
}
|
|
894
703
|
|
|
704
|
+
SSH.on ('end', function( sessionText, sshObj ) {
|
|
705
|
+
this.emit("msg", sshObj.server.host + ": onEnd every host");
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
SSH.connect(callback);
|
|
709
|
+
```
|
|
895
710
|
|
|
896
|
-
Keyboard-interactive
|
|
897
|
-
----------------------
|
|
898
|
-
Keyboard-interactive authentication is available when both host.server.tryKeyboard is set to true and the event handler
|
|
899
|
-
keyboard-interactive is defined as below. The keyboard-interactive event handler can only be used on the first connection.
|
|
900
711
|
|
|
901
|
-
|
|
712
|
+
Tunnelling nested host objects:
|
|
713
|
+
---------------------------------
|
|
714
|
+
`hosts: [ host1, host2]` setting can make multiple host connections possible.
|
|
715
|
+
Each host config object has its own server settings, commands, command handlers and
|
|
716
|
+
event handlers. Each host object also has its own hosts array that other host objects can be added to.
|
|
717
|
+
This provides for different host connection sequences to multiple depth of connections.
|
|
902
718
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
host.server.
|
|
719
|
+
**SSH connections:**
|
|
720
|
+
Once the primary host connection is made all other connections are made using an ssh command from the
|
|
721
|
+
current host to the next. A number of ssh command options are set using an optional `host.server.ssh` object.
|
|
722
|
+
`host.server.ssh.options` allows setting any ssh config options.
|
|
907
723
|
|
|
908
|
-
var SSH2Shell = require ('../lib/ssh2shell');
|
|
909
|
-
var SSH = new SSH2Shell(host);
|
|
910
|
-
|
|
911
|
-
//Add the keyboard-interactive handler
|
|
912
|
-
//The event function must call finish() with an array of responses in the same order as prompts received
|
|
913
|
-
// in the prompts array
|
|
914
|
-
SSH.on ('keyboard-interactive', function(name, instructions, instructionsLang, prompts, finish){
|
|
915
|
-
if (this.sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Keyboard-interactive");}
|
|
916
|
-
if (this.sshObj.verbose){
|
|
917
|
-
this.emit('msg', "name: " + name);
|
|
918
|
-
this.emit('msg', "instructions: " + instructions);
|
|
919
|
-
var str = JSON.stringify(prompts, null, 4);
|
|
920
|
-
this.emit('msg', "Prompts object: " + str);
|
|
921
|
-
}
|
|
922
|
-
//The example presumes only the password is required
|
|
923
|
-
finish([this.sshObj.server.password] );
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
SSH.connect();
|
|
927
724
|
```
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
725
|
+
var host = {
|
|
726
|
+
server: { ...,
|
|
727
|
+
ssh: {
|
|
728
|
+
forceProtocolVersion: true/false,
|
|
729
|
+
forceAddressType: true/false,
|
|
730
|
+
disablePseudoTTY: true/false,
|
|
731
|
+
forcePseudoTTY: true/false,
|
|
732
|
+
verbose: true/false,
|
|
733
|
+
cipherSpec: "",
|
|
734
|
+
escape: "",
|
|
735
|
+
logFile: "",
|
|
736
|
+
configFile: "",
|
|
737
|
+
identityFile: "",
|
|
738
|
+
loginName: "",
|
|
739
|
+
macSpec: "",
|
|
740
|
+
options: {}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
946
743
|
}
|
|
947
744
|
```
|
|
948
745
|
|
|
949
|
-
Sudo and su Commands:
|
|
950
|
-
--------------
|
|
951
|
-
It is possible to use `sudo [command]`, `sudo su`, `su [username]` and `sudo -u [username] -i`. Sudo commands uses the
|
|
952
|
-
password for the user that is accessing the server and is handled by SSH2shell. Su on the other hand uses the password
|
|
953
|
-
of root or the other user (`su seconduser`) and requires you detect the password prompt in onCommandProcessing.
|
|
954
746
|
|
|
955
|
-
|
|
956
|
-
|
|
747
|
+
**Tunnelling Example:**
|
|
748
|
+
This example shows two hosts (server2, server3) that are connected to via server1.
|
|
749
|
+
The two host configs are added to server1.hosts array.
|
|
957
750
|
|
|
958
|
-
|
|
751
|
+
```
|
|
752
|
+
server1.hosts = [server2, server3]
|
|
753
|
+
server2.hosts = []
|
|
754
|
+
server3.hosts = []
|
|
755
|
+
```
|
|
756
|
+
*The following would also be valid:*
|
|
757
|
+
```
|
|
758
|
+
server1.hosts = [server2]
|
|
759
|
+
server2.hosts = [server3]
|
|
760
|
+
server3.hosts = []
|
|
761
|
+
```
|
|
959
762
|
|
|
763
|
+
*The nested process:*
|
|
960
764
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
765
|
+
1. The primary host (server1) is connected and all its commands completed.
|
|
766
|
+
2. Server1.hosts array is checked for other hosts.
|
|
767
|
+
3. Server1 is stored for use later and server2's host object is loaded as the current host.
|
|
768
|
+
4. A connection to the next host is made using its server parameters via running the ssh command in the current session.
|
|
769
|
+
5. That hosts commands are completed and it's hosts array is checked for other hosts.
|
|
770
|
+
6. If no hosts are found the connection is closed triggering an end event for the current host if defined.
|
|
771
|
+
5. Server1 host object is reloaded as the current host object and server2 host object discarded.
|
|
772
|
+
6. Server1.hosts array is checked for other hosts and the next host popped off the array.
|
|
773
|
+
7. Server1's host object is restored and the process repeats for an all other hosts.
|
|
774
|
+
8. Server1 is loaded for the last time and all hosts sessionText's are appended to `this.callback(sessionText)`.
|
|
775
|
+
10. As all sessions are closed, the process ends.
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
**_Note:_**
|
|
779
|
+
* A host object needs to be defined before it is added to another host.hosts array.
|
|
780
|
+
* Only the primary host objects connected, ready and closed messages will be used by ssh2shell.
|
|
781
|
+
* Connection events only apply to the first host. Keyboard-interactive event handler is one of these.
|
|
964
782
|
|
|
965
|
-
1. `"msg:This is a message intended for monitoring the process as it runs"`. The `msg:` command raises a onMsg(message)
|
|
966
|
-
event.
|
|
967
|
-
* The text after `msg:` is passed to the message property of the onMsg event.
|
|
968
|
-
2. "\`SessionText notification\`" will take the text between "\` \`" and add it to the sessionText.
|
|
969
|
-
* The reason for not using echo or printf commands as a normal command is that you see both the command and the message
|
|
970
|
-
in the sessionText which is pointless when all you want is the message.
|
|
971
783
|
|
|
784
|
+
*How to:*
|
|
785
|
+
* Define nested hosts
|
|
786
|
+
* Use unique host connection settings for each host
|
|
787
|
+
* Defining different commands and command event handlers for each host
|
|
788
|
+
* Sharing duplicate functions between host objects
|
|
789
|
+
* What host object attributes you can leave out of primary and secondary host objects
|
|
790
|
+
* Unique event handlers set in host objects, common event handler set on class instance
|
|
972
791
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
The following properties have been added to the host object making it possible to override prompt string values used
|
|
976
|
-
with regular expressions to for prompt detection. Being able to change these values enables you to easily manage all
|
|
977
|
-
sorts of prompt options subject to you server prompts.
|
|
792
|
+
**_Note:_**
|
|
793
|
+
* Change debug to true to see the full process for each host.
|
|
978
794
|
|
|
979
|
-
These are optional settings.
|
|
980
|
-
```
|
|
981
|
-
host.standardPrompt = ">$%#";
|
|
982
|
-
host.passwordPrompt = ":";
|
|
983
|
-
host.passphrasePrompt = ":";
|
|
984
|
-
```
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
Text regular expression filters:
|
|
988
|
-
-------------------------------
|
|
989
|
-
There are two regular expression filters that remove unwanted text from response data.
|
|
990
|
-
|
|
991
|
-
The first removes non-standard ascii and the second removes ANSI text formatting codes. Both of these can be modified in
|
|
992
|
-
your host object to override defaults. It is also possible to output the ANSI codes by setting disableColorFilter to true.
|
|
993
|
-
|
|
994
|
-
These are optional settings
|
|
995
795
|
```javascript
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
host.textColorFilter = "(\[{1}[0-9;]+m{1})"
|
|
999
|
-
```
|
|
796
|
+
var dotenv = require('dotenv');
|
|
797
|
+
dotenv.load();
|
|
1000
798
|
|
|
799
|
+
//Host connection and authentication parameters
|
|
800
|
+
var conParamsHost1 = {
|
|
801
|
+
host: process.env.SERVER1_HOST,
|
|
802
|
+
port: process.env.SERVER1_PORT,
|
|
803
|
+
userName: process.env.SERVER1_USER_NAME,
|
|
804
|
+
password: process.env.SERVER1_PASSWORD,
|
|
805
|
+
passPhrase: process.env.SERVER1_PASS_PHRASE,
|
|
806
|
+
privateKey: require('fs').readFileSync(process.env.SERVER1_PRIV_KEY_PATH)
|
|
807
|
+
},
|
|
808
|
+
conParamsHost2 = {
|
|
809
|
+
host: process.env.SERVER2_HOST,
|
|
810
|
+
port: process.env.SERVER2_PORT,
|
|
811
|
+
userName: process.env.SERVER2_USER_NAME,
|
|
812
|
+
password: process.env.SERVER2_PASSWORD
|
|
813
|
+
},
|
|
814
|
+
conParamsHost3 = {
|
|
815
|
+
host: process.env.SERVER3_HOST,
|
|
816
|
+
port: process.env.SERVER3_PORT,
|
|
817
|
+
userName: process.env.SERVER3_USER_NAME,
|
|
818
|
+
password: process.env.SERVER3_PASSWORD
|
|
819
|
+
}
|
|
1001
820
|
|
|
1002
|
-
Responding to non-standard command prompts:
|
|
1003
|
-
----------------------
|
|
1004
|
-
When running commands there are cases that you might need to respond to specific prompt that results from the command
|
|
1005
|
-
being run. The command response check method is the same as in the example for the host.onCommandComplete event handler
|
|
1006
|
-
but in this case we use it in the host.onCommandProcessing event handler. The stream object is available in
|
|
1007
|
-
onCommandProcessing to the prompt directly using strea.write("y\n"), note "\n" might be required to complete the
|
|
1008
|
-
response.
|
|
1009
821
|
|
|
1010
|
-
Host
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
822
|
+
//Host objects:
|
|
823
|
+
var host1 = {
|
|
824
|
+
server: conParamsHost1,
|
|
825
|
+
commands: [
|
|
826
|
+
"msg:connected to host: passed. Listing dir.",
|
|
827
|
+
"ls -l"
|
|
828
|
+
],
|
|
829
|
+
debug: false,
|
|
830
|
+
onCommandComplete: function( command, response, sshObj ) {
|
|
831
|
+
//we are listing the dir so output it to the msg handler
|
|
832
|
+
if(sshObj.debug){
|
|
833
|
+
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host1, command: " + command);
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
onEnd: function( sessionText, sshObj ) {
|
|
837
|
+
//show the full session output for all hosts.
|
|
838
|
+
if(sshObj.debug){this.emit("msg", this.sshObj.server.host + ": primary host.onEnd all sessiontText");}
|
|
839
|
+
this.emit ("msg", "\nAll Hosts SessiontText ---------------------------------------\n");
|
|
840
|
+
this.emit ("msg", sshObj.server.host + ":\n" + sessionText);
|
|
841
|
+
this.emit ("msg", "\nEnd sessiontText ---------------------------------------------\n");
|
|
842
|
+
})
|
|
843
|
+
},
|
|
1023
844
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
845
|
+
host2 = {
|
|
846
|
+
server: conParamsHost2,
|
|
847
|
+
commands: [
|
|
848
|
+
"msg:connected to host: passed",
|
|
849
|
+
"msg:Changing to root dir",
|
|
850
|
+
"cd ~/",
|
|
851
|
+
"msg:Listing dir",
|
|
852
|
+
"ls -l"
|
|
853
|
+
],
|
|
854
|
+
debug: false,
|
|
855
|
+
connectedMessage: "Connected to host2",
|
|
856
|
+
onCommandComplete: function( command, response, sshObj ) {
|
|
857
|
+
//we are listing the dir so output it to the msg handler
|
|
858
|
+
if(sshObj.debug){
|
|
859
|
+
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host2, command: " + command);
|
|
860
|
+
}
|
|
861
|
+
if (command.indexOf("cd") != -1){
|
|
862
|
+
this.emit("msg", this.sshObj.server.host + ": Just ran a cd command:\n");
|
|
863
|
+
this.emit("msg", response);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
},
|
|
1030
867
|
|
|
1031
|
-
|
|
868
|
+
host3 = {
|
|
869
|
+
server: conParamsHost3,
|
|
870
|
+
commands: [
|
|
871
|
+
"msg:connected to host: passed",
|
|
872
|
+
"hostname"
|
|
873
|
+
],
|
|
874
|
+
debug: false,
|
|
875
|
+
connectedMessage: "Connected to host3",
|
|
876
|
+
onCommandComplete: function( command, response, sshObj) {
|
|
877
|
+
//we are listing the dir so output it to the msg handler
|
|
878
|
+
if(sshObj.debug){
|
|
879
|
+
this.emit("msg", this.sshObj.server.host + ": host.onCommandComplete host3, command: " + command);
|
|
880
|
+
}
|
|
881
|
+
if (command.indexOf("cd") != -1){
|
|
882
|
+
this.emit("msg", this.sshObj.server.host + ": Just ran hostname command:\n");
|
|
883
|
+
this.emit("msg", response);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
1032
887
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
//This debug message will only run when conditions are met not on every data event so is ok here
|
|
1036
|
-
if (sshObj.debug) {this.emit('msg', this.sshObj.server.host + ": Responding to nano install");}
|
|
1037
|
-
sshObj.firstRun = true
|
|
1038
|
-
stream.write('y\n');
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
```
|
|
888
|
+
//Set the two hosts you are tunnelling to through host1
|
|
889
|
+
host1.hosts = [ host2, host3 ];
|
|
1042
890
|
|
|
1043
|
-
|
|
1044
|
-
|
|
891
|
+
//or the alternative nested tunnelling method outlined above:
|
|
892
|
+
//host2.hosts = [ host3 ];
|
|
893
|
+
//host1.hosts = [ host2 ];
|
|
1045
894
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
or get the script on the fly. You can echo/printf the script content into a file as a command, ensure it is executable,
|
|
1050
|
-
run it and then delete it. The other option is to curl or wget the script from a remote location but
|
|
1051
|
-
this has some risks associated with it. I like to know what is in the script I am running.
|
|
895
|
+
//Create the new instance
|
|
896
|
+
var SSH2Shell = require ('ssh2shell'),
|
|
897
|
+
SSH = new SSH2Shell(host1);
|
|
1052
898
|
|
|
1053
|
-
|
|
899
|
+
//Add a commandComplete handler used by all hosts
|
|
900
|
+
SSH.on ('commandComplete', function onCommandComplete( command, response, sshObj ) {
|
|
901
|
+
//we are listing the dir so output it to the msg handler
|
|
902
|
+
if (command == "ls -l"){
|
|
903
|
+
this.emit("msg", this.sshObj.server.host + ":\n" + response);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
//Add an on end event handler used by all hosts
|
|
908
|
+
SSH.on ('end', function( sessionText, sshObj ) {
|
|
909
|
+
//show the full session output. This could be emailed or saved to a log file.
|
|
910
|
+
this.emit ("msg", "\nSessiontText -------------------------------------------------\n");
|
|
911
|
+
this.emit ("msg", sshObj.server.host + ":\n" + sessionText);
|
|
912
|
+
this.emit ("msg", "\nEnd sessiontText ---------------------------------------------\n");
|
|
913
|
+
});
|
|
1054
914
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
" #\n" +
|
|
1059
|
-
" current=$(pwd);\n" +
|
|
1060
|
-
"cd ..;\n" +
|
|
1061
|
-
"if [ -f myfile ]; then" +
|
|
1062
|
-
"sed \"/^[ \\t]*$/d\" ${current}/myfile | while read line; do\n" +
|
|
1063
|
-
"printf \"Doing some stuff\";\n" +
|
|
1064
|
-
"printf $line;\n" +
|
|
1065
|
-
"done\n" +
|
|
1066
|
-
"fi\n' > myscript.sh;" +
|
|
1067
|
-
"fi",
|
|
1068
|
-
"sudo chmod 700 myscript.sh",
|
|
1069
|
-
"./myscript.sh",
|
|
1070
|
-
"rm myscript.sh"
|
|
1071
|
-
],
|
|
915
|
+
//Start the process
|
|
916
|
+
SSH.connect();
|
|
917
|
+
|
|
1072
918
|
```
|
|
1073
919
|
|
|
1074
920
|
|
|
@@ -1086,12 +932,12 @@ There are two ways to add event handlers:
|
|
|
1086
932
|
* Instance variables and functions are available through `this` including the Emitter functions like
|
|
1087
933
|
this.emit("myEvent", properties).
|
|
1088
934
|
* Connect, ready, error and close events are not available for definition in the host object.
|
|
1089
|
-
* Defining a host event replaces the default event handler.
|
|
935
|
+
* Defining a host event handler replaces the default coded event handler.
|
|
1090
936
|
|
|
1091
937
|
2. Add an event handler, as defined below, to the class instance.
|
|
1092
938
|
* Handlers added to the class instance will be triggered every time the event is raised in parallel with any other
|
|
1093
939
|
handlers of the same name.
|
|
1094
|
-
* It will not replace the internal event handler of the class
|
|
940
|
+
* It will not replace the internal event handler of the class set by the class default or host definition.
|
|
1095
941
|
|
|
1096
942
|
An event can be raised using `this.emit('eventName', parameters)`.
|
|
1097
943
|
|
|
@@ -1191,3 +1037,31 @@ ssh2shell.on ("Unpipe", function onUnpipe(source){
|
|
|
1191
1037
|
//Source is the read stream to remove from outputting data
|
|
1192
1038
|
});
|
|
1193
1039
|
```
|
|
1040
|
+
|
|
1041
|
+
Bash scripts on the fly:
|
|
1042
|
+
------------------------
|
|
1043
|
+
If the commands you need to run would be better suited to a bash script as part of the process it is possible to generate
|
|
1044
|
+
or get the script on the fly. You can echo/printf the script content into a file as a command, ensure it is executable,
|
|
1045
|
+
run it and then delete it. The other option is to curl or wget the script from a remote location but
|
|
1046
|
+
this has some risks associated with it. I like to know what is in the script I am running.
|
|
1047
|
+
|
|
1048
|
+
**Note** # and > in the following commands with conflict with the host.standardPrompt definition ">$%#" change it to "$%"
|
|
1049
|
+
|
|
1050
|
+
```
|
|
1051
|
+
host.commands = [ "some commands here",
|
|
1052
|
+
"if [ ! -f myscript.sh ]; then printf '#!/bin/bash\n" +
|
|
1053
|
+
" #\n" +
|
|
1054
|
+
" current=$(pwd);\n" +
|
|
1055
|
+
"cd ..;\n" +
|
|
1056
|
+
"if [ -f myfile ]; then" +
|
|
1057
|
+
"sed \"/^[ \\t]*$/d\" ${current}/myfile | while read line; do\n" +
|
|
1058
|
+
"printf \"Doing some stuff\";\n" +
|
|
1059
|
+
"printf $line;\n" +
|
|
1060
|
+
"done\n" +
|
|
1061
|
+
"fi\n' > myscript.sh;" +
|
|
1062
|
+
"fi",
|
|
1063
|
+
"sudo chmod 700 myscript.sh",
|
|
1064
|
+
"./myscript.sh",
|
|
1065
|
+
"rm myscript.sh"
|
|
1066
|
+
],
|
|
1067
|
+
```
|