bdy 1.23.6-dev → 1.23.7-dev

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.
Files changed (205) hide show
  1. package/distTs/package.json +10 -2
  2. package/distTs/src/agent/linux.js +53 -3
  3. package/distTs/src/agent/manager.js +3 -6
  4. package/distTs/src/agent/osx.js +51 -3
  5. package/distTs/src/agent/system.js +400 -21
  6. package/distTs/src/agent/windows.js +4 -7
  7. package/distTs/src/command/agent/debug.js +2 -2
  8. package/distTs/src/command/agent/install.js +37 -10
  9. package/distTs/src/command/agent/restart.js +2 -2
  10. package/distTs/src/command/agent/run.js +3 -0
  11. package/distTs/src/command/agent/start.js +2 -2
  12. package/distTs/src/command/agent/status.js +19 -0
  13. package/distTs/src/command/agent/stop.js +2 -2
  14. package/distTs/src/command/agent/uninstall.js +5 -8
  15. package/distTs/src/command/agent/update.js +2 -2
  16. package/distTs/src/command/artifact/publish.js +3 -1
  17. package/distTs/src/command/version.js +13 -5
  18. package/distTs/src/logger.js +40 -4
  19. package/distTs/src/texts.js +35 -19
  20. package/distTs/src/utils.js +98 -3
  21. package/node_modules/asn1/Jenkinsfile +65 -0
  22. package/node_modules/asn1/LICENSE +19 -0
  23. package/node_modules/asn1/README.md +50 -0
  24. package/node_modules/asn1/lib/ber/errors.js +13 -0
  25. package/node_modules/asn1/lib/ber/index.js +27 -0
  26. package/node_modules/asn1/lib/ber/reader.js +262 -0
  27. package/node_modules/asn1/lib/ber/types.js +36 -0
  28. package/node_modules/asn1/lib/ber/writer.js +317 -0
  29. package/node_modules/asn1/lib/index.js +20 -0
  30. package/node_modules/asn1/package.json +31 -0
  31. package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
  32. package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
  33. package/node_modules/bcrypt-pbkdf/README.md +45 -0
  34. package/node_modules/bcrypt-pbkdf/index.js +556 -0
  35. package/node_modules/bcrypt-pbkdf/package.json +15 -0
  36. package/node_modules/safer-buffer/LICENSE +21 -0
  37. package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
  38. package/node_modules/safer-buffer/Readme.md +156 -0
  39. package/node_modules/safer-buffer/dangerous.js +58 -0
  40. package/node_modules/safer-buffer/package.json +34 -0
  41. package/node_modules/safer-buffer/safer.js +77 -0
  42. package/node_modules/safer-buffer/tests.js +406 -0
  43. package/node_modules/ssh2/.eslintignore +4 -0
  44. package/node_modules/ssh2/.eslintrc.js +5 -0
  45. package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
  46. package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
  47. package/node_modules/ssh2/LICENSE +19 -0
  48. package/node_modules/ssh2/README.md +1529 -0
  49. package/node_modules/ssh2/SFTP.md +413 -0
  50. package/node_modules/ssh2/examples/server-chat.js +238 -0
  51. package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
  52. package/node_modules/ssh2/install.js +27 -0
  53. package/node_modules/ssh2/lib/Channel.js +295 -0
  54. package/node_modules/ssh2/lib/agent.js +1123 -0
  55. package/node_modules/ssh2/lib/client.js +2176 -0
  56. package/node_modules/ssh2/lib/http-agents.js +84 -0
  57. package/node_modules/ssh2/lib/index.js +44 -0
  58. package/node_modules/ssh2/lib/keygen.js +582 -0
  59. package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
  60. package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
  61. package/node_modules/ssh2/lib/protocol/constants.js +356 -0
  62. package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
  63. package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
  64. package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
  65. package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
  66. package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
  67. package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
  68. package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
  69. package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
  70. package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
  71. package/node_modules/ssh2/lib/protocol/utils.js +356 -0
  72. package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
  73. package/node_modules/ssh2/lib/server.js +1380 -0
  74. package/node_modules/ssh2/lib/utils.js +336 -0
  75. package/node_modules/ssh2/package.json +49 -0
  76. package/node_modules/ssh2/test/common.js +316 -0
  77. package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
  78. package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
  79. package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
  80. package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
  81. package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
  82. package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
  83. package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
  84. package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
  85. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
  86. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
  87. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
  88. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
  89. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
  90. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
  91. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
  92. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
  93. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
  94. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
  95. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
  96. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
  97. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
  98. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
  99. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
  100. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
  101. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
  102. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
  103. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
  104. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
  105. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
  106. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
  107. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
  108. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
  109. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
  110. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
  111. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
  112. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
  113. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
  114. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
  115. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
  116. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
  117. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
  118. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
  119. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
  120. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
  121. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
  122. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
  123. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
  124. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
  125. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
  126. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
  127. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
  128. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
  129. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
  130. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
  131. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
  132. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
  133. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
  134. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
  135. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
  136. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
  137. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
  138. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
  139. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
  140. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
  141. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
  142. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
  143. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
  144. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
  145. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
  146. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
  147. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
  148. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
  149. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
  150. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
  151. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
  152. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
  153. package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
  154. package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
  155. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
  156. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
  157. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
  158. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
  159. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
  160. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
  161. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
  162. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
  163. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
  164. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
  165. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
  166. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
  167. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
  168. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
  169. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
  170. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
  171. package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
  172. package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
  173. package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
  174. package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
  175. package/node_modules/ssh2/test/test-exec.js +578 -0
  176. package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
  177. package/node_modules/ssh2/test/test-keygen.js +60 -0
  178. package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
  179. package/node_modules/ssh2/test/test-openssh.js +261 -0
  180. package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
  181. package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
  182. package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
  183. package/node_modules/ssh2/test/test-sftp.js +842 -0
  184. package/node_modules/ssh2/test/test-shell.js +109 -0
  185. package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
  186. package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
  187. package/node_modules/ssh2/test/test-userauth.js +611 -0
  188. package/node_modules/ssh2/test/test-worker-imports.js +25 -0
  189. package/node_modules/ssh2/test/test.js +20 -0
  190. package/node_modules/ssh2/util/build_pagent.bat +2 -0
  191. package/node_modules/ssh2/util/pagent.c +88 -0
  192. package/node_modules/ssh2/util/pagent.exe +0 -0
  193. package/node_modules/tweetnacl/.npmignore +4 -0
  194. package/node_modules/tweetnacl/AUTHORS.md +28 -0
  195. package/node_modules/tweetnacl/CHANGELOG.md +221 -0
  196. package/node_modules/tweetnacl/LICENSE +24 -0
  197. package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
  198. package/node_modules/tweetnacl/README.md +459 -0
  199. package/node_modules/tweetnacl/nacl-fast.js +2388 -0
  200. package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
  201. package/node_modules/tweetnacl/nacl.d.ts +98 -0
  202. package/node_modules/tweetnacl/nacl.js +1175 -0
  203. package/node_modules/tweetnacl/nacl.min.js +1 -0
  204. package/node_modules/tweetnacl/package.json +58 -0
  205. package/package.json +10 -2
