From 92c729dadaea274c712db4b1c3aec9db8993da14 Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 16 Jan 2025 22:14:29 +0100 Subject: [PATCH] bump russh for keyboard-interactive fixes and lock race fix --- app/package.json | 2 +- app/yarn.lock | 8 ++--- tabby-ssh/src/session/ssh.ts | 60 +++++++++++++++++++++++++++--------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/app/package.json b/app/package.json index 60b0b1a7..0007be26 100644 --- a/app/package.json +++ b/app/package.json @@ -30,7 +30,7 @@ "native-process-working-directory": "^1.0.2", "npm": "6", "rxjs": "^7.5.7", - "russh": "0.1.11", + "russh": "0.1.13", "source-map-support": "^0.5.20", "v8-compile-cache": "^2.3.0", "yargs": "^17.7.2" diff --git a/app/yarn.lock b/app/yarn.lock index e9d2059a..4859ec11 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -3628,10 +3628,10 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -russh@0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/russh/-/russh-0.1.11.tgz#22e74f93ec1cb045930c85f8ba1daf2e5efcae4b" - integrity sha512-3CuI+rMoGpnnFDJxsEmcHYRSHInf3bz3fbgeyPnX8n1wgsX6wdbyI1DKL188oQlsrFWEjO3/7ebbYliCi0Qz7w== +russh@0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/russh/-/russh-0.1.13.tgz#e5e1d1b3b7fcd62992df8efce5300b3f57262df5" + integrity sha512-5yLxrsC0rYtfynkCLIdeS2XHQKwMEFeww3XoEezaNSHks9grbHbLxgfWBnZ7ZJgrCrJCRgSOvu6Ct5GKpYgb7w== dependencies: "@napi-rs/cli" "^2.18.3" diff --git a/tabby-ssh/src/session/ssh.ts b/tabby-ssh/src/session/ssh.ts index 21300363..008e78b1 100644 --- a/tabby-ssh/src/session/ssh.ts +++ b/tabby-ssh/src/session/ssh.ts @@ -50,6 +50,18 @@ type AuthMethod = { kind: 'pageant', } +function sshAuthTypeForMethod (m: AuthMethod): string { + switch (m.type) { + case 'none': return 'none' + case 'hostbased': return 'hostbased' + case 'prompt-password': return 'password' + case 'saved-password': return 'password' + case 'keyboard-interactive': return 'keyboard-interactive' + case 'publickey': return 'publickey' + case 'agent': return 'agent' + } +} + export class KeyboardInteractivePrompt { readonly responses: string[] = [] @@ -181,6 +193,13 @@ export class SSHSession { }) } } + if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') { + const savedPassword = this.profile.options.password ?? await this.passwordStorage.loadPassword(this.profile) + if (savedPassword) { + this.remainingAuthMethods.push({ type: 'keyboard-interactive', savedPassword }) + } + this.remainingAuthMethods.push({ type: 'keyboard-interactive' }) + } if (!this.profile.options.auth || this.profile.options.auth === 'password') { if (this.profile.options.password) { this.remainingAuthMethods.push({ type: 'saved-password', password: this.profile.options.password }) @@ -191,13 +210,6 @@ export class SSHSession { } this.remainingAuthMethods.push({ type: 'prompt-password' }) } - if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') { - const savedPassword = this.profile.options.password ?? await this.passwordStorage.loadPassword(this.profile) - if (savedPassword) { - this.remainingAuthMethods.push({ type: 'keyboard-interactive', savedPassword }) - } - this.remainingAuthMethods.push({ type: 'keyboard-interactive' }) - } this.remainingAuthMethods.push({ type: 'hostbased' }) } @@ -495,7 +507,7 @@ export class SSHSession { this.keyboardInteractivePrompt.next(prompt) } - async handleAuth (methodsLeft?: string[] | null): Promise { + async handleAuth (): Promise { this.activePrivateKey = null if (!(this.ssh instanceof russh.SSHClient)) { @@ -506,22 +518,36 @@ export class SSHSession { throw new Error('No username') } + const noneResult = await this.ssh.authenticateNone(this.authUsername) + if (noneResult instanceof russh.AuthenticatedSSHClient) { + return noneResult + } + + let methodsLeft = noneResult.remainingMethods + + function maybeSetRemainingMethods (r: russh.AuthFailure) { + if (r.remainingMethods.length) { + methodsLeft = r.remainingMethods + } + } + while (true) { - const method = this.remainingAuthMethods.shift() + const m = methodsLeft + const method = this.remainingAuthMethods.find(x => !m || m.includes(sshAuthTypeForMethod(x))) + if (!method) { return null } - if (methodsLeft && !methodsLeft.includes(method.type) && method.type !== 'agent') { - // Agent can still be used even if not in methodsLeft - this.logger.info('Server does not support auth method', method.type) - continue - } + + this.remainingAuthMethods = this.remainingAuthMethods.filter(x => x !== method) + if (method.type === 'saved-password') { this.emitServiceMessage(this.translate.instant('Using saved password')) const result = await this.ssh.authenticateWithPassword(this.authUsername, method.password) if (result instanceof russh.AuthenticatedSSHClient) { return result } + maybeSetRemainingMethods(result) } if (method.type === 'prompt-password') { const modal = this.ngbModal.open(PromptModalComponent) @@ -539,6 +565,7 @@ export class SSHSession { if (result instanceof russh.AuthenticatedSSHClient) { return result } + maybeSetRemainingMethods(result) } else { continue } @@ -556,6 +583,7 @@ export class SSHSession { if (result instanceof russh.AuthenticatedSSHClient) { return result } + maybeSetRemainingMethods(result) } } catch (e) { this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`) @@ -567,6 +595,7 @@ export class SSHSession { while (true) { if (state.state === 'failure') { + maybeSetRemainingMethods(state) break } @@ -602,7 +631,7 @@ export class SSHSession { } } - state = await this.ssh .continueKeyboardInteractiveAuthentication(responses) + state = await this.ssh.continueKeyboardInteractiveAuthentication(responses) if (state instanceof russh.AuthenticatedSSHClient) { return state @@ -615,6 +644,7 @@ export class SSHSession { if (result instanceof russh.AuthenticatedSSHClient) { return result } + maybeSetRemainingMethods(result) } catch (e) { this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to authenticate using agent: ${e}`) continue