ai-remote 0.4.2 → 0.4.3
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/dist/index.js +1 -6
- package/dist/protocols/index.js +3 -34
- package/dist/protocols/ssh/session.js +17 -608
- package/dist/protocols/ssh/terminal.js +6 -190
- package/dist/shared/connection.js +1 -113
- package/dist/shared/hosts.js +1 -132
- package/dist/shared/icons.js +1 -41
- package/dist/shared/protocol.js +1 -39
- package/dist/shared/signals.js +1 -41
- package/package.json +1 -1
- package/dist/protocols/rdp/buffer.d.ts +0 -38
- package/dist/protocols/rdp/buffer.js +0 -169
- package/dist/protocols/rdp/caps.d.ts +0 -49
- package/dist/protocols/rdp/caps.js +0 -259
- package/dist/protocols/rdp/cert.d.ts +0 -18
- package/dist/protocols/rdp/cert.js +0 -134
- package/dist/protocols/rdp/client.d.ts +0 -56
- package/dist/protocols/rdp/client.js +0 -1069
- package/dist/protocols/rdp/cliprdr.d.ts +0 -27
- package/dist/protocols/rdp/cliprdr.js +0 -239
- package/dist/protocols/rdp/credssp.d.ts +0 -34
- package/dist/protocols/rdp/credssp.js +0 -253
- package/dist/protocols/rdp/crypto.d.ts +0 -63
- package/dist/protocols/rdp/crypto.js +0 -368
- package/dist/protocols/rdp/display.d.ts +0 -38
- package/dist/protocols/rdp/display.js +0 -588
- package/dist/protocols/rdp/gcc.d.ts +0 -23
- package/dist/protocols/rdp/gcc.js +0 -131
- package/dist/protocols/rdp/keymap.d.ts +0 -9
- package/dist/protocols/rdp/keymap.js +0 -131
- package/dist/protocols/rdp/mcs.d.ts +0 -40
- package/dist/protocols/rdp/mcs.js +0 -222
- package/dist/protocols/rdp/ntlm.d.ts +0 -61
- package/dist/protocols/rdp/ntlm.js +0 -304
- package/dist/protocols/rdp/pdu.d.ts +0 -155
- package/dist/protocols/rdp/pdu.js +0 -443
- package/dist/protocols/rdp/rail.d.ts +0 -119
- package/dist/protocols/rdp/rail.js +0 -382
- package/dist/protocols/rdp/rle.d.ts +0 -13
- package/dist/protocols/rdp/rle.js +0 -275
- package/dist/protocols/rdp/sec.d.ts +0 -59
- package/dist/protocols/rdp/sec.js +0 -155
- package/dist/protocols/rdp/session.d.ts +0 -62
- package/dist/protocols/rdp/session.js +0 -306
- package/dist/protocols/rdp/tls.d.ts +0 -18
- package/dist/protocols/rdp/tls.js +0 -353
- package/dist/protocols/rdp/vchannel.d.ts +0 -28
- package/dist/protocols/rdp/vchannel.js +0 -58
- package/dist/protocols/rdp/x224.d.ts +0 -40
- package/dist/protocols/rdp/x224.js +0 -109
- package/dist/protocols/ssh/kex.d.ts +0 -97
- package/dist/protocols/ssh/kex.js +0 -176
- package/dist/protocols/ssh/messages.d.ts +0 -50
- package/dist/protocols/ssh/messages.js +0 -54
- package/dist/protocols/ssh/transport.d.ts +0 -62
- package/dist/protocols/ssh/transport.js +0 -612
- package/dist/protocols/ssh/wire.d.ts +0 -52
- package/dist/protocols/ssh/wire.js +0 -188
- package/dist/protocols/vnc/input.d.ts +0 -5
- package/dist/protocols/vnc/input.js +0 -15
- package/dist/protocols/vnc/session.d.ts +0 -12
- package/dist/protocols/vnc/session.js +0 -258
|
@@ -1,608 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
function shellFromPrompt(text) {
|
|
19
|
-
const tail = text.slice(-400);
|
|
20
|
-
if (/(?:^|\n)PS [A-Za-z]:\\[^\n]*>\s*$/.test(tail)) return "powershell";
|
|
21
|
-
if (/(?:^|\n)[^\n]*[A-Za-z]:\\[^\n]*>\s*$/.test(tail)) return "cmd";
|
|
22
|
-
return "";
|
|
23
|
-
}
|
|
24
|
-
const FRAMING_REJECTED = /is not recognized as an internal or external command|command not found|not recognized as the name of a cmdlet|CommandNotFoundException/i;
|
|
25
|
-
function frameCommand(family, command, id) {
|
|
26
|
-
if (family === "cmd") {
|
|
27
|
-
return `echo.&echo CCB${id}&${command}&call echo.CCE${id} %^ERRORLEVEL%\r`;
|
|
28
|
-
}
|
|
29
|
-
if (family === "powershell") {
|
|
30
|
-
const encoded = toBase64(encodeUtf8(command));
|
|
31
|
-
return `Write-Output ""; Write-Output ("CCB" + "${id}"); $__ccCode = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encoded}')); $global:LASTEXITCODE = $null; . ([ScriptBlock]::Create($__ccCode)); $__ccOk = $?; $__cc = if ($LASTEXITCODE -ne $null) { $LASTEXITCODE } elseif ($__ccOk) { 0 } else { 1 }; Write-Output ("CCE" + "${id} " + $__cc)\r`;
|
|
32
|
-
}
|
|
33
|
-
return `printf '\\n%s%s\\n' 'CCB' '${id}'; eval ${singleQuote(command)}; __cc=$?; printf '\\n%s%s %s\\n' 'CCE' '${id}' "$__cc"\r`;
|
|
34
|
-
}
|
|
35
|
-
function stripTerminalCodes(text) {
|
|
36
|
-
return text.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, "").replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\u001b[@-Z\\-_]/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "");
|
|
37
|
-
}
|
|
38
|
-
function filterDisplay(session, bytes) {
|
|
39
|
-
const filter = session.displayFilter;
|
|
40
|
-
if (!filter) return bytes;
|
|
41
|
-
filter.pending += decodeUtf8(bytes);
|
|
42
|
-
let shown = "";
|
|
43
|
-
if (filter.stage === "before") {
|
|
44
|
-
const at = filter.pending.indexOf(filter.begin);
|
|
45
|
-
if (at === -1) return NO_BYTES;
|
|
46
|
-
filter.pending = filter.pending.slice(at + filter.begin.length).replace(/^\r?\n/, "");
|
|
47
|
-
filter.stage = "inside";
|
|
48
|
-
}
|
|
49
|
-
if (filter.stage === "inside") {
|
|
50
|
-
const at = filter.pending.indexOf(filter.end);
|
|
51
|
-
if (at === -1) {
|
|
52
|
-
shown = filter.pending;
|
|
53
|
-
filter.pending = "";
|
|
54
|
-
return shown ? encodeUtf8(shown) : NO_BYTES;
|
|
55
|
-
}
|
|
56
|
-
shown = filter.pending.slice(0, at);
|
|
57
|
-
filter.pending = filter.pending.slice(at);
|
|
58
|
-
filter.stage = "closing";
|
|
59
|
-
}
|
|
60
|
-
const newline = filter.pending.indexOf("\n");
|
|
61
|
-
if (newline !== -1) {
|
|
62
|
-
shown += filter.pending.slice(newline + 1);
|
|
63
|
-
session.displayFilter = null;
|
|
64
|
-
}
|
|
65
|
-
return shown ? encodeUtf8(shown) : NO_BYTES;
|
|
66
|
-
}
|
|
67
|
-
function releaseDisplayFilter(session) {
|
|
68
|
-
const filter = session.displayFilter;
|
|
69
|
-
session.displayFilter = null;
|
|
70
|
-
if (!filter || filter.stage !== "before" || !filter.pending) return;
|
|
71
|
-
session.dispatchEvent(new CustomEvent("data", {
|
|
72
|
-
detail: { bytes: NO_BYTES, display: encodeUtf8(filter.pending), isStderr: false }
|
|
73
|
-
}));
|
|
74
|
-
}
|
|
75
|
-
class SshSession extends EventTarget {
|
|
76
|
-
/**
|
|
77
|
-
* @param {string} url gateway WebSocket URL
|
|
78
|
-
* @param {object} options
|
|
79
|
-
* @param {string} options.username
|
|
80
|
-
* @param {string} [options.password]
|
|
81
|
-
* @param {'powershell'} [options.preferredShell]
|
|
82
|
-
* @param {(info: object) => Promise<boolean>} [options.verifyHost]
|
|
83
|
-
* @param {(prompt: object) => Promise<string>} [options.requestInput]
|
|
84
|
-
*/
|
|
85
|
-
constructor(url, options = {}) {
|
|
86
|
-
super();
|
|
87
|
-
this.options = options;
|
|
88
|
-
this.username = options.username || "";
|
|
89
|
-
this.password = options.password || "";
|
|
90
|
-
this.requestInput = options.requestInput || (async () => "");
|
|
91
|
-
this.columns = options.columns || 80;
|
|
92
|
-
this.rows = options.rows || 24;
|
|
93
|
-
this.channelId = 0;
|
|
94
|
-
this.remoteChannelId = null;
|
|
95
|
-
this.remoteWindow = 0;
|
|
96
|
-
this.remoteMaxPacket = MAX_PACKET;
|
|
97
|
-
this.localWindow = INITIAL_WINDOW;
|
|
98
|
-
this.pendingWrites = [];
|
|
99
|
-
this.shellOpen = false;
|
|
100
|
-
this.commandInFlight = false;
|
|
101
|
-
this.displayFilter = null;
|
|
102
|
-
this.readySignaled = false;
|
|
103
|
-
this.powerShellAttempted = false;
|
|
104
|
-
this.powerShellTimer = null;
|
|
105
|
-
this.shellFamily = "";
|
|
106
|
-
this.promptTail = "";
|
|
107
|
-
this.exitStatus = null;
|
|
108
|
-
this.authMethods = [];
|
|
109
|
-
this.closed = false;
|
|
110
|
-
this.waiters = /* @__PURE__ */ new Map();
|
|
111
|
-
this.transport = new SshTransport(url, {
|
|
112
|
-
verifyHost: options.verifyHost || (async () => true),
|
|
113
|
-
openTransport: options.openTransport || null,
|
|
114
|
-
log: (step, detail) => console.info(`[SSH] ${step}`, detail)
|
|
115
|
-
});
|
|
116
|
-
this.transport.addEventListener("packet", (event) => {
|
|
117
|
-
this.#handlePacket(event.detail.type, event.detail.payload);
|
|
118
|
-
});
|
|
119
|
-
this.transport.addEventListener("greeting", (event) => {
|
|
120
|
-
for (const line of event.detail.lines) this.#emitBanner(`${line}\r
|
|
121
|
-
`);
|
|
122
|
-
});
|
|
123
|
-
this.transport.addEventListener("banner", (event) => this.#emitBanner(event.detail.text));
|
|
124
|
-
this.transport.addEventListener("keys", (event) => {
|
|
125
|
-
this.fingerprint = event.detail.fingerprint;
|
|
126
|
-
this.#status("secured", `Host key ${event.detail.fingerprint} verified.`);
|
|
127
|
-
this.dispatchEvent(new CustomEvent("secure", { detail: event.detail }));
|
|
128
|
-
if (event.detail.firstTime) void this.#authenticate();
|
|
129
|
-
});
|
|
130
|
-
this.transport.addEventListener("hostdisconnect", (event) => {
|
|
131
|
-
this.lastMessage = event.detail.reason;
|
|
132
|
-
});
|
|
133
|
-
this.transport.addEventListener("error", (event) => {
|
|
134
|
-
this.lastMessage = event.detail.message;
|
|
135
|
-
this.dispatchEvent(new CustomEvent("error", { detail: event.detail }));
|
|
136
|
-
});
|
|
137
|
-
this.transport.addEventListener("close", (event) => {
|
|
138
|
-
if (this.closed) return;
|
|
139
|
-
this.closed = true;
|
|
140
|
-
clearTimeout(this.powerShellTimer);
|
|
141
|
-
this.#abandonWaiters(event.detail.message || "The SSH connection closed.");
|
|
142
|
-
this.dispatchEvent(new CustomEvent("close", {
|
|
143
|
-
detail: {
|
|
144
|
-
...event.detail,
|
|
145
|
-
message: event.detail.message || this.lastMessage || "",
|
|
146
|
-
exitStatus: this.exitStatus
|
|
147
|
-
}
|
|
148
|
-
}));
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
connect() {
|
|
152
|
-
this.#status("connecting", "Opening the SSH transport\u2026");
|
|
153
|
-
this.transport.connect();
|
|
154
|
-
}
|
|
155
|
-
disconnect() {
|
|
156
|
-
if (this.closed) return;
|
|
157
|
-
clearTimeout(this.powerShellTimer);
|
|
158
|
-
if (this.shellOpen && this.remoteChannelId !== null) {
|
|
159
|
-
this.transport.send(new SshWriter(8).u8(MSG.CHANNEL_CLOSE).u32(this.remoteChannelId).take());
|
|
160
|
-
}
|
|
161
|
-
this.transport.disconnect("Closed by the user");
|
|
162
|
-
}
|
|
163
|
-
/** Send keystrokes, or anything else the terminal produces, to the shell. */
|
|
164
|
-
write(data) {
|
|
165
|
-
const bytes = typeof data === "string" ? encodeUtf8(data) : data;
|
|
166
|
-
if (!bytes.length) return;
|
|
167
|
-
this.pendingWrites.push(bytes);
|
|
168
|
-
this.#flushWrites();
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Run one command in this shell and return what it printed.
|
|
172
|
-
*
|
|
173
|
-
* Deliberately the *same* shell the user is looking at, not a second channel:
|
|
174
|
-
* a command the assistant runs inherits the working directory, the
|
|
175
|
-
* environment and the login the user already has, and it appears in their
|
|
176
|
-
* terminal as it happens. An agent acting on a machine invisibly is worse
|
|
177
|
-
* than an agent that is slower to read.
|
|
178
|
-
*
|
|
179
|
-
* The output is framed by markers rather than measured, because an
|
|
180
|
-
* interactive shell echoes its input and prints a prompt around it. The
|
|
181
|
-
* markers are assembled by the shell from two pieces, so the echoed command
|
|
182
|
-
* line -- which contains the pieces but not the joined marker -- never looks
|
|
183
|
-
* like the real boundary.
|
|
184
|
-
*
|
|
185
|
-
* A `command` event brackets the run so the terminal can label it, and the
|
|
186
|
-
* three deadlines above decide when a command is stuck rather than slow.
|
|
187
|
-
*/
|
|
188
|
-
runCommand(command, {
|
|
189
|
-
startMs = COMMAND_START_MS,
|
|
190
|
-
idleMs = COMMAND_IDLE_MS,
|
|
191
|
-
maxMs = COMMAND_MAX_MS
|
|
192
|
-
} = {}) {
|
|
193
|
-
const text = String(command || "").trim();
|
|
194
|
-
if (!text) return Promise.reject(new Error("No command was given."));
|
|
195
|
-
if (!this.shellOpen || this.closed) {
|
|
196
|
-
return Promise.reject(new Error("The SSH shell is not open."));
|
|
197
|
-
}
|
|
198
|
-
if (this.readySignaled === false) {
|
|
199
|
-
return Promise.reject(new Error("The SSH shell is still starting."));
|
|
200
|
-
}
|
|
201
|
-
if (this.commandInFlight) {
|
|
202
|
-
return Promise.reject(new Error("Another command is still running in this shell."));
|
|
203
|
-
}
|
|
204
|
-
const id = Array.from(crypto.getRandomValues(new Uint8Array(6))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
205
|
-
const begin = `CCB${id}`;
|
|
206
|
-
const end = `CCE${id}`;
|
|
207
|
-
const family = this.shellFamily || "posix";
|
|
208
|
-
this.commandInFlight = true;
|
|
209
|
-
const startedAt = Date.now();
|
|
210
|
-
this.displayFilter = { begin, end, stage: "before", pending: "" };
|
|
211
|
-
this.dispatchEvent(new CustomEvent("command", {
|
|
212
|
-
detail: { phase: "start", id, command: text }
|
|
213
|
-
}));
|
|
214
|
-
return new Promise((resolve, reject) => {
|
|
215
|
-
let transcript = "";
|
|
216
|
-
let started = false;
|
|
217
|
-
let startTimer = null;
|
|
218
|
-
let idleTimer = null;
|
|
219
|
-
let maxTimer = null;
|
|
220
|
-
const finish = (settle) => {
|
|
221
|
-
clearTimeout(startTimer);
|
|
222
|
-
clearTimeout(idleTimer);
|
|
223
|
-
clearTimeout(maxTimer);
|
|
224
|
-
this.removeEventListener("data", onData);
|
|
225
|
-
this.removeEventListener("close", onClose);
|
|
226
|
-
this.commandInFlight = false;
|
|
227
|
-
releaseDisplayFilter(this);
|
|
228
|
-
settle();
|
|
229
|
-
};
|
|
230
|
-
const done = (result) => finish(() => {
|
|
231
|
-
this.dispatchEvent(new CustomEvent("command", {
|
|
232
|
-
detail: {
|
|
233
|
-
phase: "end",
|
|
234
|
-
id,
|
|
235
|
-
command: text,
|
|
236
|
-
exitStatus: result.exitStatus,
|
|
237
|
-
timedOut: result.timedOut,
|
|
238
|
-
started: result.started !== false,
|
|
239
|
-
reason: result.reason,
|
|
240
|
-
durationMs: result.durationMs
|
|
241
|
-
}
|
|
242
|
-
}));
|
|
243
|
-
resolve(result);
|
|
244
|
-
});
|
|
245
|
-
const fail = (error) => finish(() => {
|
|
246
|
-
this.dispatchEvent(new CustomEvent("command", {
|
|
247
|
-
detail: {
|
|
248
|
-
phase: "end",
|
|
249
|
-
id,
|
|
250
|
-
command: text,
|
|
251
|
-
exitStatus: null,
|
|
252
|
-
timedOut: false,
|
|
253
|
-
started,
|
|
254
|
-
durationMs: Date.now() - startedAt,
|
|
255
|
-
error: error.message
|
|
256
|
-
}
|
|
257
|
-
}));
|
|
258
|
-
reject(error);
|
|
259
|
-
});
|
|
260
|
-
const partialOutput = () => {
|
|
261
|
-
const at = transcript.indexOf(begin);
|
|
262
|
-
return at === -1 ? "" : stripTerminalCodes(transcript.slice(at + begin.length)).replace(/^\r?\n/, "").replace(/\s+$/, "");
|
|
263
|
-
};
|
|
264
|
-
const giveUp = (reason) => {
|
|
265
|
-
this.write("");
|
|
266
|
-
done({
|
|
267
|
-
output: partialOutput(),
|
|
268
|
-
exitStatus: null,
|
|
269
|
-
timedOut: true,
|
|
270
|
-
started,
|
|
271
|
-
reason,
|
|
272
|
-
durationMs: Date.now() - startedAt
|
|
273
|
-
});
|
|
274
|
-
};
|
|
275
|
-
const onData = (event) => {
|
|
276
|
-
transcript += decodeUtf8(event.detail.bytes);
|
|
277
|
-
if (!started && transcript.indexOf(begin) !== -1) {
|
|
278
|
-
started = true;
|
|
279
|
-
clearTimeout(startTimer);
|
|
280
|
-
startTimer = null;
|
|
281
|
-
}
|
|
282
|
-
if (started) {
|
|
283
|
-
clearTimeout(idleTimer);
|
|
284
|
-
idleTimer = setTimeout(() => giveUp("idle"), idleMs);
|
|
285
|
-
}
|
|
286
|
-
const endAt = transcript.indexOf(`${end} `);
|
|
287
|
-
if (endAt === -1) {
|
|
288
|
-
if (!started && FRAMING_REJECTED.test(stripTerminalCodes(transcript))) {
|
|
289
|
-
const guessed = shellFromPrompt(stripTerminalCodes(transcript)) || "cmd";
|
|
290
|
-
const wrongGuess = family !== guessed;
|
|
291
|
-
this.shellFamily = guessed;
|
|
292
|
-
fail(new Error(
|
|
293
|
-
wrongGuess ? `This shell is ${guessed === "powershell" ? "PowerShell" : "Windows cmd"}, not a POSIX shell, and it rejected the wrapper used to capture output. The shell has been noted; run the command again and it will be framed correctly.` : `The shell rejected the wrapper used to capture output: ${stripTerminalCodes(transcript).split("\n").filter(Boolean).pop() || "no detail"}`
|
|
294
|
-
));
|
|
295
|
-
}
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const tail = transcript.slice(endAt);
|
|
299
|
-
const status = parseInt(tail.slice(end.length + 1).trim(), 10);
|
|
300
|
-
const beginAt = transcript.indexOf(begin);
|
|
301
|
-
const body = beginAt === -1 ? transcript.slice(0, endAt) : transcript.slice(beginAt + begin.length, endAt);
|
|
302
|
-
done({
|
|
303
|
-
output: stripTerminalCodes(body).replace(/^\r?\n/, "").replace(/\s+$/, ""),
|
|
304
|
-
exitStatus: Number.isInteger(status) ? status : null,
|
|
305
|
-
timedOut: false,
|
|
306
|
-
started: true,
|
|
307
|
-
durationMs: Date.now() - startedAt
|
|
308
|
-
});
|
|
309
|
-
};
|
|
310
|
-
const onClose = () => fail(new Error("The SSH session closed while the command was running."));
|
|
311
|
-
this.addEventListener("data", onData);
|
|
312
|
-
this.addEventListener("close", onClose);
|
|
313
|
-
startTimer = setTimeout(() => giveUp("start"), startMs);
|
|
314
|
-
idleTimer = setTimeout(() => giveUp("idle"), idleMs);
|
|
315
|
-
maxTimer = setTimeout(() => giveUp("max"), maxMs);
|
|
316
|
-
this.write(frameCommand(family, text, id));
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
/** Tell the shell its window changed, so full-screen programs redraw. */
|
|
320
|
-
resize(columns, rows) {
|
|
321
|
-
const nextColumns = Math.max(1, Math.floor(columns));
|
|
322
|
-
const nextRows = Math.max(1, Math.floor(rows));
|
|
323
|
-
if (nextColumns === this.columns && nextRows === this.rows) return;
|
|
324
|
-
this.columns = nextColumns;
|
|
325
|
-
this.rows = nextRows;
|
|
326
|
-
if (!this.shellOpen || this.remoteChannelId === null) return;
|
|
327
|
-
this.transport.send(new SshWriter(64).u8(MSG.CHANNEL_REQUEST).u32(this.remoteChannelId).string("window-change").boolean(false).u32(nextColumns).u32(nextRows).u32(0).u32(0).take());
|
|
328
|
-
}
|
|
329
|
-
// --- plumbing ------------------------------------------------------------
|
|
330
|
-
#status(phase, message) {
|
|
331
|
-
this.dispatchEvent(new CustomEvent("status", { detail: { phase, message } }));
|
|
332
|
-
}
|
|
333
|
-
#emitBanner(text) {
|
|
334
|
-
if (text) this.dispatchEvent(new CustomEvent("banner", { detail: { text } }));
|
|
335
|
-
}
|
|
336
|
-
/** Wait for one of a few message types: the handshakes read linearly that way. */
|
|
337
|
-
#expect(...types) {
|
|
338
|
-
return new Promise((resolve, reject) => {
|
|
339
|
-
for (const type of types) this.waiters.set(type, { resolve, reject });
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
#settle(type, payload) {
|
|
343
|
-
const waiter = this.waiters.get(type);
|
|
344
|
-
if (!waiter) return false;
|
|
345
|
-
this.waiters.clear();
|
|
346
|
-
waiter.resolve({ type, payload });
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
/** A closed transport must not leave a handshake waiting forever. */
|
|
350
|
-
#abandonWaiters(message) {
|
|
351
|
-
if (this.waiters.size === 0) return;
|
|
352
|
-
const pending = [...new Set(this.waiters.values())];
|
|
353
|
-
this.waiters.clear();
|
|
354
|
-
for (const waiter of pending) waiter.reject(new Error(message));
|
|
355
|
-
}
|
|
356
|
-
#handlePacket(type, payload) {
|
|
357
|
-
if (this.#settle(type, payload)) return;
|
|
358
|
-
switch (type) {
|
|
359
|
-
case MSG.USERAUTH_BANNER:
|
|
360
|
-
this.#emitBanner(new SshReader(payload, 1).string().replaceAll("\n", "\r\n"));
|
|
361
|
-
return;
|
|
362
|
-
case MSG.CHANNEL_DATA: {
|
|
363
|
-
const reader = new SshReader(payload, 1);
|
|
364
|
-
reader.u32();
|
|
365
|
-
this.#consume(reader.stringBytes());
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
case MSG.CHANNEL_EXTENDED_DATA: {
|
|
369
|
-
const reader = new SshReader(payload, 1);
|
|
370
|
-
reader.u32();
|
|
371
|
-
const dataType = reader.u32();
|
|
372
|
-
const bytes = reader.stringBytes();
|
|
373
|
-
this.#consume(bytes, dataType === EXTENDED_DATA_STDERR);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
case MSG.CHANNEL_WINDOW_ADJUST: {
|
|
377
|
-
const reader = new SshReader(payload, 1);
|
|
378
|
-
reader.u32();
|
|
379
|
-
this.remoteWindow += reader.u32();
|
|
380
|
-
this.#flushWrites();
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
case MSG.CHANNEL_REQUEST: {
|
|
384
|
-
const reader = new SshReader(payload, 1);
|
|
385
|
-
reader.u32();
|
|
386
|
-
const request = reader.string();
|
|
387
|
-
const wantReply = reader.boolean();
|
|
388
|
-
if (request === "exit-status") this.exitStatus = reader.u32();
|
|
389
|
-
if (request === "exit-signal") this.exitSignal = reader.string();
|
|
390
|
-
if (wantReply && this.remoteChannelId !== null) {
|
|
391
|
-
this.transport.send(new SshWriter(8).u8(MSG.CHANNEL_SUCCESS).u32(this.remoteChannelId).take());
|
|
392
|
-
}
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
case MSG.CHANNEL_EOF:
|
|
396
|
-
return;
|
|
397
|
-
case MSG.CHANNEL_CLOSE:
|
|
398
|
-
this.shellOpen = false;
|
|
399
|
-
this.#status("closed", "The remote shell ended.");
|
|
400
|
-
this.transport.disconnect("Shell closed");
|
|
401
|
-
return;
|
|
402
|
-
default:
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Watch the prompt for which shell this machine answers with, so the first
|
|
408
|
-
* command is framed correctly rather than discovering it by failing.
|
|
409
|
-
*/
|
|
410
|
-
#noteShellFamily(bytes) {
|
|
411
|
-
this.promptTail = (this.promptTail + stripTerminalCodes(decodeUtf8(bytes))).slice(-400);
|
|
412
|
-
const family = shellFromPrompt(this.promptTail);
|
|
413
|
-
if (!family) return;
|
|
414
|
-
if (family === "cmd" && !this.powerShellAttempted) {
|
|
415
|
-
this.#startPowerShell();
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
if (family === "cmd" && this.powerShellAttempted && !this.readySignaled) return;
|
|
419
|
-
this.shellFamily = family;
|
|
420
|
-
if (family === "powershell" && !this.readySignaled) {
|
|
421
|
-
this.#markReady("Connected \xB7 PowerShell");
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
/** Replace Windows OpenSSH's usual cmd.exe with an interactive PowerShell. */
|
|
425
|
-
#startPowerShell() {
|
|
426
|
-
if (this.powerShellAttempted || !this.shellOpen) return;
|
|
427
|
-
this.powerShellAttempted = true;
|
|
428
|
-
this.readySignaled = false;
|
|
429
|
-
this.shellFamily = "";
|
|
430
|
-
this.#status("opening", "Starting PowerShell\u2026");
|
|
431
|
-
this.write("powershell.exe -NoLogo\r");
|
|
432
|
-
this.powerShellTimer = setTimeout(() => {
|
|
433
|
-
if (this.readySignaled || this.closed) return;
|
|
434
|
-
this.shellFamily = "powershell";
|
|
435
|
-
this.#markReady("Connected \xB7 PowerShell");
|
|
436
|
-
}, 2e3);
|
|
437
|
-
}
|
|
438
|
-
#markReady(message = "Connected.") {
|
|
439
|
-
clearTimeout(this.powerShellTimer);
|
|
440
|
-
this.powerShellTimer = null;
|
|
441
|
-
this.readySignaled = true;
|
|
442
|
-
this.#status("ready", message);
|
|
443
|
-
this.dispatchEvent(new CustomEvent("ready", {
|
|
444
|
-
detail: { fingerprint: this.fingerprint, shellFamily: this.shellFamily || "posix" }
|
|
445
|
-
}));
|
|
446
|
-
}
|
|
447
|
-
/** Shell output, with the receive window topped up before it runs dry. */
|
|
448
|
-
#consume(bytes, isStderr = false) {
|
|
449
|
-
if (!bytes.length) return;
|
|
450
|
-
this.#noteShellFamily(bytes);
|
|
451
|
-
this.dispatchEvent(new CustomEvent("data", {
|
|
452
|
-
detail: { bytes, display: filterDisplay(this, bytes), isStderr }
|
|
453
|
-
}));
|
|
454
|
-
this.localWindow -= bytes.length;
|
|
455
|
-
if (this.localWindow > WINDOW_REFILL_THRESHOLD || this.remoteChannelId === null) return;
|
|
456
|
-
const increment = INITIAL_WINDOW - this.localWindow;
|
|
457
|
-
this.localWindow = INITIAL_WINDOW;
|
|
458
|
-
this.transport.send(new SshWriter(16).u8(MSG.CHANNEL_WINDOW_ADJUST).u32(this.remoteChannelId).u32(increment).take());
|
|
459
|
-
}
|
|
460
|
-
#flushWrites() {
|
|
461
|
-
if (!this.shellOpen || this.remoteChannelId === null) return;
|
|
462
|
-
while (this.pendingWrites.length > 0) {
|
|
463
|
-
const next = this.pendingWrites[0];
|
|
464
|
-
const room = Math.min(this.remoteWindow, this.remoteMaxPacket - 64);
|
|
465
|
-
if (room <= 0) return;
|
|
466
|
-
const chunk = next.length <= room ? next : next.subarray(0, room);
|
|
467
|
-
if (chunk.length === next.length) this.pendingWrites.shift();
|
|
468
|
-
else this.pendingWrites[0] = next.subarray(chunk.length);
|
|
469
|
-
this.remoteWindow -= chunk.length;
|
|
470
|
-
this.transport.send(new SshWriter(chunk.length + 16).u8(MSG.CHANNEL_DATA).u32(this.remoteChannelId).string(chunk).take());
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
// --- authentication ------------------------------------------------------
|
|
474
|
-
async #authenticate() {
|
|
475
|
-
try {
|
|
476
|
-
this.#status("authenticating", `Authenticating as ${this.username}\u2026`);
|
|
477
|
-
this.transport.send(new SshWriter(32).u8(MSG.SERVICE_REQUEST).string("ssh-userauth").take());
|
|
478
|
-
await this.#expect(MSG.SERVICE_ACCEPT);
|
|
479
|
-
if (await this.#tryNone()) return void await this.#openShell();
|
|
480
|
-
if (this.authMethods.includes("password") && this.password) {
|
|
481
|
-
if (await this.#tryPassword(this.password)) return void await this.#openShell();
|
|
482
|
-
}
|
|
483
|
-
if (this.authMethods.includes("keyboard-interactive")) {
|
|
484
|
-
if (await this.#tryKeyboardInteractive()) return void await this.#openShell();
|
|
485
|
-
if (this.authMethods.includes("keyboard-interactive")) {
|
|
486
|
-
if (await this.#tryKeyboardInteractive({ usePassword: false })) {
|
|
487
|
-
return void await this.#openShell();
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (this.authMethods.includes("password")) {
|
|
492
|
-
const typed = await this.requestInput({
|
|
493
|
-
prompt: `${this.username}@${this.options.hostLabel || "host"}'s password: `,
|
|
494
|
-
echo: false
|
|
495
|
-
});
|
|
496
|
-
if (typed && await this.#tryPassword(typed)) return void await this.#openShell();
|
|
497
|
-
}
|
|
498
|
-
throw new Error(this.authMethods.length ? `SSH: authentication failed. The host accepts ${this.authMethods.join(", ")}.` : "SSH: the host rejected the connection before any authentication method was offered.");
|
|
499
|
-
} catch (error) {
|
|
500
|
-
if (!this.closed) this.transport.fail(error);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
async #tryNone() {
|
|
504
|
-
this.transport.send(new SshWriter(64).u8(MSG.USERAUTH_REQUEST).string(this.username).string(SERVICE).string("none").take());
|
|
505
|
-
return this.#authResult();
|
|
506
|
-
}
|
|
507
|
-
async #tryPassword(password) {
|
|
508
|
-
this.transport.send(new SshWriter(128).u8(MSG.USERAUTH_REQUEST).string(this.username).string(SERVICE).string("password").boolean(false).string(password).take());
|
|
509
|
-
return this.#authResult();
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* keyboard-interactive is how most Linux hosts actually ask for a password,
|
|
513
|
-
* and the only way a one-time code can be entered. Prompts are answered from
|
|
514
|
-
* the password when one is obviously being asked for, and by the user
|
|
515
|
-
* otherwise.
|
|
516
|
-
*/
|
|
517
|
-
async #tryKeyboardInteractive({ usePassword = true } = {}) {
|
|
518
|
-
this.transport.send(new SshWriter(96).u8(MSG.USERAUTH_REQUEST).string(this.username).string(SERVICE).string("keyboard-interactive").string("").string("").take());
|
|
519
|
-
let usedPassword = !usePassword;
|
|
520
|
-
while (true) {
|
|
521
|
-
const { type, payload } = await this.#expect(
|
|
522
|
-
MSG.USERAUTH_SUCCESS,
|
|
523
|
-
MSG.USERAUTH_FAILURE,
|
|
524
|
-
MSG.USERAUTH_INFO_REQUEST
|
|
525
|
-
);
|
|
526
|
-
if (type === MSG.USERAUTH_SUCCESS) return true;
|
|
527
|
-
if (type === MSG.USERAUTH_FAILURE) {
|
|
528
|
-
this.#readFailure(payload);
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
const reader = new SshReader(payload, 1);
|
|
532
|
-
const name = reader.string();
|
|
533
|
-
const instruction = reader.string();
|
|
534
|
-
reader.string();
|
|
535
|
-
const promptCount = reader.u32();
|
|
536
|
-
if (name) this.#emitBanner(`${name.replaceAll("\n", "\r\n")}\r
|
|
537
|
-
`);
|
|
538
|
-
if (instruction) this.#emitBanner(`${instruction.replaceAll("\n", "\r\n")}\r
|
|
539
|
-
`);
|
|
540
|
-
const answers = [];
|
|
541
|
-
for (let index = 0; index < promptCount; index++) {
|
|
542
|
-
const prompt = reader.string();
|
|
543
|
-
const echo = reader.boolean();
|
|
544
|
-
const looksLikePassword = !echo && /pass(word|phrase)/i.test(prompt);
|
|
545
|
-
if (looksLikePassword && this.password && !usedPassword) {
|
|
546
|
-
usedPassword = true;
|
|
547
|
-
answers.push(this.password);
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
answers.push(await this.requestInput({ prompt, echo }));
|
|
551
|
-
}
|
|
552
|
-
const response = new SshWriter(256).u8(MSG.USERAUTH_INFO_RESPONSE).u32(answers.length);
|
|
553
|
-
for (const answer of answers) response.string(answer);
|
|
554
|
-
this.transport.send(response.take());
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
async #authResult() {
|
|
558
|
-
const { type, payload } = await this.#expect(MSG.USERAUTH_SUCCESS, MSG.USERAUTH_FAILURE);
|
|
559
|
-
if (type === MSG.USERAUTH_SUCCESS) return true;
|
|
560
|
-
this.#readFailure(payload);
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
#readFailure(payload) {
|
|
564
|
-
const reader = new SshReader(payload, 1);
|
|
565
|
-
this.authMethods = reader.nameList();
|
|
566
|
-
const partial = reader.boolean();
|
|
567
|
-
if (partial) this.#emitBanner("\r\nOne factor accepted; the host wants another.\r\n");
|
|
568
|
-
}
|
|
569
|
-
// --- the shell -----------------------------------------------------------
|
|
570
|
-
async #openShell() {
|
|
571
|
-
this.#status("opening", "Starting the remote shell\u2026");
|
|
572
|
-
this.transport.send(new SshWriter(64).u8(MSG.CHANNEL_OPEN).string("session").u32(this.channelId).u32(INITIAL_WINDOW).u32(MAX_PACKET).take());
|
|
573
|
-
const opened = await this.#expect(MSG.CHANNEL_OPEN_CONFIRMATION, MSG.CHANNEL_OPEN_FAILURE);
|
|
574
|
-
if (opened.type === MSG.CHANNEL_OPEN_FAILURE) {
|
|
575
|
-
const reader2 = new SshReader(opened.payload, 1);
|
|
576
|
-
reader2.u32();
|
|
577
|
-
reader2.u32();
|
|
578
|
-
throw new Error(`SSH: the host refused to open a session channel (${reader2.string()}).`);
|
|
579
|
-
}
|
|
580
|
-
const reader = new SshReader(opened.payload, 1);
|
|
581
|
-
reader.u32();
|
|
582
|
-
this.remoteChannelId = reader.u32();
|
|
583
|
-
this.remoteWindow = reader.u32();
|
|
584
|
-
this.remoteMaxPacket = Math.max(1024, Math.min(reader.u32(), MAX_PACKET));
|
|
585
|
-
this.transport.send(new SshWriter(128).u8(MSG.CHANNEL_REQUEST).u32(this.remoteChannelId).string("pty-req").boolean(true).string(TERMINAL_TYPE).u32(this.columns).u32(this.rows).u32(0).u32(0).string(PTY_MODES).take());
|
|
586
|
-
const pty = await this.#expect(MSG.CHANNEL_SUCCESS, MSG.CHANNEL_FAILURE);
|
|
587
|
-
if (pty.type === MSG.CHANNEL_FAILURE) {
|
|
588
|
-
throw new Error("SSH: the host refused a pseudo-terminal, so there is no interactive shell to show.");
|
|
589
|
-
}
|
|
590
|
-
this.transport.send(new SshWriter(32).u8(MSG.CHANNEL_REQUEST).u32(this.remoteChannelId).string("shell").boolean(true).take());
|
|
591
|
-
const shell = await this.#expect(MSG.CHANNEL_SUCCESS, MSG.CHANNEL_FAILURE);
|
|
592
|
-
if (shell.type === MSG.CHANNEL_FAILURE) {
|
|
593
|
-
throw new Error("SSH: the host refused to start a shell for this account.");
|
|
594
|
-
}
|
|
595
|
-
this.shellOpen = true;
|
|
596
|
-
this.#flushWrites();
|
|
597
|
-
if (this.options.preferredShell === "powershell") this.#startPowerShell();
|
|
598
|
-
else this.#markReady();
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
export {
|
|
602
|
-
COMMAND_IDLE_MS,
|
|
603
|
-
COMMAND_MAX_MS,
|
|
604
|
-
COMMAND_START_MS,
|
|
605
|
-
DEFAULT_PORT,
|
|
606
|
-
SshSession,
|
|
607
|
-
frameCommand
|
|
608
|
-
};
|
|
1
|
+
var o={DISCONNECT:1,IGNORE:2,UNIMPLEMENTED:3,DEBUG:4,SERVICE_REQUEST:5,SERVICE_ACCEPT:6,EXT_INFO:7,KEXINIT:20,NEWKEYS:21,KEX_ECDH_INIT:30,KEX_ECDH_REPLY:31,USERAUTH_REQUEST:50,USERAUTH_FAILURE:51,USERAUTH_SUCCESS:52,USERAUTH_BANNER:53,USERAUTH_INFO_REQUEST:60,USERAUTH_INFO_RESPONSE:61,GLOBAL_REQUEST:80,REQUEST_SUCCESS:81,REQUEST_FAILURE:82,CHANNEL_OPEN:90,CHANNEL_OPEN_CONFIRMATION:91,CHANNEL_OPEN_FAILURE:92,CHANNEL_WINDOW_ADJUST:93,CHANNEL_DATA:94,CHANNEL_EXTENDED_DATA:95,CHANNEL_EOF:96,CHANNEL_CLOSE:97,CHANNEL_REQUEST:98,CHANNEL_SUCCESS:99,CHANNEL_FAILURE:100},se=1,re={1:"the host reported a protocol error",2:"the host could not agree on a key exchange method",3:"the key exchange failed",5:"the host ran out of resources",6:"the connection was lost below SSH",7:"the host application ended the connection",8:"too many connections",9:"authentication was cancelled by the user",10:"no more authentication methods are available",11:"the host closed the connection",12:"authentication was cancelled by the host",14:"the host refused the authentication attempt",15:"the host closed the connection while authenticating"};var Ue=new TextEncoder,ve=new TextDecoder;function S(s){return Ue.encode(s)}function k(s){return ve.decode(s)}function m(...s){let e=s.reduce((r,i)=>r+i.length,0),t=new Uint8Array(e),n=0;for(let r of s)t.set(r,n),n+=r.length;return t}function x(s){let e="";for(let t of s)e+=String.fromCharCode(t);return btoa(e)}function X(s){return x(s).replaceAll("+","-").replaceAll("/","_").replaceAll("=","")}var c=class{bytes;view;offset=0;constructor(e=256){this.bytes=new Uint8Array(new ArrayBuffer(e)),this.view=new DataView(this.bytes.buffer)}#e(e){if(this.offset+e<=this.bytes.length)return;let t=Math.max(this.bytes.length*2,64);for(;t<this.offset+e;)t*=2;let n=new Uint8Array(new ArrayBuffer(t));n.set(this.bytes.subarray(0,this.offset)),this.bytes=n,this.view=new DataView(n.buffer)}u8(e){return this.#e(1),this.view.setUint8(this.offset,e),this.offset+=1,this}boolean(e){return this.u8(e?1:0)}u32(e){return this.#e(4),this.view.setUint32(this.offset,e>>>0,!1),this.offset+=4,this}raw(e){return this.#e(e.length),this.bytes.set(e,this.offset),this.offset+=e.length,this}string(e){let t=typeof e=="string"?S(e):e;return this.u32(t.length).raw(t)}nameList(e){return this.string(e.join(","))}mpint(e){let t=0;for(;t<e.length&&e[t]===0;)t++;let n=e.subarray(t);return n.length===0?this.u32(0):(n[0]??0)&128?(this.u32(n.length+1).u8(0),this.raw(n)):this.u32(n.length).raw(n)}get length(){return this.offset}take(){return this.bytes.slice(0,this.offset)}},u=class{bytes;view;offset;constructor(e,t=0){this.bytes=e,this.view=new DataView(e.buffer,e.byteOffset,e.byteLength),this.offset=t}get remaining(){return this.bytes.length-this.offset}#e(e){if(e<0||this.remaining<e)throw new Error(`SSH: truncated packet (needed ${e} bytes, ${this.remaining} left)`)}u8(){return this.#e(1),this.view.getUint8(this.offset++)}boolean(){return this.u8()!==0}u32(){this.#e(4);let e=this.view.getUint32(this.offset,!1);return this.offset+=4,e}raw(e){this.#e(e);let t=this.bytes.subarray(this.offset,this.offset+e);return this.offset+=e,t}stringBytes(){return this.raw(this.u32())}string(){return k(this.stringBytes())}nameList(){let e=this.string();return e?e.split(","):[]}mpint(){let e=this.stringBytes(),t=0;for(;t<e.length&&e[t]===0;)t++;return e.subarray(t)}skip(e){return this.#e(e),this.offset+=e,this}rest(){return this.bytes.subarray(this.offset)}};function Q(s,e){if(s.length===e)return s;if(s.length>e)return s.subarray(s.length-e);let t=new Uint8Array(e);return t.set(s,e-s.length),t}var p=globalThis.crypto?.subtle,_e={"curve25519-sha256":{type:"x25519",hash:"SHA-256"},"curve25519-sha256@libssh.org":{type:"x25519",hash:"SHA-256"},"ecdh-sha2-nistp256":{type:"ecdh",curve:"P-256",hash:"SHA-256"}},Y={"ssh-ed25519":{keyType:"ssh-ed25519",kind:"ed25519"},"rsa-sha2-512":{keyType:"ssh-rsa",kind:"rsa",hash:"SHA-512"},"rsa-sha2-256":{keyType:"ssh-rsa",kind:"rsa",hash:"SHA-256"},"ecdsa-sha2-nistp256":{keyType:"ecdsa-sha2-nistp256",kind:"ecdsa",curve:"P-256",hash:"SHA-256"}};async function ie(s,e){if(!p)return!1;try{return await p.generateKey(s,!1,e),!0}catch{return!1}}var P=null;async function oe(){if(P)return P;let[s,e]=await Promise.all([ie({name:"X25519"},["deriveBits"]),ie({name:"Ed25519"},["sign","verify"])]),t=[];s&&t.push("curve25519-sha256","curve25519-sha256@libssh.org"),t.push("ecdh-sha2-nistp256");let n=[];return e&&n.push("ssh-ed25519"),n.push("rsa-sha2-512","rsa-sha2-256","ecdsa-sha2-nistp256"),P={kex:t,hostKey:n,x25519:s,ed25519:e},P}async function ae(s){let e=_e[s];if(!e)throw new Error(`SSH: unsupported key exchange ${s}`);if(e.type==="x25519"){let r=await p.generateKey({name:"X25519"},!1,["deriveBits"]),i=new Uint8Array(await p.exportKey("raw",r.publicKey));return{hash:e.hash,publicKey:i,async sharedSecret(a){if(a.length!==32)throw new Error("SSH: the host sent a malformed X25519 key.");let d=await p.importKey("raw",a,{name:"X25519"},!1,[]),l=new Uint8Array(await p.deriveBits({name:"X25519",public:d},r.privateKey,256));if(l.every(h=>h===0))throw new Error("SSH: the host sent a degenerate X25519 key.");return l}}}let t=await p.generateKey({name:"ECDH",namedCurve:e.curve},!1,["deriveBits"]),n=new Uint8Array(await p.exportKey("raw",t.publicKey));return{hash:e.hash,publicKey:n,async sharedSecret(r){let i=await p.importKey("raw",r,{name:"ECDH",namedCurve:e.curve},!1,[]);return new Uint8Array(await p.deriveBits({name:"ECDH",public:i},t.privateKey,256))}}}function he(s){return new u(s).string()}async function ce(s){let e=new Uint8Array(await p.digest("SHA-256",s));return`SHA256:${x(e).replace(/=+$/,"")}`}async function ke(s,e){let t=Y[s];if(!t)throw new Error(`SSH: unsupported host key algorithm ${s}`);let n=new u(e),r=n.string();if(r!==t.keyType)throw new Error(`SSH: the host key is a ${r} but was announced as ${s}.`);if(t.kind==="ed25519")return p.importKey("raw",n.stringBytes(),{name:"Ed25519"},!1,["verify"]);if(t.kind==="rsa"){let i=n.mpint(),a=n.mpint();return p.importKey("jwk",{kty:"RSA",n:X(a),e:X(i),ext:!0},{name:"RSASSA-PKCS1-v1_5",hash:t.hash},!1,["verify"])}return n.string(),p.importKey("raw",n.stringBytes(),{name:"ECDSA",namedCurve:t.curve},!1,["verify"])}function xe(s,e){let t=new u(e),n=t.string();if(n!==s)throw new Error(`SSH: the host signed with ${n} after agreeing on ${s}.`);let r=t.stringBytes();if(Y[s]?.kind!=="ecdsa")return r;let i=new u(r),a=i.mpint(),d=i.mpint();return m(Q(a,32),Q(d,32))}async function le({algorithm:s,keyBlob:e,signatureBlob:t,exchangeHash:n}){let{kind:r,hash:i}=Y[s],a=await ke(s,e),d=xe(s,t),l=r==="ecdsa"?{name:"ECDSA",hash:i}:r==="rsa"?{name:"RSASSA-PKCS1-v1_5"}:{name:"Ed25519"};return p.verify(l,a,d,n)}async function ue({hash:s,clientVersion:e,serverVersion:t,clientKexInit:n,serverKexInit:r,hostKeyBlob:i,clientPublicKey:a,serverPublicKey:d,sharedSecret:l}){let h=new c(1024);return h.string(e),h.string(t),h.string(n),h.string(r),h.string(i),h.string(a),h.string(d),h.mpint(l),new Uint8Array(await p.digest(s,h.take()))}async function N({hash:s,sharedSecret:e,exchangeHash:t,letter:n,sessionId:r,length:i}){let a=new c(64+t.length).mpint(e).take(),d=m(a,t,new Uint8Array([n.charCodeAt(0)]),r),l=new Uint8Array(await p.digest(s,d));for(;l.length<i;){let h=new Uint8Array(await p.digest(s,m(a,t,l)));l=m(l,h)}return l.subarray(0,i)}var de="VNC_GATEWAY_READY_v1",fe="VNC_GATEWAY_KEEPALIVE_v1";var pe="SSH-2.0-CloudflareGateway_1.0",z={"aes256-gcm@openssh.com":{keyLength:32},"aes128-gcm@openssh.com":{keyLength:16}},B=Object.keys(z),ge=["hmac-sha2-256","hmac-sha2-512"],ye=["none"],L=16,q=16,we=12,j=8,me=4,Ee=262144,Le=8192,J=globalThis.crypto?.subtle;function M(s,e){return s.find(t=>e.includes(t))||null}var $=class s{key;fixed;counter;constructor(e,t){this.key=e,this.fixed=t.slice(0,4),this.counter=t.slice(4,12)}static async create(e,t){let n=await J.importKey("raw",e,{name:"AES-GCM"},!1,["encrypt","decrypt"]);return new s(n,t)}nextIv(){let e=m(this.fixed,this.counter);for(let t=this.counter.length-1;t>=0&&(this.counter[t]=this.counter[t]+1&255,this.counter[t]===0);t--);return e}},F=class extends EventTarget{url;verifyHost;log;socket;openTransport;closed;buffer;serverVersion;greeting;sendSequence;receiveSequence;outgoing;incoming;sessionId;kex;kexQueue;keepaliveTimer;bytesSent;bytesReceived;encryptChain;newKeysBarrier;releaseNewKeys;pendingServerKexInit;established;constructor(e,{verifyHost:t,log:n,openTransport:r}={}){super(),this.url=e,this.verifyHost=t||(()=>!0),this.log=n||(()=>{}),this.openTransport=r||null,this.socket=null,this.closed=!1,this.buffer=new Uint8Array(0),this.serverVersion="",this.greeting=[],this.sendSequence=0,this.receiveSequence=0,this.outgoing=null,this.incoming=null,this.sessionId=null,this.kex=null,this.kexQueue=[],this.keepaliveTimer=null,this.bytesSent=0,this.bytesReceived=0}connect(){this.socket=this.openTransport?this.openTransport(this.url):new WebSocket(this.url),this.socket.binaryType="arraybuffer",this.socket.addEventListener("open",()=>{this.socket.send(de),this.#n(S(`${pe}\r
|
|
2
|
+
`)),this.keepaliveTimer=setInterval(()=>{if(this.socket?.readyState===WebSocket.OPEN&&(this.socket.send(fe),this.sessionId))try{this.#t(new c(64).u8(o.GLOBAL_REQUEST).string("keepalive@openssh.com").u8(1).take())}catch{}},25e3)}),this.socket.addEventListener("message",e=>{typeof e.data!="string"&&this.#s(new Uint8Array(e.data))}),this.socket.addEventListener("close",e=>{this.#e({clean:e.code===1e3,code:e.code,message:e.reason||(e.code===1006?"The gateway rejected or could not reach this SSH target. Confirm that SSH is published for this host.":"")})}),this.socket.addEventListener("error",()=>{this.log("The SSH gateway WebSocket reported a transport error; waiting for its close reason.")})}disconnect(e="Client disconnected"){if(this.closed)return;try{let n=new c(64).u8(o.DISCONNECT).u32(11).string(e).string("").take();this.#t(n)}catch{}let t=this.socket;Promise.resolve(this.encryptChain).finally(()=>{try{t?.close(1e3,"Client disconnected")}catch{}}),this.#e({clean:!0,code:1e3,message:""})}#e(e){this.closed||(this.closed=!0,clearInterval(this.keepaliveTimer),this.keepaliveTimer=null,this.dispatchEvent(new CustomEvent("close",{detail:e})))}fail(e){let t=e instanceof Error?e.message:String(e);if(!this.closed){this.dispatchEvent(new CustomEvent("error",{detail:{message:t}})),this.#e({clean:!1,code:4e3,message:t});try{this.socket?.close(4e3,t.slice(0,100))}catch{}}}#n(e){this.socket?.readyState===WebSocket.OPEN&&(this.bytesSent+=e.length,this.socket.send(e))}#s(e){this.bytesReceived+=e.length,this.buffer=this.buffer.length?m(this.buffer,e):e,this.drainChain=(this.drainChain||Promise.resolve()).then(()=>this.#f()).catch(t=>this.fail(t))}async#f(){if(!this.closed&&!(!this.serverVersion&&!this.#p()))for(;!this.closed;){if(this.newKeysBarrier){let t=this.newKeysBarrier;this.newKeysBarrier=null,await t;continue}if(!this.incoming){let t=this.#g();if(!t)return;this.#r(t);continue}let e=this.#y();if(!e)return;this.#r(await this.#c(e))}}#p(){for(;;){let e=this.buffer.indexOf(10);if(e===-1){if(this.buffer.length>Le)throw new Error("SSH: the host sent no version line.");return!1}let t=e;t>0&&this.buffer[t-1]===13&&t--;let n=new TextDecoder().decode(this.buffer.subarray(0,t));if(this.buffer=this.buffer.subarray(e+1),!n.startsWith("SSH-")){n.trim()&&this.greeting.push(n);continue}if(!n.startsWith("SSH-2.0-")&&!n.startsWith("SSH-1.99-"))throw new Error(`SSH: this host speaks ${n.split("-").slice(0,2).join("-")}, which is too old.`);return this.serverVersion=n,this.log("version",{server:n,greeting:this.greeting}),this.dispatchEvent(new CustomEvent("greeting",{detail:{lines:this.greeting.slice()}})),this.#l(),!0}}#g(){if(this.buffer.length<5)return null;let t=new DataView(this.buffer.buffer,this.buffer.byteOffset,this.buffer.byteLength).getUint32(0,!1);if(t<8||t>Ee)throw new Error("SSH: the host sent a packet of an impossible size.");if(this.buffer.length<4+t)return null;let n=this.buffer[4];if(n+1>t)throw new Error("SSH: a packet claimed more padding than it had.");let r=this.buffer.slice(5,4+t-n);return this.buffer=this.buffer.subarray(4+t),this.receiveSequence=this.receiveSequence+1>>>0,r}#y(){if(this.buffer.length<4)return null;let t=new DataView(this.buffer.buffer,this.buffer.byteOffset,this.buffer.byteLength).getUint32(0,!1);if(t<L||t%L!==0||t>Ee)throw new Error("SSH: the host sent a packet of an impossible size.");let n=4+t+q;if(this.buffer.length<n)return null;let r=this.buffer.slice(0,n);return this.buffer=this.buffer.subarray(n),r}async#c(e){let t;try{t=new Uint8Array(await J.decrypt({name:"AES-GCM",iv:this.incoming.nextIv(),additionalData:e.subarray(0,4),tagLength:q*8},this.incoming.key,e.subarray(4)))}catch{throw new Error("SSH: a packet failed its integrity check, so the session was dropped.")}let n=t[0];if(n+1>t.length)throw new Error("SSH: a packet claimed more padding than it had.");return this.receiveSequence=this.receiveSequence+1>>>0,t.slice(1,t.length-n)}#r(e){if(e.length===0)return;let t=e[0];switch(t){case o.DISCONNECT:{let n=new u(e,1),r=n.u32(),a=n.string()||re[r]||`reason ${r}`;this.dispatchEvent(new CustomEvent("hostdisconnect",{detail:{code:r,reason:a}})),this.#e({clean:r===11,code:1e3,message:a});try{this.socket?.close(1e3,"Host disconnected")}catch{}return}case o.IGNORE:case o.UNIMPLEMENTED:case o.EXT_INFO:return;case o.DEBUG:{let n=new u(e,1),r=n.boolean(),i=n.string();this.log("debug",{message:i,alwaysDisplay:r}),r&&this.dispatchEvent(new CustomEvent("banner",{detail:{text:`${i}\r
|
|
3
|
+
`}}));return}case o.KEXINIT:this.#w(e);return;case o.NEWKEYS:this.#d();return;case o.GLOBAL_REQUEST:{let n=new u(e,1);n.string(),n.boolean()&&this.#t(new Uint8Array([o.REQUEST_FAILURE]));return}case o.REQUEST_SUCCESS:case o.REQUEST_FAILURE:return;default:break}if(this.kex&&t>=30&&t<=49){this.#i(e);return}this.dispatchEvent(new CustomEvent("packet",{detail:{type:t,payload:e}}))}send(e){return this.closed?!1:this.kex?.running?(this.kexQueue.push(e),!0):(this.#t(e),!0)}#t(e){if(!(this.closed||this.socket?.readyState!==WebSocket.OPEN)){if(!this.outgoing){this.#n(He(e)),this.sendSequence=this.sendSequence+1>>>0;return}this.#a(e)}}async#a(e){let t=this.encryptChain||Promise.resolve();return this.encryptChain=t.then(async()=>{if(this.closed||!this.outgoing)return;let n=Ie(e.length),r=new Uint8Array(1+e.length+n);r[0]=n,r.set(e,1),crypto.getRandomValues(r.subarray(1+e.length));let i=new c(4).u32(r.length).take(),a=new Uint8Array(await J.encrypt({name:"AES-GCM",iv:this.outgoing.nextIv(),additionalData:i,tagLength:q*8},this.outgoing.key,r));this.#n(m(i,a)),this.sendSequence=this.sendSequence+1>>>0}).catch(n=>this.fail(n)),this.encryptChain}async#l(){if(!this.kex?.running){this.kex={running:!0,clientKexInit:null,pending:null,inbox:[]};try{let e=await oe(),t=Re(e);this.kex.clientKexInit=t,this.#t(t);let n=this.pendingServerKexInit||await this.#o(o.KEXINIT);this.pendingServerKexInit=null,await this.#u(e,t,n)}catch(e){this.fail(e)}}}async#w(e){if(this.kex?.running){this.#i(e);return}this.log("rekey",{reason:"the host asked for new keys"}),this.pendingServerKexInit=e,await this.#l()}#i(e){if(this.kex){if(this.kex.pending){let t=this.kex.pending;this.kex.pending=null,t(e);return}this.kex.inbox.push(e)}}#o(e){return new Promise((t,n)=>{let r=a=>{if(e!==void 0&&a[0]!==e){n(new Error(`SSH: expected message ${e} during key exchange but got ${a[0]}.`));return}t(a)},i=this.kex.inbox.shift();if(i){r(i);return}this.kex.pending=a=>{this.kex.pending=null,r(a)}})}async#u(e,t,n){let r=Oe(n),i=M(e.kex,r.kex),a=M(e.hostKey,r.hostKey),d=M(B,r.cipherClientToServer),l=M(B,r.cipherServerToClient);if(!i)throw new Error(`SSH: no shared key exchange method. The host offers ${r.kex.join(", ")}.`);if(!a)throw new Error(`SSH: no host key type this browser can verify. The host offers ${r.hostKey.join(", ")}.`);if(!d||!l)throw new Error(`SSH: the host does not offer AES-GCM, which is the only cipher this client can use. It offers ${r.cipherServerToClient.join(", ")}.`);this.log("negotiated",{kex:i,hostKey:a,cipher:l,rekey:!!this.sessionId});let h=await ae(i);this.#t(new c(128).u8(o.KEX_ECDH_INIT).string(h.publicKey).take());let w=new u(await this.#o(o.KEX_ECDH_REPLY),1),y=w.stringBytes(),C=w.stringBytes(),g=w.stringBytes(),E=await h.sharedSecret(C),A=await ue({hash:h.hash,clientVersion:pe,serverVersion:this.serverVersion,clientKexInit:t,serverKexInit:n,hostKeyBlob:y,clientPublicKey:h.publicKey,serverPublicKey:C,sharedSecret:E});if(!await le({algorithm:a,keyBlob:y,signatureBlob:g,exchangeHash:A}))throw new Error("SSH: the host key signature is invalid, so this is not the machine it claims to be.");let _=await ce(y);if(!this.sessionId){if(!await this.verifyHost({fingerprint:_,keyType:he(y),algorithm:a}))throw new Error("SSH: the host key was not accepted, so the connection was stopped.");this.sessionId=A}this.#t(new Uint8Array([o.NEWKEYS])),await this.#o(o.NEWKEYS);let b={hash:h.hash,sharedSecret:E,exchangeHash:A,sessionId:this.sessionId},[H,R,V,T]=await Promise.all([N({...b,letter:"A",length:we}),N({...b,letter:"B",length:we}),N({...b,letter:"C",length:z[d].keyLength}),N({...b,letter:"D",length:z[l].keyLength})]);this.outgoing=await $.create(V,H),this.incoming=await $.create(T,R),this.kex.running=!1,this.releaseNewKeys?.(),this.releaseNewKeys=null;let O=this.kexQueue;this.kexQueue=[];for(let f of O)this.#t(f);let K=!this.established;this.established=!0,this.dispatchEvent(new CustomEvent("keys",{detail:{fingerprint:_,kexAlgorithm:i,hostKeyAlgorithm:a,cipher:l,firstTime:K}}))}#d(){this.kex&&(this.newKeysBarrier=new Promise(e=>{this.releaseNewKeys=e}),this.#i(new Uint8Array([o.NEWKEYS])))}};function Ie(s){let e=1+s,t=L-e%L;for(;t<me;)t+=L;return t}function He(s){let e=5+s.length,t=j-e%j;t<me&&(t+=j);let n=new c(e+t);n.u32(1+s.length+t),n.u8(t),n.raw(s);let r=n.take(),i=new Uint8Array(t);return crypto.getRandomValues(i),m(r,i)}function Re(s){let e=new Uint8Array(16);crypto.getRandomValues(e);let t=new c(512);return t.u8(o.KEXINIT).raw(e),t.nameList(s.kex),t.nameList(s.hostKey),t.nameList(B),t.nameList(B),t.nameList(ge),t.nameList(ge),t.nameList(ye),t.nameList(ye),t.nameList([]),t.nameList([]),t.boolean(!1),t.u32(0),t.take()}function Oe(s){let e=new u(s,17);return{kex:e.nameList(),hostKey:e.nameList(),cipherClientToServer:e.nameList(),cipherServerToClient:e.nameList(),macClientToServer:e.nameList(),macServerToClient:e.nameList(),compressionClientToServer:e.nameList(),compressionServerToClient:e.nameList()}}var ot=22,Z="ssh-connection",Ke="xterm-256color",I=2*1024*1024,ee=32*1024,De=I/2,Pe=new Uint8Array([0]),Me=2e4,Be=5*6e4,$e=60*6e4,W=new Uint8Array(0);function Fe(s){return`'${String(s).replace(/'/g,"'\\''")}'`}function Se(s){let e=s.slice(-400);return/(?:^|\n)PS [A-Za-z]:\\[^\n]*>\s*$/.test(e)?"powershell":/(?:^|\n)[^\n]*[A-Za-z]:\\[^\n]*>\s*$/.test(e)?"cmd":""}var We=/is not recognized as an internal or external command|command not found|not recognized as the name of a cmdlet|CommandNotFoundException/i;function Ve(s,e,t){if(s==="cmd")return`echo.&echo CCB${t}&${e}&call echo.CCE${t} %^ERRORLEVEL%\r`;if(s==="powershell"){let n=x(S(e));return`Write-Output ""; Write-Output ("CCB" + "${t}"); $__ccCode = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${n}')); $global:LASTEXITCODE = $null; . ([ScriptBlock]::Create($__ccCode)); $__ccOk = $?; $__cc = if ($LASTEXITCODE -ne $null) { $LASTEXITCODE } elseif ($__ccOk) { 0 } else { 1 }; Write-Output ("CCE" + "${t} " + $__cc)\r`}return`printf '\\n%s%s\\n' 'CCB' '${t}'; eval ${Fe(e)}; __cc=$?; printf '\\n%s%s %s\\n' 'CCE' '${t}' "$__cc"\r`}function U(s){return s.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g,"").replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/\u001b[@-Z\\-_]/g,"").replace(/\r\n/g,`
|
|
4
|
+
`).replace(/\r/g,"")}function Ge(s,e){let t=s.displayFilter;if(!t)return e;t.pending+=k(e);let n="";if(t.stage==="before"){let i=t.pending.indexOf(t.begin);if(i===-1)return W;t.pending=t.pending.slice(i+t.begin.length).replace(/^\r?\n/,""),t.stage="inside"}if(t.stage==="inside"){let i=t.pending.indexOf(t.end);if(i===-1)return n=t.pending,t.pending="",n?S(n):W;n=t.pending.slice(0,i),t.pending=t.pending.slice(i),t.stage="closing"}let r=t.pending.indexOf(`
|
|
5
|
+
`);return r!==-1&&(n+=t.pending.slice(r+1),s.displayFilter=null),n?S(n):W}function Xe(s){let e=s.displayFilter;s.displayFilter=null,!(!e||e.stage!=="before"||!e.pending)&&s.dispatchEvent(new CustomEvent("data",{detail:{bytes:W,display:S(e.pending),isStderr:!1}}))}var Ae=class extends EventTarget{constructor(e,t={}){super(),this.options=t,this.username=t.username||"",this.password=t.password||"",this.requestInput=t.requestInput||(async()=>""),this.columns=t.columns||80,this.rows=t.rows||24,this.channelId=0,this.remoteChannelId=null,this.remoteWindow=0,this.remoteMaxPacket=ee,this.localWindow=I,this.pendingWrites=[],this.shellOpen=!1,this.commandInFlight=!1,this.displayFilter=null,this.readySignaled=!1,this.powerShellAttempted=!1,this.powerShellTimer=null,this.shellFamily="",this.promptTail="",this.exitStatus=null,this.authMethods=[],this.closed=!1,this.waiters=new Map,this.transport=new F(e,{verifyHost:t.verifyHost||(async()=>!0),openTransport:t.openTransport||null,log:(n,r)=>console.info(`[SSH] ${n}`,r)}),this.transport.addEventListener("packet",n=>{this.#g(n.detail.type,n.detail.payload)}),this.transport.addEventListener("greeting",n=>{for(let r of n.detail.lines)this.#n(`${r}\r
|
|
6
|
+
`)}),this.transport.addEventListener("banner",n=>this.#n(n.detail.text)),this.transport.addEventListener("keys",n=>{this.fingerprint=n.detail.fingerprint,this.#e("secured",`Host key ${n.detail.fingerprint} verified.`),this.dispatchEvent(new CustomEvent("secure",{detail:n.detail})),n.detail.firstTime&&this.#l()}),this.transport.addEventListener("hostdisconnect",n=>{this.lastMessage=n.detail.reason}),this.transport.addEventListener("error",n=>{this.lastMessage=n.detail.message,this.dispatchEvent(new CustomEvent("error",{detail:n.detail}))}),this.transport.addEventListener("close",n=>{this.closed||(this.closed=!0,clearTimeout(this.powerShellTimer),this.#p(n.detail.message||"The SSH connection closed."),this.dispatchEvent(new CustomEvent("close",{detail:{...n.detail,message:n.detail.message||this.lastMessage||"",exitStatus:this.exitStatus}})))})}connect(){this.#e("connecting","Opening the SSH transport\u2026"),this.transport.connect()}disconnect(){this.closed||(clearTimeout(this.powerShellTimer),this.shellOpen&&this.remoteChannelId!==null&&this.transport.send(new c(8).u8(o.CHANNEL_CLOSE).u32(this.remoteChannelId).take()),this.transport.disconnect("Closed by the user"))}write(e){let t=typeof e=="string"?S(e):e;t.length&&(this.pendingWrites.push(t),this.#a())}runCommand(e,{startMs:t=Me,idleMs:n=Be,maxMs:r=$e}={}){let i=String(e||"").trim();if(!i)return Promise.reject(new Error("No command was given."));if(!this.shellOpen||this.closed)return Promise.reject(new Error("The SSH shell is not open."));if(this.readySignaled===!1)return Promise.reject(new Error("The SSH shell is still starting."));if(this.commandInFlight)return Promise.reject(new Error("Another command is still running in this shell."));let a=Array.from(crypto.getRandomValues(new Uint8Array(6))).map(y=>y.toString(16).padStart(2,"0")).join(""),d=`CCB${a}`,l=`CCE${a}`,h=this.shellFamily||"posix";this.commandInFlight=!0;let w=Date.now();return this.displayFilter={begin:d,end:l,stage:"before",pending:""},this.dispatchEvent(new CustomEvent("command",{detail:{phase:"start",id:a,command:i}})),new Promise((y,C)=>{let g="",E=!1,A=null,v=null,_=null,b=f=>{clearTimeout(A),clearTimeout(v),clearTimeout(_),this.removeEventListener("data",O),this.removeEventListener("close",K),this.commandInFlight=!1,Xe(this),f()},H=f=>b(()=>{this.dispatchEvent(new CustomEvent("command",{detail:{phase:"end",id:a,command:i,exitStatus:f.exitStatus,timedOut:f.timedOut,started:f.started!==!1,reason:f.reason,durationMs:f.durationMs}})),y(f)}),R=f=>b(()=>{this.dispatchEvent(new CustomEvent("command",{detail:{phase:"end",id:a,command:i,exitStatus:null,timedOut:!1,started:E,durationMs:Date.now()-w,error:f.message}})),C(f)}),V=()=>{let f=g.indexOf(d);return f===-1?"":U(g.slice(f+d.length)).replace(/^\r?\n/,"").replace(/\s+$/,"")},T=f=>{this.write(""),H({output:V(),exitStatus:null,timedOut:!0,started:E,reason:f,durationMs:Date.now()-w})},O=f=>{g+=k(f.detail.bytes),!E&&g.indexOf(d)!==-1&&(E=!0,clearTimeout(A),A=null),E&&(clearTimeout(v),v=setTimeout(()=>T("idle"),n));let D=g.indexOf(`${l} `);if(D===-1){if(!E&&We.test(U(g))){let G=Se(U(g))||"cmd",Te=h!==G;this.shellFamily=G,R(new Error(Te?`This shell is ${G==="powershell"?"PowerShell":"Windows cmd"}, not a POSIX shell, and it rejected the wrapper used to capture output. The shell has been noted; run the command again and it will be framed correctly.`:`The shell rejected the wrapper used to capture output: ${U(g).split(`
|
|
7
|
+
`).filter(Boolean).pop()||"no detail"}`))}return}let Ce=g.slice(D),te=parseInt(Ce.slice(l.length+1).trim(),10),ne=g.indexOf(d),be=ne===-1?g.slice(0,D):g.slice(ne+d.length,D);H({output:U(be).replace(/^\r?\n/,"").replace(/\s+$/,""),exitStatus:Number.isInteger(te)?te:null,timedOut:!1,started:!0,durationMs:Date.now()-w})},K=()=>R(new Error("The SSH session closed while the command was running."));this.addEventListener("data",O),this.addEventListener("close",K),A=setTimeout(()=>T("start"),t),v=setTimeout(()=>T("idle"),n),_=setTimeout(()=>T("max"),r),this.write(Ve(h,i,a))})}resize(e,t){let n=Math.max(1,Math.floor(e)),r=Math.max(1,Math.floor(t));n===this.columns&&r===this.rows||(this.columns=n,this.rows=r,!(!this.shellOpen||this.remoteChannelId===null)&&this.transport.send(new c(64).u8(o.CHANNEL_REQUEST).u32(this.remoteChannelId).string("window-change").boolean(!1).u32(n).u32(r).u32(0).u32(0).take()))}#e(e,t){this.dispatchEvent(new CustomEvent("status",{detail:{phase:e,message:t}}))}#n(e){e&&this.dispatchEvent(new CustomEvent("banner",{detail:{text:e}}))}#s(...e){return new Promise((t,n)=>{for(let r of e)this.waiters.set(r,{resolve:t,reject:n})})}#f(e,t){let n=this.waiters.get(e);return n?(this.waiters.clear(),n.resolve({type:e,payload:t}),!0):!1}#p(e){if(this.waiters.size===0)return;let t=[...new Set(this.waiters.values())];this.waiters.clear();for(let n of t)n.reject(new Error(e))}#g(e,t){if(!this.#f(e,t))switch(e){case o.USERAUTH_BANNER:this.#n(new u(t,1).string().replaceAll(`
|
|
8
|
+
`,`\r
|
|
9
|
+
`));return;case o.CHANNEL_DATA:{let n=new u(t,1);n.u32(),this.#t(n.stringBytes());return}case o.CHANNEL_EXTENDED_DATA:{let n=new u(t,1);n.u32();let r=n.u32(),i=n.stringBytes();this.#t(i,r===se);return}case o.CHANNEL_WINDOW_ADJUST:{let n=new u(t,1);n.u32(),this.remoteWindow+=n.u32(),this.#a();return}case o.CHANNEL_REQUEST:{let n=new u(t,1);n.u32();let r=n.string(),i=n.boolean();r==="exit-status"&&(this.exitStatus=n.u32()),r==="exit-signal"&&(this.exitSignal=n.string()),i&&this.remoteChannelId!==null&&this.transport.send(new c(8).u8(o.CHANNEL_SUCCESS).u32(this.remoteChannelId).take());return}case o.CHANNEL_EOF:return;case o.CHANNEL_CLOSE:this.shellOpen=!1,this.#e("closed","The remote shell ended."),this.transport.disconnect("Shell closed");return;default:return}}#y(e){this.promptTail=(this.promptTail+U(k(e))).slice(-400);let t=Se(this.promptTail);if(t){if(t==="cmd"&&!this.powerShellAttempted){this.#c();return}t==="cmd"&&this.powerShellAttempted&&!this.readySignaled||(this.shellFamily=t,t==="powershell"&&!this.readySignaled&&this.#r("Connected \xB7 PowerShell"))}}#c(){this.powerShellAttempted||!this.shellOpen||(this.powerShellAttempted=!0,this.readySignaled=!1,this.shellFamily="",this.#e("opening","Starting PowerShell\u2026"),this.write("powershell.exe -NoLogo\r"),this.powerShellTimer=setTimeout(()=>{this.readySignaled||this.closed||(this.shellFamily="powershell",this.#r("Connected \xB7 PowerShell"))},2e3))}#r(e="Connected."){clearTimeout(this.powerShellTimer),this.powerShellTimer=null,this.readySignaled=!0,this.#e("ready",e),this.dispatchEvent(new CustomEvent("ready",{detail:{fingerprint:this.fingerprint,shellFamily:this.shellFamily||"posix"}}))}#t(e,t=!1){if(!e.length||(this.#y(e),this.dispatchEvent(new CustomEvent("data",{detail:{bytes:e,display:Ge(this,e),isStderr:t}})),this.localWindow-=e.length,this.localWindow>De||this.remoteChannelId===null))return;let n=I-this.localWindow;this.localWindow=I,this.transport.send(new c(16).u8(o.CHANNEL_WINDOW_ADJUST).u32(this.remoteChannelId).u32(n).take())}#a(){if(!(!this.shellOpen||this.remoteChannelId===null))for(;this.pendingWrites.length>0;){let e=this.pendingWrites[0],t=Math.min(this.remoteWindow,this.remoteMaxPacket-64);if(t<=0)return;let n=e.length<=t?e:e.subarray(0,t);n.length===e.length?this.pendingWrites.shift():this.pendingWrites[0]=e.subarray(n.length),this.remoteWindow-=n.length,this.transport.send(new c(n.length+16).u8(o.CHANNEL_DATA).u32(this.remoteChannelId).string(n).take())}}async#l(){try{if(this.#e("authenticating",`Authenticating as ${this.username}\u2026`),this.transport.send(new c(32).u8(o.SERVICE_REQUEST).string("ssh-userauth").take()),await this.#s(o.SERVICE_ACCEPT),await this.#w())return void await this.#h();if(this.authMethods.includes("password")&&this.password&&await this.#i(this.password))return void await this.#h();if(this.authMethods.includes("keyboard-interactive")){if(await this.#o())return void await this.#h();if(this.authMethods.includes("keyboard-interactive")&&await this.#o({usePassword:!1}))return void await this.#h()}if(this.authMethods.includes("password")){let e=await this.requestInput({prompt:`${this.username}@${this.options.hostLabel||"host"}'s password: `,echo:!1});if(e&&await this.#i(e))return void await this.#h()}throw new Error(this.authMethods.length?`SSH: authentication failed. The host accepts ${this.authMethods.join(", ")}.`:"SSH: the host rejected the connection before any authentication method was offered.")}catch(e){this.closed||this.transport.fail(e)}}async#w(){return this.transport.send(new c(64).u8(o.USERAUTH_REQUEST).string(this.username).string(Z).string("none").take()),this.#u()}async#i(e){return this.transport.send(new c(128).u8(o.USERAUTH_REQUEST).string(this.username).string(Z).string("password").boolean(!1).string(e).take()),this.#u()}async#o({usePassword:e=!0}={}){this.transport.send(new c(96).u8(o.USERAUTH_REQUEST).string(this.username).string(Z).string("keyboard-interactive").string("").string("").take());let t=!e;for(;;){let{type:n,payload:r}=await this.#s(o.USERAUTH_SUCCESS,o.USERAUTH_FAILURE,o.USERAUTH_INFO_REQUEST);if(n===o.USERAUTH_SUCCESS)return!0;if(n===o.USERAUTH_FAILURE)return this.#d(r),!1;let i=new u(r,1),a=i.string(),d=i.string();i.string();let l=i.u32();a&&this.#n(`${a.replaceAll(`
|
|
10
|
+
`,`\r
|
|
11
|
+
`)}\r
|
|
12
|
+
`),d&&this.#n(`${d.replaceAll(`
|
|
13
|
+
`,`\r
|
|
14
|
+
`)}\r
|
|
15
|
+
`);let h=[];for(let y=0;y<l;y++){let C=i.string(),g=i.boolean();if(!g&&/pass(word|phrase)/i.test(C)&&this.password&&!t){t=!0,h.push(this.password);continue}h.push(await this.requestInput({prompt:C,echo:g}))}let w=new c(256).u8(o.USERAUTH_INFO_RESPONSE).u32(h.length);for(let y of h)w.string(y);this.transport.send(w.take())}}async#u(){let{type:e,payload:t}=await this.#s(o.USERAUTH_SUCCESS,o.USERAUTH_FAILURE);return e===o.USERAUTH_SUCCESS?!0:(this.#d(t),!1)}#d(e){let t=new u(e,1);this.authMethods=t.nameList(),t.boolean()&&this.#n(`\r
|
|
16
|
+
One factor accepted; the host wants another.\r
|
|
17
|
+
`)}async#h(){this.#e("opening","Starting the remote shell\u2026"),this.transport.send(new c(64).u8(o.CHANNEL_OPEN).string("session").u32(this.channelId).u32(I).u32(ee).take());let e=await this.#s(o.CHANNEL_OPEN_CONFIRMATION,o.CHANNEL_OPEN_FAILURE);if(e.type===o.CHANNEL_OPEN_FAILURE){let i=new u(e.payload,1);throw i.u32(),i.u32(),new Error(`SSH: the host refused to open a session channel (${i.string()}).`)}let t=new u(e.payload,1);if(t.u32(),this.remoteChannelId=t.u32(),this.remoteWindow=t.u32(),this.remoteMaxPacket=Math.max(1024,Math.min(t.u32(),ee)),this.transport.send(new c(128).u8(o.CHANNEL_REQUEST).u32(this.remoteChannelId).string("pty-req").boolean(!0).string(Ke).u32(this.columns).u32(this.rows).u32(0).u32(0).string(Pe).take()),(await this.#s(o.CHANNEL_SUCCESS,o.CHANNEL_FAILURE)).type===o.CHANNEL_FAILURE)throw new Error("SSH: the host refused a pseudo-terminal, so there is no interactive shell to show.");if(this.transport.send(new c(32).u8(o.CHANNEL_REQUEST).u32(this.remoteChannelId).string("shell").boolean(!0).take()),(await this.#s(o.CHANNEL_SUCCESS,o.CHANNEL_FAILURE)).type===o.CHANNEL_FAILURE)throw new Error("SSH: the host refused to start a shell for this account.");this.shellOpen=!0,this.#a(),this.options.preferredShell==="powershell"?this.#c():this.#r()}};export{Be as COMMAND_IDLE_MS,$e as COMMAND_MAX_MS,Me as COMMAND_START_MS,ot as DEFAULT_PORT,Ae as SshSession,Ve as frameCommand};
|