split openssh-importer into tabby-electron, support tilde in private key paths - fixes #5627

This commit is contained in:
Eugene Pankov
2022-01-28 22:49:52 +01:00
parent b469dd603b
commit 9e9066d3cd
6 changed files with 147 additions and 129 deletions

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'tabby-core'
import { TerminalColorSchemeProvider } from 'tabby-terminal'
import { SFTPContextMenuItemProvider } from 'tabby-ssh'
import { SFTPContextMenuItemProvider, SSHProfileImporter } from 'tabby-ssh'
import { auditTime } from 'rxjs'
import { HyperColorSchemes } from './colorSchemes'
@@ -17,6 +17,7 @@ import { ElectronService } from './services/electron.service'
import { ElectronHotkeyProvider } from './hotkeys'
import { ElectronConfigProvider } from './config'
import { EditSFTPContextMenu } from './sftpContextMenu'
import { OpenSSHImporter } from './openSSHImport'
@NgModule({
providers: [
@@ -31,6 +32,7 @@ import { EditSFTPContextMenu } from './sftpContextMenu'
{ provide: ConfigProvider, useClass: ElectronConfigProvider, multi: true },
{ provide: FileProvider, useClass: ElectronFileProvider, multi: true },
{ provide: SFTPContextMenuItemProvider, useClass: EditSFTPContextMenu, multi: true },
{ provide: SSHProfileImporter, useClass: OpenSSHImporter, multi: true },
],
})
export default class ElectronModule {

View File

@@ -0,0 +1,128 @@
import * as fs from 'fs/promises'
import * as path from 'path'
import slugify from 'slugify'
import { PartialProfile } from 'tabby-core'
import { SSHProfileImporter, PortForwardType, SSHProfile, SSHProfileOptions } from 'tabby-ssh'
function deriveID (name: string): string {
return 'openssh-config:' + slugify(name)
}
export class OpenSSHImporter extends SSHProfileImporter {
async getProfiles (): Promise<PartialProfile<SSHProfile>[]> {
const results: PartialProfile<SSHProfile>[] = []
const configPath = path.join(process.env.HOME ?? '~', '.ssh', 'config')
try {
const lines = (await fs.readFile(configPath, 'utf8')).split('\n')
const globalOptions: Partial<SSHProfileOptions> = {}
let currentProfile: PartialProfile<SSHProfile>|null = null
for (let line of lines) {
if (line.trim().startsWith('#') || !line.trim()) {
continue
}
if (line.startsWith('Host ')) {
if (currentProfile) {
results.push(currentProfile)
}
const name = line.substr(5).trim()
currentProfile = {
id: deriveID(name),
name: `${name} (.ssh/config)`,
type: 'ssh',
group: 'Imported from .ssh/config',
options: {
...globalOptions,
host: name,
},
}
} else {
const target: Partial<SSHProfileOptions> = currentProfile?.options ?? globalOptions
line = line.trim()
const idx = /\s/.exec(line)?.index ?? -1
if (idx === -1) {
continue
}
const key = line.substr(0, idx).trim()
const value = line.substr(idx + 1).trim()
if (key === 'IdentityFile') {
target.privateKeys = value.split(',').map(s => s.trim()).map(s => {
if (s.startsWith('~')) {
s = path.join(process.env.HOME ?? '~', s.slice(2))
}
return s
})
} else if (key === 'RemoteForward') {
const bind = value.split(/\s/)[0].trim()
const tgt = value.split(/\s/)[1].trim()
target.forwardedPorts ??= []
target.forwardedPorts.push({
type: PortForwardType.Remote,
description: value,
host: bind.split(':')[0] ?? '127.0.0.1',
port: parseInt(bind.split(':')[1] ?? bind),
targetAddress: tgt.split(':')[0],
targetPort: parseInt(tgt.split(':')[1]),
})
} else if (key === 'LocalForward') {
const bind = value.split(/\s/)[0].trim()
const tgt = value.split(/\s/)[1].trim()
target.forwardedPorts ??= []
target.forwardedPorts.push({
type: PortForwardType.Local,
description: value,
host: bind.split(':')[0] ?? '127.0.0.1',
port: parseInt(bind.split(':')[1] ?? bind),
targetAddress: tgt.split(':')[0],
targetPort: parseInt(tgt.split(':')[1]),
})
} else if (key === 'DynamicForward') {
const bind = value.trim()
target.forwardedPorts ??= []
target.forwardedPorts.push({
type: PortForwardType.Dynamic,
description: value,
host: bind.split(':')[0] ?? '127.0.0.1',
port: parseInt(bind.split(':')[1] ?? bind),
targetAddress: '',
targetPort: 22,
})
} else {
const mappedKey = {
Hostname: 'host',
Port: 'port',
User: 'user',
ForwardX11: 'x11',
ServerAliveInterval: 'keepaliveInterval',
ServerAliveCountMax: 'keepaliveCountMax',
ProxyCommand: 'proxyCommand',
ProxyJump: 'jumpHost',
}[key]
if (mappedKey) {
target[mappedKey] = value
}
}
}
}
if (currentProfile) {
results.push(currentProfile)
}
for (const p of results) {
if (p.options?.proxyCommand) {
p.options.proxyCommand = p.options.proxyCommand
.replace('%h', p.options.host ?? '')
.replace('%p', (p.options.port ?? 22).toString())
}
if (p.options?.jumpHost) {
p.options.jumpHost = deriveID(p.options.jumpHost)
}
}
return results
} catch (e) {
if (e.code === 'ENOENT') {
return []
}
throw e
}
}
}