@@ -0,0 +1,2176 @@
1
+ // TODO:
2
+ // * add `.connected` or similar property to allow immediate connection
3
+ // status checking
4
+ // * add/improve debug output during user authentication phase
5
+ 'use strict';
6
+
7
+ const {
8
+ createHash,
9
+ getHashes,
10
+ randomFillSync,
11
+ } = require('crypto');
12
+ const { Socket } = require('net');
13
+ const { lookup: dnsLookup } = require('dns');
14
+ const EventEmitter = require('events');
15
+ const HASHES = getHashes();
16
+
17
+ const {
18
+ COMPAT,
19
+ CHANNEL_EXTENDED_DATATYPE: { STDERR },
20
+ CHANNEL_OPEN_FAILURE,
21
+ DEFAULT_CIPHER,
22
+ DEFAULT_COMPRESSION,
23
+ DEFAULT_KEX,
24
+ DEFAULT_MAC,
25
+ DEFAULT_SERVER_HOST_KEY,
26
+ DISCONNECT_REASON,
27
+ DISCONNECT_REASON_BY_VALUE,
28
+ SUPPORTED_CIPHER,
29
+ SUPPORTED_COMPRESSION,
30
+ SUPPORTED_KEX,
31
+ SUPPORTED_MAC,
32
+ SUPPORTED_SERVER_HOST_KEY,
33
+ } = require('./protocol/constants.js');
34
+ const { init: cryptoInit } = require('./protocol/crypto.js');
35
+ const Protocol = require('./protocol/Protocol.js');
36
+ const { parseKey } = require('./protocol/keyParser.js');
37
+ const { SFTP } = require('./protocol/SFTP.js');
38
+ const {
39
+ bufferCopy,
40
+ makeBufferParser,
41
+ makeError,
42
+ readUInt32BE,
43
+ sigSSHToASN1,
44
+ writeUInt32BE,
45
+ } = require('./protocol/utils.js');
46
+
47
+ const { AgentContext, createAgent, isAgent } = require('./agent.js');
48
+ const {
49
+ Channel,
50
+ MAX_WINDOW,
51
+ PACKET_SIZE,
52
+ windowAdjust,
53
+ WINDOW_THRESHOLD,
54
+ } = require('./Channel.js');
55
+ const {
56
+ ChannelManager,
57
+ generateAlgorithmList,
58
+ isWritable,
59
+ onChannelOpenFailure,
60
+ onCHANNEL_CLOSE,
61
+ } = require('./utils.js');
62
+
63
+ const bufferParser = makeBufferParser();
64
+ const sigParser = makeBufferParser();
65
+ const RE_OPENSSH = /^OpenSSH_(?:(?![0-4])\d)|(?:\d{2,})/;
66
+ const noop = (err) => {};
67
+
68
+ class Client extends EventEmitter {
69
+ constructor() {
70
+ super();
71
+
72
+ this.config = {
73
+ host: undefined,
74
+ port: undefined,
75
+ localAddress: undefined,
76
+ localPort: undefined,
77
+ forceIPv4: undefined,
78
+ forceIPv6: undefined,
79
+ keepaliveCountMax: undefined,
80
+ keepaliveInterval: undefined,
81
+ readyTimeout: undefined,
82
+ ident: undefined,
83
+
84
+ username: undefined,
85
+ password: undefined,
86
+ privateKey: undefined,
87
+ tryKeyboard: undefined,
88
+ agent: undefined,
89
+ allowAgentFwd: undefined,
90
+ authHandler: undefined,
91
+
92
+ hostHashAlgo: undefined,
93
+ hostHashCb: undefined,
94
+ strictVendor: undefined,
95
+ debug: undefined
96
+ };
97
+
98
+ this._agent = undefined;
99
+ this._readyTimeout = undefined;
100
+ this._chanMgr = undefined;
101
+ this._callbacks = undefined;
102
+ this._forwarding = undefined;
103
+ this._forwardingUnix = undefined;
104
+ this._acceptX11 = undefined;
105
+ this._agentFwdEnabled = undefined;
106
+ this._remoteVer = undefined;
107
+
108
+ this._protocol = undefined;
109
+ this._sock = undefined;
110
+ this._resetKA = undefined;
111
+ }
112
+
113
+ connect(cfg) {
114
+ if (this._sock && isWritable(this._sock)) {
115
+ this.once('close', () => {
116
+ this.connect(cfg);
117
+ });
118
+ this.end();
119
+ return this;
120
+ }
121
+
122
+ this.config.host = cfg.hostname || cfg.host || 'localhost';
123
+ this.config.port = cfg.port || 22;
124
+ this.config.localAddress = (typeof cfg.localAddress === 'string'
125
+ ? cfg.localAddress
126
+ : undefined);
127
+ this.config.localPort = (typeof cfg.localPort === 'string'
128
+ || typeof cfg.localPort === 'number'
129
+ ? cfg.localPort
130
+ : undefined);
131
+ this.config.forceIPv4 = cfg.forceIPv4 || false;
132
+ this.config.forceIPv6 = cfg.forceIPv6 || false;
133
+ this.config.keepaliveCountMax = (typeof cfg.keepaliveCountMax === 'number'
134
+ && cfg.keepaliveCountMax >= 0
135
+ ? cfg.keepaliveCountMax
136
+ : 3);
137
+ this.config.keepaliveInterval = (typeof cfg.keepaliveInterval === 'number'
138
+ && cfg.keepaliveInterval > 0
139
+ ? cfg.keepaliveInterval
140
+ : 0);
141
+ this.config.readyTimeout = (typeof cfg.readyTimeout === 'number'
142
+ && cfg.readyTimeout >= 0
143
+ ? cfg.readyTimeout
144
+ : 20000);
145
+ this.config.ident = (typeof cfg.ident === 'string'
146
+ || Buffer.isBuffer(cfg.ident)
147
+ ? cfg.ident
148
+ : undefined);
149
+
150
+ const algorithms = {
151
+ kex: undefined,
152
+ serverHostKey: undefined,
153
+ cs: {
154
+ cipher: undefined,
155
+ mac: undefined,
156
+ compress: undefined,
157
+ lang: [],
158
+ },
159
+ sc: undefined,
160
+ };
161
+ let allOfferDefaults = true;
162
+ if (typeof cfg.algorithms === 'object' && cfg.algorithms !== null) {
163
+ algorithms.kex = generateAlgorithmList(cfg.algorithms.kex,
164
+ DEFAULT_KEX,
165
+ SUPPORTED_KEX);
166
+ if (algorithms.kex !== DEFAULT_KEX)
167
+ allOfferDefaults = false;
168
+
169
+ algorithms.serverHostKey =
170
+ generateAlgorithmList(cfg.algorithms.serverHostKey,
171
+ DEFAULT_SERVER_HOST_KEY,
172
+ SUPPORTED_SERVER_HOST_KEY);
173
+ if (algorithms.serverHostKey !== DEFAULT_SERVER_HOST_KEY)
174
+ allOfferDefaults = false;
175
+
176
+ algorithms.cs.cipher = generateAlgorithmList(cfg.algorithms.cipher,
177
+ DEFAULT_CIPHER,
178
+ SUPPORTED_CIPHER);
179
+ if (algorithms.cs.cipher !== DEFAULT_CIPHER)
180
+ allOfferDefaults = false;
181
+
182
+ algorithms.cs.mac = generateAlgorithmList(cfg.algorithms.hmac,
183
+ DEFAULT_MAC,
184
+ SUPPORTED_MAC);
185
+ if (algorithms.cs.mac !== DEFAULT_MAC)
186
+ allOfferDefaults = false;
187
+
188
+ algorithms.cs.compress = generateAlgorithmList(cfg.algorithms.compress,
189
+ DEFAULT_COMPRESSION,
190
+ SUPPORTED_COMPRESSION);
191
+ if (algorithms.cs.compress !== DEFAULT_COMPRESSION)
192
+ allOfferDefaults = false;
193
+
194
+ if (!allOfferDefaults)
195
+ algorithms.sc = algorithms.cs;
196
+ }
197
+
198
+ if (typeof cfg.username === 'string')
199
+ this.config.username = cfg.username;
200
+ else if (typeof cfg.user === 'string')
201
+ this.config.username = cfg.user;
202
+ else
203
+ throw new Error('Invalid username');
204
+
205
+ this.config.password = (typeof cfg.password === 'string'
206
+ ? cfg.password
207
+ : undefined);
208
+ this.config.privateKey = (typeof cfg.privateKey === 'string'
209
+ || Buffer.isBuffer(cfg.privateKey)
210
+ ? cfg.privateKey
211
+ : undefined);
212
+ this.config.localHostname = (typeof cfg.localHostname === 'string'
213
+ ? cfg.localHostname
214
+ : undefined);
215
+ this.config.localUsername = (typeof cfg.localUsername === 'string'
216
+ ? cfg.localUsername
217
+ : undefined);
218
+ this.config.tryKeyboard = (cfg.tryKeyboard === true);
219
+ if (typeof cfg.agent === 'string' && cfg.agent.length)
220
+ this.config.agent = createAgent(cfg.agent);
221
+ else if (isAgent(cfg.agent))
222
+ this.config.agent = cfg.agent;
223
+ else
224
+ this.config.agent = undefined;
225
+ this.config.allowAgentFwd = (cfg.agentForward === true
226
+ && this.config.agent !== undefined);
227
+ let authHandler = this.config.authHandler = (
228
+ typeof cfg.authHandler === 'function'
229
+ || Array.isArray(cfg.authHandler)
230
+ ? cfg.authHandler
231
+ : undefined
232
+ );
233
+
234
+ this.config.strictVendor = (typeof cfg.strictVendor === 'boolean'
235
+ ? cfg.strictVendor
236
+ : true);
237
+
238
+ const debug = this.config.debug = (typeof cfg.debug === 'function'
239
+ ? cfg.debug
240
+ : undefined);
241
+
242
+ if (cfg.agentForward === true && !this.config.allowAgentFwd) {
243
+ throw new Error(
244
+ 'You must set a valid agent path to allow agent forwarding'
245
+ );
246
+ }
247
+
248
+ let callbacks = this._callbacks = [];
249
+ this._chanMgr = new ChannelManager(this);
250
+ this._forwarding = {};
251
+ this._forwardingUnix = {};
252
+ this._acceptX11 = 0;
253
+ this._agentFwdEnabled = false;
254
+ this._agent = (this.config.agent ? this.config.agent : undefined);
255
+ this._remoteVer = undefined;
256
+ let privateKey;
257
+
258
+ if (this.config.privateKey) {
259
+ privateKey = parseKey(this.config.privateKey, cfg.passphrase);
260
+ if (privateKey instanceof Error)
261
+ throw new Error(`Cannot parse privateKey: ${privateKey.message}`);
262
+ if (Array.isArray(privateKey)) {
263
+ // OpenSSH's newer format only stores 1 key for now
264
+ privateKey = privateKey[0];
265
+ }
266
+ if (privateKey.getPrivatePEM() === null) {
267
+ throw new Error(
268
+ 'privateKey value does not contain a (valid) private key'
269
+ );
270
+ }
271
+ }
272
+
273
+ let hostVerifier;
274
+ if (typeof cfg.hostVerifier === 'function') {
275
+ const hashCb = cfg.hostVerifier;
276
+ let hashAlgo;
277
+ if (HASHES.indexOf(cfg.hostHash) !== -1) {
278
+ // Default to old behavior of hashing on user's behalf
279
+ hashAlgo = cfg.hostHash;
280
+ }
281
+ hostVerifier = (key, verify) => {
282
+ if (hashAlgo)
283
+ key = createHash(hashAlgo).update(key).digest('hex');
284
+ const ret = hashCb(key, verify);
285
+ if (ret !== undefined)
286
+ verify(ret);
287
+ };
288
+ }
289
+
290
+ const sock = this._sock = (cfg.sock || new Socket());
291
+ let ready = false;
292
+ let sawHeader = false;
293
+ if (this._protocol)
294
+ this._protocol.cleanup();
295
+ const DEBUG_HANDLER = (!debug ? undefined : (p, display, msg) => {
296
+ debug(`Debug output from server: ${JSON.stringify(msg)}`);
297
+ });
298
+ let serverSigAlgs;
299
+ const proto = this._protocol = new Protocol({
300
+ ident: this.config.ident,
301
+ offer: (allOfferDefaults ? undefined : algorithms),
302
+ onWrite: (data) => {
303
+ if (isWritable(sock))
304
+ sock.write(data);
305
+ },
306
+ onError: (err) => {
307
+ if (err.level === 'handshake')
308
+ clearTimeout(this._readyTimeout);
309
+ if (!proto._destruct)
310
+ sock.removeAllListeners('data');
311
+ this.emit('error', err);
312
+ try {
313
+ sock.end();
314
+ } catch {}
315
+ },
316
+ onHeader: (header) => {
317
+ sawHeader = true;
318
+ this._remoteVer = header.versions.software;
319
+ if (header.greeting)
320
+ this.emit('greeting', header.greeting);
321
+ },
322
+ onHandshakeComplete: (negotiated) => {
323
+ this.emit('handshake', negotiated);
324
+ if (!ready) {
325
+ ready = true;
326
+ proto.service('ssh-userauth');
327
+ }
328
+ },
329
+ debug,
330
+ hostVerifier,
331
+ messageHandlers: {
332
+ DEBUG: DEBUG_HANDLER,
333
+ DISCONNECT: (p, reason, desc) => {
334
+ if (reason !== DISCONNECT_REASON.BY_APPLICATION) {
335
+ if (!desc) {
336
+ desc = DISCONNECT_REASON_BY_VALUE[reason];
337
+ if (desc === undefined)
338
+ desc = `Unexpected disconnection reason: ${reason}`;
339
+ }
340
+ const err = new Error(desc);
341
+ err.code = reason;
342
+ this.emit('error', err);
343
+ }
344
+ sock.end();
345
+ },
346
+ SERVICE_ACCEPT: (p, name) => {
347
+ if (name === 'ssh-userauth')
348
+ tryNextAuth();
349
+ },
350
+ EXT_INFO: (p, exts) => {
351
+ if (serverSigAlgs === undefined) {
352
+ for (const ext of exts) {
353
+ if (ext.name === 'server-sig-algs') {
354
+ serverSigAlgs = ext.algs;
355
+ return;
356
+ }
357
+ }
358
+ serverSigAlgs = null;
359
+ }
360
+ },
361
+ USERAUTH_BANNER: (p, msg) => {
362
+ this.emit('banner', msg);
363
+ },
364
+ USERAUTH_SUCCESS: (p) => {
365
+ // Start keepalive mechanism
366
+ resetKA();
367
+
368
+ clearTimeout(this._readyTimeout);
369
+
370
+ this.emit('ready');
371
+ },
372
+ USERAUTH_FAILURE: (p, authMethods, partialSuccess) => {
373
+ // For key-based authentication, check if we should retry the current
374
+ // key with a different algorithm first
375
+ if (curAuth.keyAlgos) {
376
+ const oldKeyAlgo = curAuth.keyAlgos[0][0];
377
+ if (debug)
378
+ debug(`Client: ${curAuth.type} (${oldKeyAlgo}) auth failed`);
379
+ curAuth.keyAlgos.shift();
380
+ if (curAuth.keyAlgos.length) {
381
+ const [keyAlgo, hashAlgo] = curAuth.keyAlgos[0];
382
+ switch (curAuth.type) {
383
+ case 'agent':
384
+ proto.authPK(
385
+ curAuth.username,
386
+ curAuth.agentCtx.currentKey(),
387
+ keyAlgo
388
+ );
389
+ return;
390
+ case 'publickey':
391
+ proto.authPK(curAuth.username, curAuth.key, keyAlgo);
392
+ return;
393
+ case 'hostbased':
394
+ proto.authHostbased(curAuth.username,
395
+ curAuth.key,
396
+ curAuth.localHostname,
397
+ curAuth.localUsername,
398
+ keyAlgo,
399
+ (buf, cb) => {
400
+ const signature = curAuth.key.sign(buf, hashAlgo);
401
+ if (signature instanceof Error) {
402
+ signature.message =
403
+ `Error while signing with key: ${signature.message}`;
404
+ signature.level = 'client-authentication';
405
+ this.emit('error', signature);
406
+ return tryNextAuth();
407
+ }
408
+
409
+ cb(signature);
410
+ });
411
+ return;
412
+ }
413
+ } else {
414
+ curAuth.keyAlgos = undefined;
415
+ }
416
+ }
417
+
418
+ if (curAuth.type === 'agent') {
419
+ const pos = curAuth.agentCtx.pos();
420
+ debug && debug(`Client: Agent key #${pos + 1} failed`);
421
+ return tryNextAgentKey();
422
+ }
423
+
424
+ debug && debug(`Client: ${curAuth.type} auth failed`);
425
+
426
+ curPartial = partialSuccess;
427
+ curAuthsLeft = authMethods;
428
+ tryNextAuth();
429
+ },
430
+ USERAUTH_PASSWD_CHANGEREQ: (p, prompt) => {
431
+ if (curAuth.type === 'password') {
432
+ // TODO: support a `changePrompt()` on `curAuth` that defaults to
433
+ // emitting 'change password' as before
434
+ this.emit('change password', prompt, (newPassword) => {
435
+ proto.authPassword(
436
+ this.config.username,
437
+ this.config.password,
438
+ newPassword
439
+ );
440
+ });
441
+ }
442
+ },
443
+ USERAUTH_PK_OK: (p) => {
444
+ let keyAlgo;
445
+ let hashAlgo;
446
+ if (curAuth.keyAlgos)
447
+ [keyAlgo, hashAlgo] = curAuth.keyAlgos[0];
448
+ if (curAuth.type === 'agent') {
449
+ const key = curAuth.agentCtx.currentKey();
450
+ proto.authPK(curAuth.username, key, keyAlgo, (buf, cb) => {
451
+ const opts = { hash: hashAlgo };
452
+ curAuth.agentCtx.sign(key, buf, opts, (err, signed) => {
453
+ if (err) {
454
+ err.level = 'agent';
455
+ this.emit('error', err);
456
+ } else {
457
+ return cb(signed);
458
+ }
459
+
460
+ tryNextAgentKey();
461
+ });
462
+ });
463
+ } else if (curAuth.type === 'publickey') {
464
+ proto.authPK(curAuth.username, curAuth.key, keyAlgo, (buf, cb) => {
465
+ const signature = curAuth.key.sign(buf, hashAlgo);
466
+ if (signature instanceof Error) {
467
+ signature.message =
468
+ `Error signing data with key: ${signature.message}`;
469
+ signature.level = 'client-authentication';
470
+ this.emit('error', signature);
471
+ return tryNextAuth();
472
+ }
473
+ cb(signature);
474
+ });
475
+ }
476
+ },
477
+ USERAUTH_INFO_REQUEST: (p, name, instructions, prompts) => {
478
+ if (curAuth.type === 'keyboard-interactive') {
479
+ const nprompts = (Array.isArray(prompts) ? prompts.length : 0);
480
+ if (nprompts === 0) {
481
+ debug && debug(
482
+ 'Client: Sending automatic USERAUTH_INFO_RESPONSE'
483
+ );
484
+ proto.authInfoRes();
485
+ return;
486
+ }
487
+ // We sent a keyboard-interactive user authentication request and
488
+ // now the server is sending us the prompts we need to present to
489
+ // the user
490
+ curAuth.prompt(
491
+ name,
492
+ instructions,
493
+ '',
494
+ prompts,
495
+ (answers) => {
496
+ proto.authInfoRes(answers);
497
+ }
498
+ );
499
+ }
500
+ },
501
+ REQUEST_SUCCESS: (p, data) => {
502
+ if (callbacks.length)
503
+ callbacks.shift()(false, data);
504
+ },
505
+ REQUEST_FAILURE: (p) => {
506
+ if (callbacks.length)
507
+ callbacks.shift()(true);
508
+ },
509
+ GLOBAL_REQUEST: (p, name, wantReply, data) => {
510
+ switch (name) {
511
+ case 'hostkeys-00@openssh.com':
512
+ // Automatically verify keys before passing to end user
513
+ hostKeysProve(this, data, (err, keys) => {
514
+ if (err)
515
+ return;
516
+ this.emit('hostkeys', keys);
517
+ });
518
+ if (wantReply)
519
+ proto.requestSuccess();
520
+ break;
521
+ default:
522
+ // Auto-reject all other global requests, this can be especially
523
+ // useful if the server is sending us dummy keepalive global
524
+ // requests
525
+ if (wantReply)
526
+ proto.requestFailure();
527
+ }
528
+ },
529
+ CHANNEL_OPEN: (p, info) => {
530
+ // Handle incoming requests from server, typically a forwarded TCP or
531
+ // X11 connection
532
+ onCHANNEL_OPEN(this, info);
533
+ },
534
+ CHANNEL_OPEN_CONFIRMATION: (p, info) => {
535
+ const channel = this._chanMgr.get(info.recipient);
536
+ if (typeof channel !== 'function')
537
+ return;
538
+
539
+ const isSFTP = (channel.type === 'sftp');
540
+ const type = (isSFTP ? 'session' : channel.type);
541
+ const chanInfo = {
542
+ type,
543
+ incoming: {
544
+ id: info.recipient,
545
+ window: MAX_WINDOW,
546
+ packetSize: PACKET_SIZE,
547
+ state: 'open'
548
+ },
549
+ outgoing: {
550
+ id: info.sender,
551
+ window: info.window,
552
+ packetSize: info.packetSize,
553
+ state: 'open'
554
+ }
555
+ };
556
+ const instance = (
557
+ isSFTP
558
+ ? new SFTP(this, chanInfo, { debug })
559
+ : new Channel(this, chanInfo)
560
+ );
561
+ this._chanMgr.update(info.recipient, instance);
562
+ channel(undefined, instance);
563
+ },
564
+ CHANNEL_OPEN_FAILURE: (p, recipient, reason, description) => {
565
+ const channel = this._chanMgr.get(recipient);
566
+ if (typeof channel !== 'function')
567
+ return;
568
+
569
+ const info = { reason, description };
570
+ onChannelOpenFailure(this, recipient, info, channel);
571
+ },
572
+ CHANNEL_DATA: (p, recipient, data) => {
573
+ const channel = this._chanMgr.get(recipient);
574
+ if (typeof channel !== 'object' || channel === null)
575
+ return;
576
+
577
+ // The remote party should not be sending us data if there is no
578
+ // window space available ...
579
+ // TODO: raise error on data with not enough window?
580
+ if (channel.incoming.window === 0)
581
+ return;
582
+
583
+ channel.incoming.window -= data.length;
584
+
585
+ if (channel.push(data) === false) {
586
+ channel._waitChanDrain = true;
587
+ return;
588
+ }
589
+
590
+ if (channel.incoming.window <= WINDOW_THRESHOLD)
591
+ windowAdjust(channel);
592
+ },
593
+ CHANNEL_EXTENDED_DATA: (p, recipient, data, type) => {
594
+ if (type !== STDERR)
595
+ return;
596
+
597
+ const channel = this._chanMgr.get(recipient);
598
+ if (typeof channel !== 'object' || channel === null)
599
+ return;
600
+
601
+ // The remote party should not be sending us data if there is no
602
+ // window space available ...
603
+ // TODO: raise error on data with not enough window?
604
+ if (channel.incoming.window === 0)
605
+ return;
606
+
607
+ channel.incoming.window -= data.length;
608
+
609
+ if (!channel.stderr.push(data)) {
610
+ channel._waitChanDrain = true;
611
+ return;
612
+ }
613
+
614
+ if (channel.incoming.window <= WINDOW_THRESHOLD)
615
+ windowAdjust(channel);
616
+ },
617
+ CHANNEL_WINDOW_ADJUST: (p, recipient, amount) => {
618
+ const channel = this._chanMgr.get(recipient);
619
+ if (typeof channel !== 'object' || channel === null)
620
+ return;
621
+
622
+ // The other side is allowing us to send `amount` more bytes of data
623
+ channel.outgoing.window += amount;
624
+
625
+ if (channel._waitWindow) {
626
+ channel._waitWindow = false;
627
+
628
+ if (channel._chunk) {
629
+ channel._write(channel._chunk, null, channel._chunkcb);
630
+ } else if (channel._chunkcb) {
631
+ channel._chunkcb();
632
+ } else if (channel._chunkErr) {
633
+ channel.stderr._write(channel._chunkErr,
634
+ null,
635
+ channel._chunkcbErr);
636
+ } else if (channel._chunkcbErr) {
637
+ channel._chunkcbErr();
638
+ }
639
+ }
640
+ },
641
+ CHANNEL_SUCCESS: (p, recipient) => {
642
+ const channel = this._chanMgr.get(recipient);
643
+ if (typeof channel !== 'object' || channel === null)
644
+ return;
645
+
646
+ this._resetKA();
647
+
648
+ if (channel._callbacks.length)
649
+ channel._callbacks.shift()(false);
650
+ },
651
+ CHANNEL_FAILURE: (p, recipient) => {
652
+ const channel = this._chanMgr.get(recipient);
653
+ if (typeof channel !== 'object' || channel === null)
654
+ return;
655
+
656
+ this._resetKA();
657
+
658
+ if (channel._callbacks.length)
659
+ channel._callbacks.shift()(true);
660
+ },
661
+ CHANNEL_REQUEST: (p, recipient, type, wantReply, data) => {
662
+ const channel = this._chanMgr.get(recipient);
663
+ if (typeof channel !== 'object' || channel === null)
664
+ return;
665
+
666
+ const exit = channel._exit;
667
+ if (exit.code !== undefined)
668
+ return;
669
+ switch (type) {
670
+ case 'exit-status':
671
+ channel.emit('exit', exit.code = data);
672
+ return;
673
+ case 'exit-signal':
674
+ channel.emit('exit',
675
+ exit.code = null,
676
+ exit.signal = `SIG${data.signal}`,
677
+ exit.dump = data.coreDumped,
678
+ exit.desc = data.errorMessage);
679
+ return;
680
+ }
681
+
682
+ // Keepalive request? OpenSSH will send one as a channel request if
683
+ // there is a channel open
684
+
685
+ if (wantReply)
686
+ p.channelFailure(channel.outgoing.id);
687
+ },
688
+ CHANNEL_EOF: (p, recipient) => {
689
+ const channel = this._chanMgr.get(recipient);
690
+ if (typeof channel !== 'object' || channel === null)
691
+ return;
692
+
693
+ if (channel.incoming.state !== 'open')
694
+ return;
695
+ channel.incoming.state = 'eof';
696
+
697
+ if (channel.readable)
698
+ channel.push(null);
699
+ if (channel.stderr.readable)
700
+ channel.stderr.push(null);
701
+ },
702
+ CHANNEL_CLOSE: (p, recipient) => {
703
+ onCHANNEL_CLOSE(this, recipient, this._chanMgr.get(recipient));
704
+ },
705
+ },
706
+ });
707
+
708
+ sock.pause();
709
+
710
+ // TODO: check keepalive implementation
711
+ // Keepalive-related
712
+ const kainterval = this.config.keepaliveInterval;
713
+ const kacountmax = this.config.keepaliveCountMax;
714
+ let kacount = 0;
715
+ let katimer;
716
+ const sendKA = () => {
717
+ if (++kacount > kacountmax) {
718
+ clearInterval(katimer);
719
+ if (sock.readable) {
720
+ const err = new Error('Keepalive timeout');
721
+ err.level = 'client-timeout';
722
+ this.emit('error', err);
723
+ sock.destroy();
724
+ }
725
+ return;
726
+ }
727
+ if (isWritable(sock)) {
728
+ // Append dummy callback to keep correct callback order
729
+ callbacks.push(resetKA);
730
+ proto.ping();
731
+ } else {
732
+ clearInterval(katimer);
733
+ }
734
+ };
735
+ function resetKA() {
736
+ if (kainterval > 0) {
737
+ kacount = 0;
738
+ clearInterval(katimer);
739
+ if (isWritable(sock))
740
+ katimer = setInterval(sendKA, kainterval);
741
+ }
742
+ }
743
+ this._resetKA = resetKA;
744
+
745
+ const onDone = (() => {
746
+ let called = false;
747
+ return () => {
748
+ if (called)
749
+ return;
750
+ called = true;
751
+ if (wasConnected && !sawHeader) {
752
+ const err =
753
+ makeError('Connection lost before handshake', 'protocol', true);
754
+ this.emit('error', err);
755
+ }
756
+ };
757
+ })();
758
+ const onConnect = (() => {
759
+ let called = false;
760
+ return () => {
761
+ if (called)
762
+ return;
763
+ called = true;
764
+
765
+ wasConnected = true;
766
+ debug && debug('Socket connected');
767
+ this.emit('connect');
768
+
769
+ cryptoInit.then(() => {
770
+ proto.start();
771
+ sock.on('data', (data) => {
772
+ try {
773
+ proto.parse(data, 0, data.length);
774
+ } catch (ex) {
775
+ this.emit('error', ex);
776
+ try {
777
+ if (isWritable(sock))
778
+ sock.end();
779
+ } catch {}
780
+ }
781
+ });
782
+
783
+ // Drain stderr if we are connection hopping using an exec stream
784
+ if (sock.stderr && typeof sock.stderr.resume === 'function')
785
+ sock.stderr.resume();
786
+
787
+ sock.resume();
788
+ }).catch((err) => {
789
+ this.emit('error', err);
790
+ try {
791
+ if (isWritable(sock))
792
+ sock.end();
793
+ } catch {}
794
+ });
795
+ };
796
+ })();
797
+ let wasConnected = false;
798
+ sock.on('connect', onConnect)
799
+ .on('timeout', () => {
800
+ this.emit('timeout');
801
+ }).on('error', (err) => {
802
+ debug && debug(`Socket error: ${err.message}`);
803
+ clearTimeout(this._readyTimeout);
804
+ err.level = 'client-socket';
805
+ this.emit('error', err);
806
+ }).on('end', () => {
807
+ debug && debug('Socket ended');
808
+ onDone();
809
+ proto.cleanup();
810
+ clearTimeout(this._readyTimeout);
811
+ clearInterval(katimer);
812
+ this.emit('end');
813
+ }).on('close', () => {
814
+ debug && debug('Socket closed');
815
+ onDone();
816
+ proto.cleanup();
817
+ clearTimeout(this._readyTimeout);
818
+ clearInterval(katimer);
819
+ this.emit('close');
820
+
821
+ // Notify outstanding channel requests of disconnection ...
822
+ const callbacks_ = callbacks;
823
+ callbacks = this._callbacks = [];
824
+ const err = new Error('No response from server');
825
+ for (let i = 0; i < callbacks_.length; ++i)
826
+ callbacks_[i](err);
827
+
828
+ // Simulate error for any channels waiting to be opened
829
+ this._chanMgr.cleanup(err);
830
+ });
831
+
832
+ // Begin authentication handling ===========================================
833
+ let curAuth;
834
+ let curPartial = null;
835
+ let curAuthsLeft = null;
836
+ const authsAllowed = ['none'];
837
+ if (this.config.password !== undefined)
838
+ authsAllowed.push('password');
839
+ if (privateKey !== undefined)
840
+ authsAllowed.push('publickey');
841
+ if (this._agent !== undefined)
842
+ authsAllowed.push('agent');
843
+ if (this.config.tryKeyboard)
844
+ authsAllowed.push('keyboard-interactive');
845
+ if (privateKey !== undefined
846
+ && this.config.localHostname !== undefined
847
+ && this.config.localUsername !== undefined) {
848
+ authsAllowed.push('hostbased');
849
+ }
850
+
851
+ if (Array.isArray(authHandler))
852
+ authHandler = makeSimpleAuthHandler(authHandler);
853
+ else if (typeof authHandler !== 'function')
854
+ authHandler = makeSimpleAuthHandler(authsAllowed);
855
+
856
+ let hasSentAuth = false;
857
+ const doNextAuth = (nextAuth) => {
858
+ if (hasSentAuth)
859
+ return;
860
+ hasSentAuth = true;
861
+
862
+ if (nextAuth === false) {
863
+ const err = new Error('All configured authentication methods failed');
864
+ err.level = 'client-authentication';
865
+ this.emit('error', err);
866
+ this.end();
867
+ return;
868
+ }
869
+
870
+ if (typeof nextAuth === 'string') {
871
+ // Remain backwards compatible with original `authHandler()` usage,
872
+ // which only supported passing names of next method to try using data
873
+ // from the `connect()` config object
874
+
875
+ const type = nextAuth;
876
+ if (authsAllowed.indexOf(type) === -1)
877
+ return skipAuth(`Authentication method not allowed: ${type}`);
878
+
879
+ const username = this.config.username;
880
+ switch (type) {
881
+ case 'password':
882
+ nextAuth = { type, username, password: this.config.password };
883
+ break;
884
+ case 'publickey':
885
+ nextAuth = { type, username, key: privateKey };
886
+ break;
887
+ case 'hostbased':
888
+ nextAuth = {
889
+ type,
890
+ username,
891
+ key: privateKey,
892
+ localHostname: this.config.localHostname,
893
+ localUsername: this.config.localUsername,
894
+ };
895
+ break;
896
+ case 'agent':
897
+ nextAuth = {
898
+ type,
899
+ username,
900
+ agentCtx: new AgentContext(this._agent),
901
+ };
902
+ break;
903
+ case 'keyboard-interactive':
904
+ nextAuth = {
905
+ type,
906
+ username,
907
+ prompt: (...args) => this.emit('keyboard-interactive', ...args),
908
+ };
909
+ break;
910
+ case 'none':
911
+ nextAuth = { type, username };
912
+ break;
913
+ default:
914
+ return skipAuth(
915
+ `Skipping unsupported authentication method: ${nextAuth}`
916
+ );
917
+ }
918
+ } else if (typeof nextAuth !== 'object' || nextAuth === null) {
919
+ return skipAuth(
920
+ `Skipping invalid authentication attempt: ${nextAuth}`
921
+ );
922
+ } else {
923
+ const username = nextAuth.username;
924
+ if (typeof username !== 'string') {
925
+ return skipAuth(
926
+ `Skipping invalid authentication attempt: ${nextAuth}`
927
+ );
928
+ }
929
+ const type = nextAuth.type;
930
+ switch (type) {
931
+ case 'password': {
932
+ const { password } = nextAuth;
933
+ if (typeof password !== 'string' && !Buffer.isBuffer(password))
934
+ return skipAuth('Skipping invalid password auth attempt');
935
+ nextAuth = { type, username, password };
936
+ break;
937
+ }
938
+ case 'publickey': {
939
+ const key = parseKey(nextAuth.key, nextAuth.passphrase);
940
+ if (key instanceof Error)
941
+ return skipAuth('Skipping invalid key auth attempt');
942
+ if (!key.isPrivateKey())
943
+ return skipAuth('Skipping non-private key');
944
+ nextAuth = { type, username, key };
945
+ break;
946
+ }
947
+ case 'hostbased': {
948
+ const { localHostname, localUsername } = nextAuth;
949
+ const key = parseKey(nextAuth.key, nextAuth.passphrase);
950
+ if (key instanceof Error
951
+ || typeof localHostname !== 'string'
952
+ || typeof localUsername !== 'string') {
953
+ return skipAuth('Skipping invalid hostbased auth attempt');
954
+ }
955
+ if (!key.isPrivateKey())
956
+ return skipAuth('Skipping non-private key');
957
+ nextAuth = { type, username, key, localHostname, localUsername };
958
+ break;
959
+ }
960
+ case 'agent': {
961
+ let agent = nextAuth.agent;
962
+ if (typeof agent === 'string' && agent.length) {
963
+ agent = createAgent(agent);
964
+ } else if (!isAgent(agent)) {
965
+ return skipAuth(
966
+ `Skipping invalid agent: ${nextAuth.agent}`
967
+ );
968
+ }
969
+ nextAuth = { type, username, agentCtx: new AgentContext(agent) };
970
+ break;
971
+ }
972
+ case 'keyboard-interactive': {
973
+ const { prompt } = nextAuth;
974
+ if (typeof prompt !== 'function') {
975
+ return skipAuth(
976
+ 'Skipping invalid keyboard-interactive auth attempt'
977
+ );
978
+ }
979
+ nextAuth = { type, username, prompt };
980
+ break;
981
+ }
982
+ case 'none':
983
+ nextAuth = { type, username };
984
+ break;
985
+ default:
986
+ return skipAuth(
987
+ `Skipping unsupported authentication method: ${nextAuth}`
988
+ );
989
+ }
990
+ }
991
+ curAuth = nextAuth;
992
+
993
+ // Begin authentication method's process
994
+ try {
995
+ const username = curAuth.username;
996
+ switch (curAuth.type) {
997
+ case 'password':
998
+ proto.authPassword(username, curAuth.password);
999
+ break;
1000
+ case 'publickey': {
1001
+ let keyAlgo;
1002
+ curAuth.keyAlgos = getKeyAlgos(this, curAuth.key, serverSigAlgs);
1003
+ if (curAuth.keyAlgos) {
1004
+ if (curAuth.keyAlgos.length) {
1005
+ keyAlgo = curAuth.keyAlgos[0][0];
1006
+ } else {
1007
+ return skipAuth(
1008
+ 'Skipping key authentication (no mutual hash algorithm)'
1009
+ );
1010
+ }
1011
+ }
1012
+ proto.authPK(username, curAuth.key, keyAlgo);
1013
+ break;
1014
+ }
1015
+ case 'hostbased': {
1016
+ let keyAlgo;
1017
+ let hashAlgo;
1018
+ curAuth.keyAlgos = getKeyAlgos(this, curAuth.key, serverSigAlgs);
1019
+ if (curAuth.keyAlgos) {
1020
+ if (curAuth.keyAlgos.length) {
1021
+ [keyAlgo, hashAlgo] = curAuth.keyAlgos[0];
1022
+ } else {
1023
+ return skipAuth(
1024
+ 'Skipping hostbased authentication (no mutual hash algorithm)'
1025
+ );
1026
+ }
1027
+ }
1028
+
1029
+ proto.authHostbased(username,
1030
+ curAuth.key,
1031
+ curAuth.localHostname,
1032
+ curAuth.localUsername,
1033
+ keyAlgo,
1034
+ (buf, cb) => {
1035
+ const signature = curAuth.key.sign(buf, hashAlgo);
1036
+ if (signature instanceof Error) {
1037
+ signature.message =
1038
+ `Error while signing with key: ${signature.message}`;
1039
+ signature.level = 'client-authentication';
1040
+ this.emit('error', signature);
1041
+ return tryNextAuth();
1042
+ }
1043
+
1044
+ cb(signature);
1045
+ });
1046
+ break;
1047
+ }
1048
+ case 'agent':
1049
+ curAuth.agentCtx.init((err) => {
1050
+ if (err) {
1051
+ err.level = 'agent';
1052
+ this.emit('error', err);
1053
+ return tryNextAuth();
1054
+ }
1055
+ tryNextAgentKey();
1056
+ });
1057
+ break;
1058
+ case 'keyboard-interactive':
1059
+ proto.authKeyboard(username);
1060
+ break;
1061
+ case 'none':
1062
+ proto.authNone(username);
1063
+ break;
1064
+ }
1065
+ } finally {
1066
+ hasSentAuth = false;
1067
+ }
1068
+ };
1069
+
1070
+ function skipAuth(msg) {
1071
+ debug && debug(msg);
1072
+ process.nextTick(tryNextAuth);
1073
+ }
1074
+
1075
+ function tryNextAuth() {
1076
+ hasSentAuth = false;
1077
+ const auth = authHandler(curAuthsLeft, curPartial, doNextAuth);
1078
+ if (hasSentAuth || auth === undefined)
1079
+ return;
1080
+ doNextAuth(auth);
1081
+ }
1082
+
1083
+ const tryNextAgentKey = () => {
1084
+ if (curAuth.type === 'agent') {
1085
+ const key = curAuth.agentCtx.nextKey();
1086
+ if (key === false) {
1087
+ debug && debug('Agent: No more keys left to try');
1088
+ debug && debug('Client: agent auth failed');
1089
+ tryNextAuth();
1090
+ } else {
1091
+ const pos = curAuth.agentCtx.pos();
1092
+ let keyAlgo;
1093
+ curAuth.keyAlgos = getKeyAlgos(this, key, serverSigAlgs);
1094
+ if (curAuth.keyAlgos) {
1095
+ if (curAuth.keyAlgos.length) {
1096
+ keyAlgo = curAuth.keyAlgos[0][0];
1097
+ } else {
1098
+ debug && debug(
1099
+ `Agent: Skipping key #${pos + 1} (no mutual hash algorithm)`
1100
+ );
1101
+ tryNextAgentKey();
1102
+ return;
1103
+ }
1104
+ }
1105
+ debug && debug(`Agent: Trying key #${pos + 1}`);
1106
+ proto.authPK(curAuth.username, key, keyAlgo);
1107
+ }
1108
+ }
1109
+ };
1110
+
1111
+ const startTimeout = () => {
1112
+ if (this.config.readyTimeout > 0) {
1113
+ this._readyTimeout = setTimeout(() => {
1114
+ const err = new Error('Timed out while waiting for handshake');
1115
+ err.level = 'client-timeout';
1116
+ this.emit('error', err);
1117
+ sock.destroy();
1118
+ }, this.config.readyTimeout);
1119
+ }
1120
+ };
1121
+
1122
+ if (!cfg.sock) {
1123
+ let host = this.config.host;
1124
+ const forceIPv4 = this.config.forceIPv4;
1125
+ const forceIPv6 = this.config.forceIPv6;
1126
+
1127
+ debug && debug(`Client: Trying ${host} on port ${this.config.port} ...`);
1128
+
1129
+ const doConnect = () => {
1130
+ startTimeout();
1131
+ sock.connect({
1132
+ host,
1133
+ port: this.config.port,
1134
+ localAddress: this.config.localAddress,
1135
+ localPort: this.config.localPort
1136
+ });
1137
+ sock.setMaxListeners(0);
1138
+ sock.setTimeout(typeof cfg.timeout === 'number' ? cfg.timeout : 0);
1139
+ };
1140
+
1141
+ if ((!forceIPv4 && !forceIPv6) || (forceIPv4 && forceIPv6)) {
1142
+ doConnect();
1143
+ } else {
1144
+ dnsLookup(host, (forceIPv4 ? 4 : 6), (err, address, family) => {
1145
+ if (err) {
1146
+ const type = (forceIPv4 ? 'IPv4' : 'IPv6');
1147
+ const error = new Error(
1148
+ `Error while looking up ${type} address for '${host}': ${err}`
1149
+ );
1150
+ clearTimeout(this._readyTimeout);
1151
+ error.level = 'client-dns';
1152
+ this.emit('error', error);
1153
+ this.emit('close');
1154
+ return;
1155
+ }
1156
+ host = address;
1157
+ doConnect();
1158
+ });
1159
+ }
1160
+ } else {
1161
+ // Custom socket passed in
1162
+ startTimeout();
1163
+ if (typeof sock.connecting === 'boolean') {
1164
+ // net.Socket
1165
+
1166
+ if (!sock.connecting) {
1167
+ // Already connected
1168
+ onConnect();
1169
+ }
1170
+ } else {
1171
+ // Assume socket/stream is already "connected"
1172
+ onConnect();
1173
+ }
1174
+ }
1175
+
1176
+ return this;
1177
+ }
1178
+
1179
+ end() {
1180
+ if (this._sock && isWritable(this._sock)) {
1181
+ this._protocol.disconnect(DISCONNECT_REASON.BY_APPLICATION);
1182
+ this._sock.end();
1183
+ }
1184
+ return this;
1185
+ }
1186
+
1187
+ destroy() {
1188
+ this._sock && isWritable(this._sock) && this._sock.destroy();
1189
+ return this;
1190
+ }
1191
+
1192
+ exec(cmd, opts, cb) {
1193
+ if (!this._sock || !isWritable(this._sock))
1194
+ throw new Error('Not connected');
1195
+
1196
+ if (typeof opts === 'function') {
1197
+ cb = opts;
1198
+ opts = {};
1199
+ }
1200
+
1201
+ const extraOpts = { allowHalfOpen: (opts.allowHalfOpen !== false) };
1202
+
1203
+ openChannel(this, 'session', extraOpts, (err, chan) => {
1204
+ if (err) {
1205
+ cb(err);
1206
+ return;
1207
+ }
1208
+
1209
+ const todo = [];
1210
+
1211
+ function reqCb(err) {
1212
+ if (err) {
1213
+ chan.close();
1214
+ cb(err);
1215
+ return;
1216
+ }
1217
+ if (todo.length)
1218
+ todo.shift()();
1219
+ }
1220
+
1221
+ if (this.config.allowAgentFwd === true
1222
+ || (opts
1223
+ && opts.agentForward === true
1224
+ && this._agent !== undefined)) {
1225
+ todo.push(() => reqAgentFwd(chan, reqCb));
1226
+ }
1227
+
1228
+ if (typeof opts === 'object' && opts !== null) {
1229
+ if (typeof opts.env === 'object' && opts.env !== null)
1230
+ reqEnv(chan, opts.env);
1231
+ if ((typeof opts.pty === 'object' && opts.pty !== null)
1232
+ || opts.pty === true) {
1233
+ todo.push(() => reqPty(chan, opts.pty, reqCb));
1234
+ }
1235
+ if ((typeof opts.x11 === 'object' && opts.x11 !== null)
1236
+ || opts.x11 === 'number'
1237
+ || opts.x11 === true) {
1238
+ todo.push(() => reqX11(chan, opts.x11, reqCb));
1239
+ }
1240
+ }
1241
+
1242
+ todo.push(() => reqExec(chan, cmd, opts, cb));
1243
+ todo.shift()();
1244
+ });
1245
+
1246
+ return this;
1247
+ }
1248
+
1249
+ shell(wndopts, opts, cb) {
1250
+ if (!this._sock || !isWritable(this._sock))
1251
+ throw new Error('Not connected');
1252
+
1253
+ if (typeof wndopts === 'function') {
1254
+ cb = wndopts;
1255
+ wndopts = opts = undefined;
1256
+ } else if (typeof opts === 'function') {
1257
+ cb = opts;
1258
+ opts = undefined;
1259
+ }
1260
+ if (wndopts && (wndopts.x11 !== undefined || wndopts.env !== undefined)) {
1261
+ opts = wndopts;
1262
+ wndopts = undefined;
1263
+ }
1264
+
1265
+ openChannel(this, 'session', (err, chan) => {
1266
+ if (err) {
1267
+ cb(err);
1268
+ return;
1269
+ }
1270
+
1271
+ const todo = [];
1272
+
1273
+ function reqCb(err) {
1274
+ if (err) {
1275
+ chan.close();
1276
+ cb(err);
1277
+ return;
1278
+ }
1279
+ if (todo.length)
1280
+ todo.shift()();
1281
+ }
1282
+
1283
+ if (this.config.allowAgentFwd === true
1284
+ || (opts
1285
+ && opts.agentForward === true
1286
+ && this._agent !== undefined)) {
1287
+ todo.push(() => reqAgentFwd(chan, reqCb));
1288
+ }
1289
+
1290
+ if (wndopts !== false)
1291
+ todo.push(() => reqPty(chan, wndopts, reqCb));
1292
+
1293
+ if (typeof opts === 'object' && opts !== null) {
1294
+ if (typeof opts.env === 'object' && opts.env !== null)
1295
+ reqEnv(chan, opts.env);
1296
+ if ((typeof opts.x11 === 'object' && opts.x11 !== null)
1297
+ || opts.x11 === 'number'
1298
+ || opts.x11 === true) {
1299
+ todo.push(() => reqX11(chan, opts.x11, reqCb));
1300
+ }
1301
+ }
1302
+
1303
+ todo.push(() => reqShell(chan, cb));
1304
+ todo.shift()();
1305
+ });
1306
+
1307
+ return this;
1308
+ }
1309
+
1310
+ subsys(name, cb) {
1311
+ if (!this._sock || !isWritable(this._sock))
1312
+ throw new Error('Not connected');
1313
+
1314
+ openChannel(this, 'session', (err, chan) => {
1315
+ if (err) {
1316
+ cb(err);
1317
+ return;
1318
+ }
1319
+
1320
+ reqSubsystem(chan, name, (err, stream) => {
1321
+ if (err) {
1322
+ cb(err);
1323
+ return;
1324
+ }
1325
+
1326
+ cb(undefined, stream);
1327
+ });
1328
+ });
1329
+
1330
+ return this;
1331
+ }
1332
+
1333
+ forwardIn(bindAddr, bindPort, cb) {
1334
+ if (!this._sock || !isWritable(this._sock))
1335
+ throw new Error('Not connected');
1336
+
1337
+ // Send a request for the server to start forwarding TCP connections to us
1338
+ // on a particular address and port
1339
+
1340
+ const wantReply = (typeof cb === 'function');
1341
+
1342
+ if (wantReply) {
1343
+ this._callbacks.push((had_err, data) => {
1344
+ if (had_err) {
1345
+ cb(had_err !== true
1346
+ ? had_err
1347
+ : new Error(`Unable to bind to ${bindAddr}:${bindPort}`));
1348
+ return;
1349
+ }
1350
+
1351
+ let realPort = bindPort;
1352
+ if (bindPort === 0 && data && data.length >= 4) {
1353
+ realPort = readUInt32BE(data, 0);
1354
+ if (!(this._protocol._compatFlags & COMPAT.DYN_RPORT_BUG))
1355
+ bindPort = realPort;
1356
+ }
1357
+
1358
+ this._forwarding[`${bindAddr}:${bindPort}`] = realPort;
1359
+
1360
+ cb(undefined, realPort);
1361
+ });
1362
+ }
1363
+
1364
+ this._protocol.tcpipForward(bindAddr, bindPort, wantReply);
1365
+
1366
+ return this;
1367
+ }
1368
+
1369
+ unforwardIn(bindAddr, bindPort, cb) {
1370
+ if (!this._sock || !isWritable(this._sock))
1371
+ throw new Error('Not connected');
1372
+
1373
+ // Send a request to stop forwarding us new connections for a particular
1374
+ // address and port
1375
+
1376
+ const wantReply = (typeof cb === 'function');
1377
+
1378
+ if (wantReply) {
1379
+ this._callbacks.push((had_err) => {
1380
+ if (had_err) {
1381
+ cb(had_err !== true
1382
+ ? had_err
1383
+ : new Error(`Unable to unbind from ${bindAddr}:${bindPort}`));
1384
+ return;
1385
+ }
1386
+
1387
+ delete this._forwarding[`${bindAddr}:${bindPort}`];
1388
+
1389
+ cb();
1390
+ });
1391
+ }
1392
+
1393
+ this._protocol.cancelTcpipForward(bindAddr, bindPort, wantReply);
1394
+
1395
+ return this;
1396
+ }
1397
+
1398
+ forwardOut(srcIP, srcPort, dstIP, dstPort, cb) {
1399
+ if (!this._sock || !isWritable(this._sock))
1400
+ throw new Error('Not connected');
1401
+
1402
+ // Send a request to forward a TCP connection to the server
1403
+
1404
+ const cfg = {
1405
+ srcIP: srcIP,
1406
+ srcPort: srcPort,
1407
+ dstIP: dstIP,
1408
+ dstPort: dstPort
1409
+ };
1410
+
1411
+ if (typeof cb !== 'function')
1412
+ cb = noop;
1413
+
1414
+ openChannel(this, 'direct-tcpip', cfg, cb);
1415
+
1416
+ return this;
1417
+ }
1418
+
1419
+ openssh_noMoreSessions(cb) {
1420
+ if (!this._sock || !isWritable(this._sock))
1421
+ throw new Error('Not connected');
1422
+
1423
+ const wantReply = (typeof cb === 'function');
1424
+
1425
+ if (!this.config.strictVendor
1426
+ || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1427
+ if (wantReply) {
1428
+ this._callbacks.push((had_err) => {
1429
+ if (had_err) {
1430
+ cb(had_err !== true
1431
+ ? had_err
1432
+ : new Error('Unable to disable future sessions'));
1433
+ return;
1434
+ }
1435
+
1436
+ cb();
1437
+ });
1438
+ }
1439
+
1440
+ this._protocol.openssh_noMoreSessions(wantReply);
1441
+ return this;
1442
+ }
1443
+
1444
+ if (!wantReply)
1445
+ return this;
1446
+
1447
+ process.nextTick(
1448
+ cb,
1449
+ new Error(
1450
+ 'strictVendor enabled and server is not OpenSSH or compatible version'
1451
+ )
1452
+ );
1453
+
1454
+ return this;
1455
+ }
1456
+
1457
+ openssh_forwardInStreamLocal(socketPath, cb) {
1458
+ if (!this._sock || !isWritable(this._sock))
1459
+ throw new Error('Not connected');
1460
+
1461
+ const wantReply = (typeof cb === 'function');
1462
+
1463
+ if (!this.config.strictVendor
1464
+ || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1465
+ if (wantReply) {
1466
+ this._callbacks.push((had_err) => {
1467
+ if (had_err) {
1468
+ cb(had_err !== true
1469
+ ? had_err
1470
+ : new Error(`Unable to bind to ${socketPath}`));
1471
+ return;
1472
+ }
1473
+ this._forwardingUnix[socketPath] = true;
1474
+ cb();
1475
+ });
1476
+ }
1477
+
1478
+ this._protocol.openssh_streamLocalForward(socketPath, wantReply);
1479
+ return this;
1480
+ }
1481
+
1482
+ if (!wantReply)
1483
+ return this;
1484
+
1485
+ process.nextTick(
1486
+ cb,
1487
+ new Error(
1488
+ 'strictVendor enabled and server is not OpenSSH or compatible version'
1489
+ )
1490
+ );
1491
+
1492
+ return this;
1493
+ }
1494
+
1495
+ openssh_unforwardInStreamLocal(socketPath, cb) {
1496
+ if (!this._sock || !isWritable(this._sock))
1497
+ throw new Error('Not connected');
1498
+
1499
+ const wantReply = (typeof cb === 'function');
1500
+
1501
+ if (!this.config.strictVendor
1502
+ || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1503
+ if (wantReply) {
1504
+ this._callbacks.push((had_err) => {
1505
+ if (had_err) {
1506
+ cb(had_err !== true
1507
+ ? had_err
1508
+ : new Error(`Unable to unbind from ${socketPath}`));
1509
+ return;
1510
+ }
1511
+ delete this._forwardingUnix[socketPath];
1512
+ cb();
1513
+ });
1514
+ }
1515
+
1516
+ this._protocol.openssh_cancelStreamLocalForward(socketPath, wantReply);
1517
+ return this;
1518
+ }
1519
+
1520
+ if (!wantReply)
1521
+ return this;
1522
+
1523
+ process.nextTick(
1524
+ cb,
1525
+ new Error(
1526
+ 'strictVendor enabled and server is not OpenSSH or compatible version'
1527
+ )
1528
+ );
1529
+
1530
+ return this;
1531
+ }
1532
+
1533
+ openssh_forwardOutStreamLocal(socketPath, cb) {
1534
+ if (!this._sock || !isWritable(this._sock))
1535
+ throw new Error('Not connected');
1536
+
1537
+ if (typeof cb !== 'function')
1538
+ cb = noop;
1539
+
1540
+ if (!this.config.strictVendor
1541
+ || (this.config.strictVendor && RE_OPENSSH.test(this._remoteVer))) {
1542
+ openChannel(this, 'direct-streamlocal@openssh.com', { socketPath }, cb);
1543
+ return this;
1544
+ }
1545
+ process.nextTick(
1546
+ cb,
1547
+ new Error(
1548
+ 'strictVendor enabled and server is not OpenSSH or compatible version'
1549
+ )
1550
+ );
1551
+
1552
+ return this;
1553
+ }
1554
+
1555
+ sftp(env, cb) {
1556
+ if (!this._sock || !isWritable(this._sock))
1557
+ throw new Error('Not connected');
1558
+
1559
+ if (typeof env === 'function') {
1560
+ cb = env;
1561
+ env = undefined;
1562
+ }
1563
+
1564
+ openChannel(this, 'sftp', (err, sftp) => {
1565
+ if (err) {
1566
+ cb(err);
1567
+ return;
1568
+ }
1569
+
1570
+ const reqSubsystemCb = (err, sftp_) => {
1571
+ if (err) {
1572
+ cb(err);
1573
+ return;
1574
+ }
1575
+
1576
+ function removeListeners() {
1577
+ sftp.removeListener('ready', onReady);
1578
+ sftp.removeListener('error', onError);
1579
+ sftp.removeListener('exit', onExit);
1580
+ sftp.removeListener('close', onExit);
1581
+ }
1582
+
1583
+ function onReady() {
1584
+ // TODO: do not remove exit/close in case remote end closes the
1585
+ // channel abruptly and we need to notify outstanding callbacks
1586
+ removeListeners();
1587
+ cb(undefined, sftp);
1588
+ }
1589
+
1590
+ function onError(err) {
1591
+ removeListeners();
1592
+ cb(err);
1593
+ }
1594
+
1595
+ function onExit(code, signal) {
1596
+ removeListeners();
1597
+ let msg;
1598
+ if (typeof code === 'number')
1599
+ msg = `Received exit code ${code} while establishing SFTP session`;
1600
+ else if (signal !== undefined)
1601
+ msg = `Received signal ${signal} while establishing SFTP session`;
1602
+ else
1603
+ msg = 'Received unexpected SFTP session termination';
1604
+ const err = new Error(msg);
1605
+ err.code = code;
1606
+ err.signal = signal;
1607
+ cb(err);
1608
+ }
1609
+
1610
+ sftp.on('ready', onReady)
1611
+ .on('error', onError)
1612
+ .on('exit', onExit)
1613
+ .on('close', onExit);
1614
+
1615
+ sftp._init();
1616
+ };
1617
+
1618
+ if (typeof env === 'object' && env !== null) {
1619
+ reqEnv(sftp, env, (err) => {
1620
+ if (err) {
1621
+ cb(err);
1622
+ return;
1623
+ }
1624
+
1625
+ reqSubsystem(sftp, 'sftp', reqSubsystemCb);
1626
+ });
1627
+ } else {
1628
+ reqSubsystem(sftp, 'sftp', reqSubsystemCb);
1629
+ }
1630
+ });
1631
+
1632
+ return this;
1633
+ }
1634
+
1635
+ setNoDelay(noDelay) {
1636
+ if (this._sock && typeof this._sock.setNoDelay === 'function')
1637
+ this._sock.setNoDelay(noDelay);
1638
+
1639
+ return this;
1640
+ }
1641
+ }
1642
+
1643
+ function openChannel(self, type, opts, cb) {
1644
+ // Ask the server to open a channel for some purpose
1645
+ // (e.g. session (sftp, exec, shell), or forwarding a TCP connection
1646
+ const initWindow = MAX_WINDOW;
1647
+ const maxPacket = PACKET_SIZE;
1648
+
1649
+ if (typeof opts === 'function') {
1650
+ cb = opts;
1651
+ opts = {};
1652
+ }
1653
+
1654
+ const wrapper = (err, stream) => {
1655
+ cb(err, stream);
1656
+ };
1657
+ wrapper.type = type;
1658
+
1659
+ const localChan = self._chanMgr.add(wrapper);
1660
+
1661
+ if (localChan === -1) {
1662
+ cb(new Error('No free channels available'));
1663
+ return;
1664
+ }
1665
+
1666
+ switch (type) {
1667
+ case 'session':
1668
+ case 'sftp':
1669
+ self._protocol.session(localChan, initWindow, maxPacket);
1670
+ break;
1671
+ case 'direct-tcpip':
1672
+ self._protocol.directTcpip(localChan, initWindow, maxPacket, opts);
1673
+ break;
1674
+ case 'direct-streamlocal@openssh.com':
1675
+ self._protocol.openssh_directStreamLocal(
1676
+ localChan, initWindow, maxPacket, opts
1677
+ );
1678
+ break;
1679
+ default:
1680
+ throw new Error(`Unsupported channel type: ${type}`);
1681
+ }
1682
+ }
1683
+
1684
+ function reqX11(chan, screen, cb) {
1685
+ // Asks server to start sending us X11 connections
1686
+ const cfg = {
1687
+ single: false,
1688
+ protocol: 'MIT-MAGIC-COOKIE-1',
1689
+ cookie: undefined,
1690
+ screen: 0
1691
+ };
1692
+
1693
+ if (typeof screen === 'function') {
1694
+ cb = screen;
1695
+ } else if (typeof screen === 'object' && screen !== null) {
1696
+ if (typeof screen.single === 'boolean')
1697
+ cfg.single = screen.single;
1698
+ if (typeof screen.screen === 'number')
1699
+ cfg.screen = screen.screen;
1700
+ if (typeof screen.protocol === 'string')
1701
+ cfg.protocol = screen.protocol;
1702
+ if (typeof screen.cookie === 'string')
1703
+ cfg.cookie = screen.cookie;
1704
+ else if (Buffer.isBuffer(screen.cookie))
1705
+ cfg.cookie = screen.cookie.hexSlice(0, screen.cookie.length);
1706
+ }
1707
+ if (cfg.cookie === undefined)
1708
+ cfg.cookie = randomCookie();
1709
+
1710
+ const wantReply = (typeof cb === 'function');
1711
+
1712
+ if (chan.outgoing.state !== 'open') {
1713
+ if (wantReply)
1714
+ cb(new Error('Channel is not open'));
1715
+ return;
1716
+ }
1717
+
1718
+ if (wantReply) {
1719
+ chan._callbacks.push((had_err) => {
1720
+ if (had_err) {
1721
+ cb(had_err !== true ? had_err : new Error('Unable to request X11'));
1722
+ return;
1723
+ }
1724
+
1725
+ chan._hasX11 = true;
1726
+ ++chan._client._acceptX11;
1727
+ chan.once('close', () => {
1728
+ if (chan._client._acceptX11)
1729
+ --chan._client._acceptX11;
1730
+ });
1731
+
1732
+ cb();
1733
+ });
1734
+ }
1735
+
1736
+ chan._client._protocol.x11Forward(chan.outgoing.id, cfg, wantReply);
1737
+ }
1738
+
1739
+ function reqPty(chan, opts, cb) {
1740
+ let rows = 24;
1741
+ let cols = 80;
1742
+ let width = 640;
1743
+ let height = 480;
1744
+ let term = 'vt100';
1745
+ let modes = null;
1746
+
1747
+ if (typeof opts === 'function') {
1748
+ cb = opts;
1749
+ } else if (typeof opts === 'object' && opts !== null) {
1750
+ if (typeof opts.rows === 'number')
1751
+ rows = opts.rows;
1752
+ if (typeof opts.cols === 'number')
1753
+ cols = opts.cols;
1754
+ if (typeof opts.width === 'number')
1755
+ width = opts.width;
1756
+ if (typeof opts.height === 'number')
1757
+ height = opts.height;
1758
+ if (typeof opts.term === 'string')
1759
+ term = opts.term;
1760
+ if (typeof opts.modes === 'object')
1761
+ modes = opts.modes;
1762
+ }
1763
+
1764
+ const wantReply = (typeof cb === 'function');
1765
+
1766
+ if (chan.outgoing.state !== 'open') {
1767
+ if (wantReply)
1768
+ cb(new Error('Channel is not open'));
1769
+ return;
1770
+ }
1771
+
1772
+ if (wantReply) {
1773
+ chan._callbacks.push((had_err) => {
1774
+ if (had_err) {
1775
+ cb(had_err !== true
1776
+ ? had_err
1777
+ : new Error('Unable to request a pseudo-terminal'));
1778
+ return;
1779
+ }
1780
+ cb();
1781
+ });
1782
+ }
1783
+
1784
+ chan._client._protocol.pty(chan.outgoing.id,
1785
+ rows,
1786
+ cols,
1787
+ height,
1788
+ width,
1789
+ term,
1790
+ modes,
1791
+ wantReply);
1792
+ }
1793
+
1794
+ function reqAgentFwd(chan, cb) {
1795
+ const wantReply = (typeof cb === 'function');
1796
+
1797
+ if (chan.outgoing.state !== 'open') {
1798
+ wantReply && cb(new Error('Channel is not open'));
1799
+ return;
1800
+ }
1801
+ if (chan._client._agentFwdEnabled) {
1802
+ wantReply && cb(false);
1803
+ return;
1804
+ }
1805
+
1806
+ chan._client._agentFwdEnabled = true;
1807
+
1808
+ chan._callbacks.push((had_err) => {
1809
+ if (had_err) {
1810
+ chan._client._agentFwdEnabled = false;
1811
+ if (wantReply) {
1812
+ cb(had_err !== true
1813
+ ? had_err
1814
+ : new Error('Unable to request agent forwarding'));
1815
+ }
1816
+ return;
1817
+ }
1818
+
1819
+ if (wantReply)
1820
+ cb();
1821
+ });
1822
+
1823
+ chan._client._protocol.openssh_agentForward(chan.outgoing.id, true);
1824
+ }
1825
+
1826
+ function reqShell(chan, cb) {
1827
+ if (chan.outgoing.state !== 'open') {
1828
+ cb(new Error('Channel is not open'));
1829
+ return;
1830
+ }
1831
+
1832
+ chan._callbacks.push((had_err) => {
1833
+ if (had_err) {
1834
+ cb(had_err !== true ? had_err : new Error('Unable to open shell'));
1835
+ return;
1836
+ }
1837
+ chan.subtype = 'shell';
1838
+ cb(undefined, chan);
1839
+ });
1840
+
1841
+ chan._client._protocol.shell(chan.outgoing.id, true);
1842
+ }
1843
+
1844
+ function reqExec(chan, cmd, opts, cb) {
1845
+ if (chan.outgoing.state !== 'open') {
1846
+ cb(new Error('Channel is not open'));
1847
+ return;
1848
+ }
1849
+
1850
+ chan._callbacks.push((had_err) => {
1851
+ if (had_err) {
1852
+ cb(had_err !== true ? had_err : new Error('Unable to exec'));
1853
+ return;
1854
+ }
1855
+ chan.subtype = 'exec';
1856
+ chan.allowHalfOpen = (opts.allowHalfOpen !== false);
1857
+ cb(undefined, chan);
1858
+ });
1859
+
1860
+ chan._client._protocol.exec(chan.outgoing.id, cmd, true);
1861
+ }
1862
+
1863
+ function reqEnv(chan, env, cb) {
1864
+ const wantReply = (typeof cb === 'function');
1865
+
1866
+ if (chan.outgoing.state !== 'open') {
1867
+ if (wantReply)
1868
+ cb(new Error('Channel is not open'));
1869
+ return;
1870
+ }
1871
+
1872
+ if (wantReply) {
1873
+ chan._callbacks.push((had_err) => {
1874
+ if (had_err) {
1875
+ cb(had_err !== true
1876
+ ? had_err
1877
+ : new Error('Unable to set environment'));
1878
+ return;
1879
+ }
1880
+ cb();
1881
+ });
1882
+ }
1883
+
1884
+ const keys = Object.keys(env || {});
1885
+
1886
+ for (let i = 0; i < keys.length; ++i) {
1887
+ const key = keys[i];
1888
+ const val = env[key];
1889
+ chan._client._protocol.env(chan.outgoing.id, key, val, wantReply);
1890
+ }
1891
+ }
1892
+
1893
+ function reqSubsystem(chan, name, cb) {
1894
+ if (chan.outgoing.state !== 'open') {
1895
+ cb(new Error('Channel is not open'));
1896
+ return;
1897
+ }
1898
+
1899
+ chan._callbacks.push((had_err) => {
1900
+ if (had_err) {
1901
+ cb(had_err !== true
1902
+ ? had_err
1903
+ : new Error(`Unable to start subsystem: ${name}`));
1904
+ return;
1905
+ }
1906
+ chan.subtype = 'subsystem';
1907
+ cb(undefined, chan);
1908
+ });
1909
+
1910
+ chan._client._protocol.subsystem(chan.outgoing.id, name, true);
1911
+ }
1912
+
1913
+ // TODO: inline implementation into single call site
1914
+ function onCHANNEL_OPEN(self, info) {
1915
+ // The server is trying to open a channel with us, this is usually when
1916
+ // we asked the server to forward us connections on some port and now they
1917
+ // are asking us to accept/deny an incoming connection on their side
1918
+
1919
+ let localChan = -1;
1920
+ let reason;
1921
+
1922
+ const accept = () => {
1923
+ const chanInfo = {
1924
+ type: info.type,
1925
+ incoming: {
1926
+ id: localChan,
1927
+ window: MAX_WINDOW,
1928
+ packetSize: PACKET_SIZE,
1929
+ state: 'open'
1930
+ },
1931
+ outgoing: {
1932
+ id: info.sender,
1933
+ window: info.window,
1934
+ packetSize: info.packetSize,
1935
+ state: 'open'
1936
+ }
1937
+ };
1938
+ const stream = new Channel(self, chanInfo);
1939
+ self._chanMgr.update(localChan, stream);
1940
+
1941
+ self._protocol.channelOpenConfirm(info.sender,
1942
+ localChan,
1943
+ MAX_WINDOW,
1944
+ PACKET_SIZE);
1945
+ return stream;
1946
+ };
1947
+ const reject = () => {
1948
+ if (reason === undefined) {
1949
+ if (localChan === -1)
1950
+ reason = CHANNEL_OPEN_FAILURE.RESOURCE_SHORTAGE;
1951
+ else
1952
+ reason = CHANNEL_OPEN_FAILURE.CONNECT_FAILED;
1953
+ }
1954
+
1955
+ if (localChan !== -1)
1956
+ self._chanMgr.remove(localChan);
1957
+
1958
+ self._protocol.channelOpenFail(info.sender, reason, '');
1959
+ };
1960
+ const reserveChannel = () => {
1961
+ localChan = self._chanMgr.add();
1962
+
1963
+ if (localChan === -1) {
1964
+ reason = CHANNEL_OPEN_FAILURE.RESOURCE_SHORTAGE;
1965
+ if (self.config.debug) {
1966
+ self.config.debug(
1967
+ 'Client: Automatic rejection of incoming channel open: '
1968
+ + 'no channels available'
1969
+ );
1970
+ }
1971
+ }
1972
+
1973
+ return (localChan !== -1);
1974
+ };
1975
+
1976
+ const data = info.data;
1977
+ switch (info.type) {
1978
+ case 'forwarded-tcpip': {
1979
+ const val = self._forwarding[`${data.destIP}:${data.destPort}`];
1980
+ if (val !== undefined && reserveChannel()) {
1981
+ if (data.destPort === 0)
1982
+ data.destPort = val;
1983
+ self.emit('tcp connection', data, accept, reject);
1984
+ return;
1985
+ }
1986
+ break;
1987
+ }
1988
+ case 'forwarded-streamlocal@openssh.com':
1989
+ if (self._forwardingUnix[data.socketPath] !== undefined
1990
+ && reserveChannel()) {
1991
+ self.emit('unix connection', data, accept, reject);
1992
+ return;
1993
+ }
1994
+ break;
1995
+ case 'auth-agent@openssh.com':
1996
+ if (self._agentFwdEnabled
1997
+ && typeof self._agent.getStream === 'function'
1998
+ && reserveChannel()) {
1999
+ self._agent.getStream((err, stream) => {
2000
+ if (err)
2001
+ return reject();
2002
+
2003
+ const upstream = accept();
2004
+ upstream.pipe(stream).pipe(upstream);
2005
+ });
2006
+ return;
2007
+ }
2008
+ break;
2009
+ case 'x11':
2010
+ if (self._acceptX11 !== 0 && reserveChannel()) {
2011
+ self.emit('x11', data, accept, reject);
2012
+ return;
2013
+ }
2014
+ break;
2015
+ default:
2016
+ // Automatically reject any unsupported channel open requests
2017
+ reason = CHANNEL_OPEN_FAILURE.UNKNOWN_CHANNEL_TYPE;
2018
+ if (self.config.debug) {
2019
+ self.config.debug(
2020
+ 'Client: Automatic rejection of unsupported incoming channel open '
2021
+ + `type: ${info.type}`
2022
+ );
2023
+ }
2024
+ }
2025
+
2026
+ if (reason === undefined) {
2027
+ reason = CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED;
2028
+ if (self.config.debug) {
2029
+ self.config.debug(
2030
+ 'Client: Automatic rejection of unexpected incoming channel open for: '
2031
+ + info.type
2032
+ );
2033
+ }
2034
+ }
2035
+
2036
+ reject();
2037
+ }
2038
+
2039
+ const randomCookie = (() => {
2040
+ const buffer = Buffer.allocUnsafe(16);
2041
+ return () => {
2042
+ randomFillSync(buffer, 0, 16);
2043
+ return buffer.hexSlice(0, 16);
2044
+ };
2045
+ })();
2046
+
2047
+ function makeSimpleAuthHandler(authList) {
2048
+ if (!Array.isArray(authList))
2049
+ throw new Error('authList must be an array');
2050
+
2051
+ let a = 0;
2052
+ return (authsLeft, partialSuccess, cb) => {
2053
+ if (a === authList.length)
2054
+ return false;
2055
+ return authList[a++];
2056
+ };
2057
+ }
2058
+
2059
+ function hostKeysProve(client, keys_, cb) {
2060
+ if (!client._sock || !isWritable(client._sock))
2061
+ return;
2062
+
2063
+ if (typeof cb !== 'function')
2064
+ cb = noop;
2065
+
2066
+ if (!Array.isArray(keys_))
2067
+ throw new TypeError('Invalid keys argument type');
2068
+
2069
+ const keys = [];
2070
+ for (const key of keys_) {
2071
+ const parsed = parseKey(key);
2072
+ if (parsed instanceof Error)
2073
+ throw parsed;
2074
+ keys.push(parsed);
2075
+ }
2076
+
2077
+ if (!client.config.strictVendor
2078
+ || (client.config.strictVendor && RE_OPENSSH.test(client._remoteVer))) {
2079
+ client._callbacks.push((had_err, data) => {
2080
+ if (had_err) {
2081
+ cb(had_err !== true
2082
+ ? had_err
2083
+ : new Error('Server failed to prove supplied keys'));
2084
+ return;
2085
+ }
2086
+
2087
+ // TODO: move all of this parsing/verifying logic out of the client?
2088
+ const ret = [];
2089
+ let keyIdx = 0;
2090
+ bufferParser.init(data, 0);
2091
+ while (bufferParser.avail()) {
2092
+ if (keyIdx === keys.length)
2093
+ break;
2094
+ const key = keys[keyIdx++];
2095
+ const keyPublic = key.getPublicSSH();
2096
+
2097
+ const sigEntry = bufferParser.readString();
2098
+ sigParser.init(sigEntry, 0);
2099
+ const type = sigParser.readString(true);
2100
+ let value = sigParser.readString();
2101
+
2102
+ let algo;
2103
+ if (type !== key.type) {
2104
+ if (key.type === 'ssh-rsa') {
2105
+ switch (type) {
2106
+ case 'rsa-sha2-256':
2107
+ algo = 'sha256';
2108
+ break;
2109
+ case 'rsa-sha2-512':
2110
+ algo = 'sha512';
2111
+ break;
2112
+ default:
2113
+ continue;
2114
+ }
2115
+ } else {
2116
+ continue;
2117
+ }
2118
+ }
2119
+
2120
+ const sessionID = client._protocol._kex.sessionID;
2121
+ const verifyData = Buffer.allocUnsafe(
2122
+ 4 + 29 + 4 + sessionID.length + 4 + keyPublic.length
2123
+ );
2124
+ let p = 0;
2125
+ writeUInt32BE(verifyData, 29, p);
2126
+ verifyData.utf8Write('hostkeys-prove-00@openssh.com', p += 4, 29);
2127
+ writeUInt32BE(verifyData, sessionID.length, p += 29);
2128
+ bufferCopy(sessionID, verifyData, 0, sessionID.length, p += 4);
2129
+ writeUInt32BE(verifyData, keyPublic.length, p += sessionID.length);
2130
+ bufferCopy(keyPublic, verifyData, 0, keyPublic.length, p += 4);
2131
+
2132
+ if (!(value = sigSSHToASN1(value, type)))
2133
+ continue;
2134
+ if (key.verify(verifyData, value, algo) === true)
2135
+ ret.push(key);
2136
+ }
2137
+ sigParser.clear();
2138
+ bufferParser.clear();
2139
+
2140
+ cb(null, ret);
2141
+ });
2142
+
2143
+ client._protocol.openssh_hostKeysProve(keys);
2144
+ return;
2145
+ }
2146
+
2147
+ process.nextTick(
2148
+ cb,
2149
+ new Error(
2150
+ 'strictVendor enabled and server is not OpenSSH or compatible version'
2151
+ )
2152
+ );
2153
+ }
2154
+
2155
+ function getKeyAlgos(client, key, serverSigAlgs) {
2156
+ switch (key.type) {
2157
+ case 'ssh-rsa':
2158
+ if (client._protocol._compatFlags & COMPAT.IMPLY_RSA_SHA2_SIGALGS) {
2159
+ if (!Array.isArray(serverSigAlgs))
2160
+ serverSigAlgs = ['rsa-sha2-256', 'rsa-sha2-512'];
2161
+ else
2162
+ serverSigAlgs = ['rsa-sha2-256', 'rsa-sha2-512', ...serverSigAlgs];
2163
+ }
2164
+ if (Array.isArray(serverSigAlgs)) {
2165
+ if (serverSigAlgs.indexOf('rsa-sha2-256') !== -1)
2166
+ return [['rsa-sha2-256', 'sha256']];
2167
+ if (serverSigAlgs.indexOf('rsa-sha2-512') !== -1)
2168
+ return [['rsa-sha2-512', 'sha512']];
2169
+ if (serverSigAlgs.indexOf('ssh-rsa') === -1)
2170
+ return [];
2171
+ }
2172
+ return [['ssh-rsa', 'sha1']];
2173
+ }
2174
+ }
2175
+
2176
+ module.exports = Client;