mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-30 14:14:35 +00:00
Compare commits
22 Commits
v1.0.221
...
all-contri
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6f032b2d12 | ||
![]() |
711b78d882 | ||
![]() |
5de20848e1 | ||
![]() |
8b96882d27 | ||
![]() |
c340861aa9 | ||
![]() |
cd0869cdc1 | ||
![]() |
f405bd35e6 | ||
![]() |
4a515d9432 | ||
![]() |
b83b2e5acc | ||
![]() |
e407ee8bf1 | ||
![]() |
c7b39bdca7 | ||
![]() |
934cdff0f8 | ||
![]() |
ab87099b8b | ||
![]() |
47b4b54557 | ||
![]() |
15f4182e0e | ||
![]() |
4be1e12559 | ||
![]() |
5d2d179677 | ||
![]() |
4197cefdfd | ||
![]() |
7c1421ffcf | ||
![]() |
380c306d89 | ||
![]() |
cf0da75224 | ||
![]() |
d1c1b48502 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -258,7 +258,7 @@ jobs:
|
||||
repo: 'eugeny/tabby'
|
||||
dir: 'dist'
|
||||
rpmvers: 'el/9 el/8 ol/6 ol/7'
|
||||
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble ubuntu/oracular debian/jessie debian/stretch debian/buster'
|
||||
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble ubuntu/oracular debian/jessie debian/stretch debian/buster debian/bullseye debian/bookworm debian/trixie'
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload AppImage (${{matrix.arch}})
|
||||
|
@@ -347,6 +347,7 @@ Dank geht an diese wunderbaren Menschen ([emoji key](https://allcontributors.org
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mannjani"><img src="https://avatars.githubusercontent.com/u/4688839?v=4?s=100" width="100px;" alt="mannjani"/><br /><sub><b>mannjani</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mannjani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -346,6 +346,7 @@ Terima kasih kepada mereka yang telah membantu ([emoji key](https://allcontribut
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mannjani"><img src="https://avatars.githubusercontent.com/u/4688839?v=4?s=100" width="100px;" alt="mannjani"/><br /><sub><b>mannjani</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mannjani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -342,6 +342,7 @@ Grazie a queste persone meravigliose ([emoji key](https://allcontributors.org/do
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mannjani"><img src="https://avatars.githubusercontent.com/u/4688839?v=4?s=100" width="100px;" alt="mannjani"/><br /><sub><b>mannjani</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mannjani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -341,6 +341,7 @@ Pull requests and plugins are welcome!
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mannjani"><img src="https://avatars.githubusercontent.com/u/4688839?v=4?s=100" width="100px;" alt="mannjani"/><br /><sub><b>mannjani</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mannjani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -365,6 +365,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mannjani"><img src="https://avatars.githubusercontent.com/u/4688839?v=4?s=100" width="100px;" alt="mannjani"/><br /><sub><b>mannjani</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mannjani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -31,7 +31,7 @@
|
||||
* Встроенный SSH- и Telnet-клиент и менеджер подключений;
|
||||
* Встроенный последовательный терминал;
|
||||
* Темы и цветовые схемы;
|
||||
* Полностью настраеваемые сочетания клавиш;
|
||||
* Полностью настраиваемые сочетания клавиш;
|
||||
* Панели;
|
||||
* Запоминание вкладок;
|
||||
* Поддержка PowerShell (and PS Core), WSL, Git-Bash, Cygwin, MSYS2, Cmder и CMD;
|
||||
@@ -342,6 +342,7 @@ Pull-запросы и плагины приветствуются!
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mannjani"><img src="https://avatars.githubusercontent.com/u/4688839?v=4?s=100" width="100px;" alt="mannjani"/><br /><sub><b>mannjani</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mannjani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -341,6 +341,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mannjani"><img src="https://avatars.githubusercontent.com/u/4688839?v=4?s=100" width="100px;" alt="mannjani"/><br /><sub><b>mannjani</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=mannjani" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -30,7 +30,7 @@
|
||||
"native-process-working-directory": "^1.0.2",
|
||||
"npm": "6",
|
||||
"rxjs": "^7.5.7",
|
||||
"russh": "0.1.15",
|
||||
"russh": "0.1.22",
|
||||
"source-map-support": "^0.5.20",
|
||||
"v8-compile-cache": "^2.3.0",
|
||||
"yargs": "^17.7.2"
|
||||
|
@@ -3628,10 +3628,10 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
||||
dependencies:
|
||||
aproba "^1.1.1"
|
||||
|
||||
russh@0.1.15:
|
||||
version "0.1.15"
|
||||
resolved "https://registry.yarnpkg.com/russh/-/russh-0.1.15.tgz#121c0be876c6b70c43910a56fadefffe78588a35"
|
||||
integrity sha512-md/72roZn8nmgq+U8rVrfqHS82aPancQM5VqrYh7wEMcnK/ll8vNJJcKH/YJv5/8n8Ovxrl8SMwDE5DlfXSL6Q==
|
||||
russh@0.1.22:
|
||||
version "0.1.22"
|
||||
resolved "https://registry.yarnpkg.com/russh/-/russh-0.1.22.tgz#f56e515b1938fa3c1ee7321b2559c17bb7f7f5cc"
|
||||
integrity sha512-ors+8pqxb9cyyy0tkAgEkrEWoN18kJuw0GtcZsheTQBdqEw/BSulmkKqNva6jjvoOc3wP1GIjbkQ5OdRGqQwmg==
|
||||
dependencies:
|
||||
"@napi-rs/cli" "^2.18.3"
|
||||
|
||||
|
44
tabby-community-color-schemes/schemes/TokyoNight
Normal file
44
tabby-community-color-schemes/schemes/TokyoNight
Normal file
@@ -0,0 +1,44 @@
|
||||
!
|
||||
! Generated with :
|
||||
! XRDB2Xreources.py
|
||||
!
|
||||
*.foreground: #c0caf5
|
||||
*.background: #1a1b26
|
||||
*.cursorColor: #c0caf5
|
||||
!
|
||||
! Black
|
||||
*.color0: #15161e
|
||||
*.color8: #414868
|
||||
!
|
||||
! Red
|
||||
*.color1: #f7768e
|
||||
*.color9: #f7768e
|
||||
!
|
||||
! Green
|
||||
*.color2: #9ece6a
|
||||
*.color10: #9ece6a
|
||||
!
|
||||
! Yellow
|
||||
*.color3: #e0af68
|
||||
*.color11: #e0af68
|
||||
!
|
||||
! Blue
|
||||
*.color4: #7aa2f7
|
||||
*.color12: #7aa2f7
|
||||
!
|
||||
! Magenta
|
||||
*.color5: #bb9af7
|
||||
*.color13: #bb9af7
|
||||
!
|
||||
! Cyan
|
||||
*.color6: #7dcfff
|
||||
*.color14: #7dcfff
|
||||
!
|
||||
! White
|
||||
*.color7: #a9b1d6
|
||||
*.color15: #c0caf5
|
||||
!
|
||||
! Bold, Italic, Underline
|
||||
*.colorBD: #eeeeee
|
||||
!*.colorIT:
|
||||
!*.colorUL:
|
44
tabby-community-color-schemes/schemes/TokyoNight Day
Normal file
44
tabby-community-color-schemes/schemes/TokyoNight Day
Normal file
@@ -0,0 +1,44 @@
|
||||
!
|
||||
! Generated with :
|
||||
! XRDB2Xreources.py
|
||||
!
|
||||
*.foreground: #3760bf
|
||||
*.background: #e1e2e7
|
||||
*.cursorColor: #3760bf
|
||||
!
|
||||
! Black
|
||||
*.color0: #e9e9ed
|
||||
*.color8: #a1a6c5
|
||||
!
|
||||
! Red
|
||||
*.color1: #f52a65
|
||||
*.color9: #f52a65
|
||||
!
|
||||
! Green
|
||||
*.color2: #587539
|
||||
*.color10: #587539
|
||||
!
|
||||
! Yellow
|
||||
*.color3: #8c6c3e
|
||||
*.color11: #8c6c3e
|
||||
!
|
||||
! Blue
|
||||
*.color4: #2e7de9
|
||||
*.color12: #2e7de9
|
||||
!
|
||||
! Magenta
|
||||
*.color5: #9854f1
|
||||
*.color13: #9854f1
|
||||
!
|
||||
! Cyan
|
||||
*.color6: #007197
|
||||
*.color14: #007197
|
||||
!
|
||||
! White
|
||||
*.color7: #6172b0
|
||||
*.color15: #3760bf
|
||||
!
|
||||
! Bold, Italic, Underline
|
||||
*.colorBD: #eeeeee
|
||||
!*.colorIT:
|
||||
!*.colorUL:
|
44
tabby-community-color-schemes/schemes/TokyoNight Storm
Normal file
44
tabby-community-color-schemes/schemes/TokyoNight Storm
Normal file
@@ -0,0 +1,44 @@
|
||||
!
|
||||
! Generated with :
|
||||
! XRDB2Xreources.py
|
||||
!
|
||||
*.foreground: #c0caf5
|
||||
*.background: #24283b
|
||||
*.cursorColor: #c0caf5
|
||||
!
|
||||
! Black
|
||||
*.color0: #1d202f
|
||||
*.color8: #414868
|
||||
!
|
||||
! Red
|
||||
*.color1: #f7768e
|
||||
*.color9: #f7768e
|
||||
!
|
||||
! Green
|
||||
*.color2: #9ece6a
|
||||
*.color10: #9ece6a
|
||||
!
|
||||
! Yellow
|
||||
*.color3: #e0af68
|
||||
*.color11: #e0af68
|
||||
!
|
||||
! Blue
|
||||
*.color4: #7aa2f7
|
||||
*.color12: #7aa2f7
|
||||
!
|
||||
! Magenta
|
||||
*.color5: #bb9af7
|
||||
*.color13: #bb9af7
|
||||
!
|
||||
! Cyan
|
||||
*.color6: #7dcfff
|
||||
*.color14: #7dcfff
|
||||
!
|
||||
! White
|
||||
*.color7: #a9b1d6
|
||||
*.color15: #c0caf5
|
||||
!
|
||||
! Bold, Italic, Underline
|
||||
*.colorBD: #eeeeee
|
||||
!*.colorIT:
|
||||
!*.colorUL:
|
@@ -195,7 +195,13 @@ export class VaultService {
|
||||
if (!vault) {
|
||||
return null
|
||||
}
|
||||
return vault.secrets.find(s => s.type === type && this.keyMatches(key, s)) ?? null
|
||||
let vaultSecret = vault.secrets.find(s => s.type === type && this.keyMatches(key, s))
|
||||
if (!vaultSecret) {
|
||||
// search for secret without host in vault (like a default user/password used in multiple servers)
|
||||
key['host'] = null
|
||||
vaultSecret = vault.secrets.find(s => s.type === type && this.keyMatches(key, s))
|
||||
}
|
||||
return vaultSecret ?? null
|
||||
}
|
||||
|
||||
async addSecret (secret: VaultSecret): Promise<void> {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
// import * as fs from 'fs/promises'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as crypto from 'crypto'
|
||||
import * as tmp from 'tmp-promise'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ConfigService, HostAppService, Platform, PlatformService } from 'tabby-core'
|
||||
import { ConfigService, FileProvidersService, HostAppService, Platform, PlatformService } from 'tabby-core'
|
||||
import { SSHSession } from '../session/ssh'
|
||||
import { SSHProfile } from '../api'
|
||||
import { PasswordStorageService } from './passwordStorage.service'
|
||||
@@ -15,6 +16,7 @@ export class SSHService {
|
||||
private config: ConfigService,
|
||||
hostApp: HostAppService,
|
||||
private platform: PlatformService,
|
||||
private fileProviders: FileProvidersService,
|
||||
) {
|
||||
if (hostApp.platform === Platform.Windows) {
|
||||
this.detectedWinSCPPath = platform.getWinSCPPath()
|
||||
@@ -47,14 +49,35 @@ export class SSHService {
|
||||
const args = [await this.getWinSCPURI(session.profile, undefined, session.authUsername ?? undefined)]
|
||||
|
||||
let tmpFile: tmp.FileResult|null = null
|
||||
if (session.activePrivateKey) {
|
||||
tmpFile = await tmp.file()
|
||||
// await fs.writeFile(tmpFile.path, session.activePrivateKey)
|
||||
const winSCPcom = path.slice(0, -3) + 'com'
|
||||
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, `/output=${tmpFile.path}`])
|
||||
args.push(`/privatekey=${tmpFile.path}`)
|
||||
try {
|
||||
if (session.activePrivateKey && session.profile.options.privateKeys && session.profile.options.privateKeys.length > 0) {
|
||||
tmpFile = await tmp.file()
|
||||
let passphrase: string|null = null
|
||||
for (const pk of session.profile.options.privateKeys) {
|
||||
let privateKeyContent: string|null = null
|
||||
const buffer = await this.fileProviders.retrieveFile(pk)
|
||||
privateKeyContent = buffer.toString()
|
||||
await fs.writeFile(tmpFile.path, privateKeyContent)
|
||||
const keyHash = crypto.createHash('sha512').update(privateKeyContent).digest('hex')
|
||||
// need to pass an default passphrase, otherwise it might get stuck at the passphrase input
|
||||
passphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash) ?? 'tabby'
|
||||
const winSCPcom = path.slice(0, -3) + 'com'
|
||||
try {
|
||||
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, '-o', tmpFile.path, '--old-passphrase', passphrase])
|
||||
} catch (error) {
|
||||
console.warn('Could not convert private key ', error)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
args.push(`/privatekey=${tmpFile.path}`)
|
||||
if (passphrase != null) {
|
||||
args.push(`/passphrase=${passphrase}`)
|
||||
}
|
||||
}
|
||||
await this.platform.exec(path, args)
|
||||
} finally {
|
||||
tmpFile?.cleanup()
|
||||
}
|
||||
await this.platform.exec(path, args)
|
||||
tmpFile?.cleanup()
|
||||
}
|
||||
}
|
||||
|
@@ -111,7 +111,7 @@ export class SSHSession {
|
||||
|
||||
private logger: Logger
|
||||
private refCount = 0
|
||||
private remainingAuthMethods: AuthMethod[] = []
|
||||
private allAuthMethods: AuthMethod[] = []
|
||||
private serviceMessage = new Subject<string>()
|
||||
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
|
||||
private willDestroy = new Subject<void>()
|
||||
@@ -125,6 +125,7 @@ export class SSHSession {
|
||||
private translate: TranslateService
|
||||
private knownHosts: SSHKnownHostsService
|
||||
private privateKeyImporters: AutoPrivateKeyLocator[]
|
||||
private previouslyDisconnected = false
|
||||
|
||||
constructor (
|
||||
private injector: Injector,
|
||||
@@ -150,7 +151,7 @@ export class SSHSession {
|
||||
}
|
||||
|
||||
private addPublicKeyAuthMethod (name: string, contents: Buffer) {
|
||||
this.remainingAuthMethods.push({
|
||||
this.allAuthMethods.push({
|
||||
type: 'publickey',
|
||||
name,
|
||||
contents,
|
||||
@@ -158,12 +159,14 @@ export class SSHSession {
|
||||
}
|
||||
|
||||
async init (): Promise<void> {
|
||||
this.remainingAuthMethods = [{ type: 'none' }]
|
||||
this.allAuthMethods = [{ type: 'none' }]
|
||||
if (!this.profile.options.auth || this.profile.options.auth === 'publicKey') {
|
||||
if (this.profile.options.privateKeys?.length) {
|
||||
for (const pk of this.profile.options.privateKeys) {
|
||||
for (let pk of this.profile.options.privateKeys) {
|
||||
// eslint-disable-next-line @typescript-eslint/init-declarations
|
||||
let contents: Buffer
|
||||
pk = pk.replace('%h', this.profile.options.host)
|
||||
pk = pk.replace('%r', this.profile.options.user)
|
||||
try {
|
||||
contents = await this.fileProviders.retrieveFile(pk)
|
||||
} catch (error) {
|
||||
@@ -187,30 +190,32 @@ export class SSHSession {
|
||||
if (!spec) {
|
||||
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running Agent process is found`)
|
||||
} else {
|
||||
this.remainingAuthMethods.push({
|
||||
this.allAuthMethods.push({
|
||||
type: 'agent',
|
||||
...spec,
|
||||
})
|
||||
}
|
||||
}
|
||||
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 })
|
||||
this.allAuthMethods.push({ type: 'saved-password', password: this.profile.options.password })
|
||||
}
|
||||
const password = await this.passwordStorage.loadPassword(this.profile)
|
||||
if (password) {
|
||||
this.remainingAuthMethods.push({ type: 'saved-password', password })
|
||||
this.allAuthMethods.push({ type: 'saved-password', password })
|
||||
}
|
||||
this.remainingAuthMethods.push({ type: 'prompt-password' })
|
||||
}
|
||||
this.remainingAuthMethods.push({ type: 'hostbased' })
|
||||
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.allAuthMethods.push({ type: 'keyboard-interactive', savedPassword })
|
||||
}
|
||||
this.allAuthMethods.push({ type: 'keyboard-interactive' })
|
||||
}
|
||||
if (!this.profile.options.auth || this.profile.options.auth === 'password') {
|
||||
this.allAuthMethods.push({ type: 'prompt-password' })
|
||||
}
|
||||
this.allAuthMethods.push({ type: 'hostbased' })
|
||||
}
|
||||
|
||||
private async getAgentConnectionSpec (): Promise<russh.AgentConnectionSpec|null> {
|
||||
@@ -323,9 +328,14 @@ export class SSHSession {
|
||||
}
|
||||
})
|
||||
|
||||
this.previouslyDisconnected = false
|
||||
this.ssh.disconnect$.subscribe(() => {
|
||||
if (this.open) {
|
||||
this.destroy()
|
||||
if (!this.previouslyDisconnected) {
|
||||
this.previouslyDisconnected = true
|
||||
// Let service messages drain
|
||||
setTimeout(() => {
|
||||
this.destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -508,6 +518,22 @@ export class SSHSession {
|
||||
}
|
||||
|
||||
async handleAuth (): Promise<russh.AuthenticatedSSHClient|null> {
|
||||
const subscription = this.ssh.disconnect$.subscribe(() => {
|
||||
// Auto auth and >=3 keys found
|
||||
if (!this.profile.options.auth && this.allAuthMethods.filter(x => x.type === 'publickey').length >= 3) {
|
||||
this.emitServiceMessage('The server has disconnected during authentication.')
|
||||
this.emitServiceMessage('This may happen if too many private key authentication attemps are made.')
|
||||
this.emitServiceMessage('You can set the specific private key for authentication in the profile settings.')
|
||||
}
|
||||
})
|
||||
try {
|
||||
return await this._handleAuth()
|
||||
} finally {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleAuth (): Promise<russh.AuthenticatedSSHClient|null> {
|
||||
this.activePrivateKey = null
|
||||
|
||||
if (!(this.ssh instanceof russh.SSHClient)) {
|
||||
@@ -523,6 +549,7 @@ export class SSHSession {
|
||||
return noneResult
|
||||
}
|
||||
|
||||
let remainingMethods = [...this.allAuthMethods]
|
||||
let methodsLeft = noneResult.remainingMethods
|
||||
|
||||
function maybeSetRemainingMethods (r: russh.AuthFailure) {
|
||||
@@ -533,13 +560,13 @@ export class SSHSession {
|
||||
|
||||
while (true) {
|
||||
const m = methodsLeft
|
||||
const method = this.remainingAuthMethods.find(x => m.length === 0 || m.includes(sshAuthTypeForMethod(x)))
|
||||
const method = remainingMethods.find(x => m.length === 0 || m.includes(sshAuthTypeForMethod(x)))
|
||||
|
||||
if (!method) {
|
||||
if (this.previouslyDisconnected || !method) {
|
||||
return null
|
||||
}
|
||||
|
||||
this.remainingAuthMethods = this.remainingAuthMethods.filter(x => x !== method)
|
||||
remainingMethods = remainingMethods.filter(x => x !== method)
|
||||
|
||||
if (method.type === 'saved-password') {
|
||||
this.emitServiceMessage(this.translate.instant('Using saved password'))
|
||||
@@ -576,15 +603,12 @@ export class SSHSession {
|
||||
if (method.type === 'publickey') {
|
||||
try {
|
||||
const key = await this.loadPrivateKey(method.name, method.contents)
|
||||
const possibleHashAlgs = ['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512'].includes(key.algorithm) ? ['sha256', 'sha512', 'sha1'] as const : [null] as const
|
||||
this.emitServiceMessage(`Trying private key: ${method.name}`)
|
||||
for (const alg of possibleHashAlgs) {
|
||||
const result = await this.ssh.authenticateWithKeyPair(this.authUsername, key, alg)
|
||||
if (result instanceof russh.AuthenticatedSSHClient) {
|
||||
return result
|
||||
}
|
||||
maybeSetRemainingMethods(result)
|
||||
const result = await this.ssh.authenticateWithKeyPair(this.authUsername, key, null)
|
||||
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}`)
|
||||
continue
|
||||
|
@@ -3,7 +3,7 @@ import { Subject, Observable } from 'rxjs'
|
||||
import { SessionMiddleware } from '../api/middleware'
|
||||
|
||||
const OSCPrefix = Buffer.from('\x1b]')
|
||||
const OSCSuffix = Buffer.from('\x07')
|
||||
const OSCSuffixes = [Buffer.from('\x07'), Buffer.from('\x1b\\')]
|
||||
|
||||
export class OSCProcessor extends SessionMiddleware {
|
||||
get cwdReported$ (): Observable<string> { return this.cwdReported }
|
||||
@@ -14,11 +14,22 @@ export class OSCProcessor extends SessionMiddleware {
|
||||
|
||||
feedFromSession (data: Buffer): void {
|
||||
let startIndex = 0
|
||||
while (data.includes(OSCPrefix, startIndex) && data.includes(OSCSuffix, startIndex)) {
|
||||
const params = data.subarray(data.indexOf(OSCPrefix, startIndex) + OSCPrefix.length)
|
||||
const oscString = params.subarray(0, params.indexOf(OSCSuffix)).toString()
|
||||
while (data.includes(OSCPrefix, startIndex)) {
|
||||
const si = startIndex
|
||||
if (!OSCSuffixes.some(s => data.includes(s, si))) {
|
||||
break
|
||||
}
|
||||
|
||||
startIndex = data.indexOf(OSCSuffix, startIndex) + OSCSuffix.length
|
||||
const params = data.subarray(data.indexOf(OSCPrefix, startIndex) + OSCPrefix.length)
|
||||
|
||||
const [closesSuffix, closestSuffixIndex] = OSCSuffixes
|
||||
.map((suffix): [Buffer, number] => [suffix, params.indexOf(suffix)])
|
||||
.filter(([_, index]) => index !== -1)
|
||||
.sort(([_, a], [__, b]) => a - b)[0]
|
||||
|
||||
const oscString = params.subarray(0, closestSuffixIndex).toString()
|
||||
|
||||
startIndex = data.indexOf(closesSuffix, startIndex) + closesSuffix.length
|
||||
|
||||
const [oscCodeString, ...oscParams] = oscString.split(';')
|
||||
const oscCode = parseInt(oscCodeString)
|
||||
|
Reference in New Issue
Block a user