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.
- package/distTs/package.json +10 -2
- package/distTs/src/agent/linux.js +53 -3
- package/distTs/src/agent/manager.js +3 -6
- package/distTs/src/agent/osx.js +51 -3
- package/distTs/src/agent/system.js +400 -21
- package/distTs/src/agent/windows.js +4 -7
- package/distTs/src/command/agent/debug.js +2 -2
- package/distTs/src/command/agent/install.js +37 -10
- package/distTs/src/command/agent/restart.js +2 -2
- package/distTs/src/command/agent/run.js +3 -0
- package/distTs/src/command/agent/start.js +2 -2
- package/distTs/src/command/agent/status.js +19 -0
- package/distTs/src/command/agent/stop.js +2 -2
- package/distTs/src/command/agent/uninstall.js +5 -8
- package/distTs/src/command/agent/update.js +2 -2
- package/distTs/src/command/artifact/publish.js +3 -1
- package/distTs/src/command/version.js +13 -5
- package/distTs/src/logger.js +40 -4
- package/distTs/src/texts.js +35 -19
- package/distTs/src/utils.js +98 -3
- package/node_modules/asn1/Jenkinsfile +65 -0
- package/node_modules/asn1/LICENSE +19 -0
- package/node_modules/asn1/README.md +50 -0
- package/node_modules/asn1/lib/ber/errors.js +13 -0
- package/node_modules/asn1/lib/ber/index.js +27 -0
- package/node_modules/asn1/lib/ber/reader.js +262 -0
- package/node_modules/asn1/lib/ber/types.js +36 -0
- package/node_modules/asn1/lib/ber/writer.js +317 -0
- package/node_modules/asn1/lib/index.js +20 -0
- package/node_modules/asn1/package.json +31 -0
- package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
- package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
- package/node_modules/bcrypt-pbkdf/README.md +45 -0
- package/node_modules/bcrypt-pbkdf/index.js +556 -0
- package/node_modules/bcrypt-pbkdf/package.json +15 -0
- package/node_modules/safer-buffer/LICENSE +21 -0
- package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
- package/node_modules/safer-buffer/Readme.md +156 -0
- package/node_modules/safer-buffer/dangerous.js +58 -0
- package/node_modules/safer-buffer/package.json +34 -0
- package/node_modules/safer-buffer/safer.js +77 -0
- package/node_modules/safer-buffer/tests.js +406 -0
- package/node_modules/ssh2/.eslintignore +4 -0
- package/node_modules/ssh2/.eslintrc.js +5 -0
- package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
- package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
- package/node_modules/ssh2/LICENSE +19 -0
- package/node_modules/ssh2/README.md +1529 -0
- package/node_modules/ssh2/SFTP.md +413 -0
- package/node_modules/ssh2/examples/server-chat.js +238 -0
- package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
- package/node_modules/ssh2/install.js +27 -0
- package/node_modules/ssh2/lib/Channel.js +295 -0
- package/node_modules/ssh2/lib/agent.js +1123 -0
- package/node_modules/ssh2/lib/client.js +2176 -0
- package/node_modules/ssh2/lib/http-agents.js +84 -0
- package/node_modules/ssh2/lib/index.js +44 -0
- package/node_modules/ssh2/lib/keygen.js +582 -0
- package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
- package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
- package/node_modules/ssh2/lib/protocol/constants.js +356 -0
- package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
- package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
- package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
- package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
- package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
- package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
- package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
- package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
- package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
- package/node_modules/ssh2/lib/protocol/utils.js +356 -0
- package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
- package/node_modules/ssh2/lib/server.js +1380 -0
- package/node_modules/ssh2/lib/utils.js +336 -0
- package/node_modules/ssh2/package.json +49 -0
- package/node_modules/ssh2/test/common.js +316 -0
- package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
- package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
- package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
- package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
- package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
- package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
- package/node_modules/ssh2/test/test-exec.js +578 -0
- package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
- package/node_modules/ssh2/test/test-keygen.js +60 -0
- package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
- package/node_modules/ssh2/test/test-openssh.js +261 -0
- package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
- package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
- package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
- package/node_modules/ssh2/test/test-sftp.js +842 -0
- package/node_modules/ssh2/test/test-shell.js +109 -0
- package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
- package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
- package/node_modules/ssh2/test/test-userauth.js +611 -0
- package/node_modules/ssh2/test/test-worker-imports.js +25 -0
- package/node_modules/ssh2/test/test.js +20 -0
- package/node_modules/ssh2/util/build_pagent.bat +2 -0
- package/node_modules/ssh2/util/pagent.c +88 -0
- package/node_modules/ssh2/util/pagent.exe +0 -0
- package/node_modules/tweetnacl/.npmignore +4 -0
- package/node_modules/tweetnacl/AUTHORS.md +28 -0
- package/node_modules/tweetnacl/CHANGELOG.md +221 -0
- package/node_modules/tweetnacl/LICENSE +24 -0
- package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
- package/node_modules/tweetnacl/README.md +459 -0
- package/node_modules/tweetnacl/nacl-fast.js +2388 -0
- package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
- package/node_modules/tweetnacl/nacl.d.ts +98 -0
- package/node_modules/tweetnacl/nacl.js +1175 -0
- package/node_modules/tweetnacl/nacl.min.js +1 -0
- package/node_modules/tweetnacl/package.json +58 -0
- 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;
|