|
|
@@ -33,14 +33,31 @@ export class SSHService {
|
|
|
this.logger = log.create('ssh')
|
|
|
}
|
|
|
|
|
|
- async connect (connection: SSHConnection): Promise<SSHTabComponent> {
|
|
|
+ async openTab (connection: SSHConnection): Promise<SSHTabComponent> {
|
|
|
+ return this.zone.run(() => this.app.openNewTab(
|
|
|
+ SSHTabComponent,
|
|
|
+ { connection }
|
|
|
+ ) as SSHTabComponent)
|
|
|
+ }
|
|
|
+
|
|
|
+ async connectSession (session: SSHSession, logCallback?: (s: string) => void): Promise<void> {
|
|
|
let privateKey: string = null
|
|
|
let privateKeyPassphrase: string = null
|
|
|
- let privateKeyPath = connection.privateKey
|
|
|
+ let privateKeyPath = session.connection.privateKey
|
|
|
+
|
|
|
+ if (!logCallback) {
|
|
|
+ logCallback = (s) => null
|
|
|
+ }
|
|
|
+
|
|
|
+ const log = s => {
|
|
|
+ logCallback(s)
|
|
|
+ this.logger.info(s)
|
|
|
+ }
|
|
|
+
|
|
|
if (!privateKeyPath) {
|
|
|
let userKeyPath = path.join(process.env.HOME, '.ssh', 'id_rsa')
|
|
|
if (await fs.exists(userKeyPath)) {
|
|
|
- this.logger.info('Using user\'s default private key:', userKeyPath)
|
|
|
+ log(`Using user's default private key: ${userKeyPath}`)
|
|
|
privateKeyPath = userKeyPath
|
|
|
}
|
|
|
}
|
|
|
@@ -49,11 +66,12 @@ export class SSHService {
|
|
|
try {
|
|
|
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
|
|
} catch (error) {
|
|
|
+ log('Could not read the private key file')
|
|
|
this.toastr.warning('Could not read the private key file')
|
|
|
}
|
|
|
|
|
|
if (privateKey) {
|
|
|
- this.logger.info('Loaded private key from', privateKeyPath)
|
|
|
+ log(`Loading private key from ${privateKeyPath}`)
|
|
|
|
|
|
let encrypted = privateKey.includes('ENCRYPTED')
|
|
|
if (privateKeyPath.toLowerCase().endsWith('.ppk')) {
|
|
|
@@ -61,6 +79,7 @@ export class SSHService {
|
|
|
}
|
|
|
if (encrypted) {
|
|
|
let modal = this.ngbModal.open(PromptModalComponent)
|
|
|
+ log('Key requires passphrase')
|
|
|
modal.componentInstance.prompt = 'Private key passphrase'
|
|
|
modal.componentInstance.password = true
|
|
|
try {
|
|
|
@@ -77,12 +96,12 @@ export class SSHService {
|
|
|
ssh.on('ready', () => {
|
|
|
connected = true
|
|
|
if (savedPassword) {
|
|
|
- this.passwordStorage.savePassword(connection, savedPassword)
|
|
|
+ this.passwordStorage.savePassword(session.connection, savedPassword)
|
|
|
}
|
|
|
this.zone.run(resolve)
|
|
|
})
|
|
|
ssh.on('error', error => {
|
|
|
- this.passwordStorage.deletePassword(connection)
|
|
|
+ this.passwordStorage.deletePassword(session.connection)
|
|
|
this.zone.run(() => {
|
|
|
if (connected) {
|
|
|
this.toastr.error(error.toString())
|
|
|
@@ -92,7 +111,8 @@ export class SSHService {
|
|
|
})
|
|
|
})
|
|
|
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
|
|
- console.log(name, instructions, instructionsLang)
|
|
|
+ log(`Keyboard-interactive auth requested: ${name}`)
|
|
|
+ this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang)
|
|
|
let results = []
|
|
|
for (let prompt of prompts) {
|
|
|
let modal = this.ngbModal.open(PromptModalComponent)
|
|
|
@@ -103,6 +123,14 @@ export class SSHService {
|
|
|
finish(results)
|
|
|
}))
|
|
|
|
|
|
+ ssh.on('greeting', greeting => {
|
|
|
+ log('Greeting: ' + greeting)
|
|
|
+ })
|
|
|
+
|
|
|
+ ssh.on('banner', banner => {
|
|
|
+ log('Banner: ' + banner)
|
|
|
+ })
|
|
|
+
|
|
|
let agent: string = null
|
|
|
if (this.hostApp.platform === Platform.Windows) {
|
|
|
let pageantRunning = new Promise<boolean>(resolve => {
|
|
|
@@ -119,50 +147,61 @@ export class SSHService {
|
|
|
|
|
|
try {
|
|
|
ssh.connect({
|
|
|
- host: connection.host,
|
|
|
- port: connection.port || 22,
|
|
|
- username: connection.user,
|
|
|
- password: connection.privateKey ? undefined : '',
|
|
|
+ host: session.connection.host,
|
|
|
+ port: session.connection.port || 22,
|
|
|
+ username: session.connection.user,
|
|
|
+ password: session.connection.privateKey ? undefined : '',
|
|
|
privateKey,
|
|
|
passphrase: privateKeyPassphrase,
|
|
|
tryKeyboard: true,
|
|
|
agent,
|
|
|
agentForward: !!agent,
|
|
|
- keepaliveInterval: connection.keepaliveInterval,
|
|
|
- keepaliveCountMax: connection.keepaliveCountMax,
|
|
|
- readyTimeout: connection.readyTimeout,
|
|
|
+ keepaliveInterval: session.connection.keepaliveInterval,
|
|
|
+ keepaliveCountMax: session.connection.keepaliveCountMax,
|
|
|
+ readyTimeout: session.connection.readyTimeout,
|
|
|
+ debug: (...x) => console.log(...x),
|
|
|
+ hostVerifier: digest => {
|
|
|
+ log('SHA256 fingerprint: ' + digest)
|
|
|
+ return true
|
|
|
+ },
|
|
|
+ hostHash: 'sha256' as any,
|
|
|
})
|
|
|
} catch (e) {
|
|
|
this.toastr.error(e.message)
|
|
|
+ reject(e)
|
|
|
}
|
|
|
|
|
|
let keychainPasswordUsed = false
|
|
|
|
|
|
;(ssh as any).config.password = () => this.zone.run(async () => {
|
|
|
- if (connection.password) {
|
|
|
- this.logger.info('Using preset password')
|
|
|
- return connection.password
|
|
|
+ if (session.connection.password) {
|
|
|
+ log('Using preset password')
|
|
|
+ return session.connection.password
|
|
|
}
|
|
|
|
|
|
if (!keychainPasswordUsed) {
|
|
|
- let password = await this.passwordStorage.loadPassword(connection)
|
|
|
+ let password = await this.passwordStorage.loadPassword(session.connection)
|
|
|
if (password) {
|
|
|
- this.logger.info('Using saved password')
|
|
|
+ log('Trying saved password')
|
|
|
keychainPasswordUsed = true
|
|
|
return password
|
|
|
}
|
|
|
}
|
|
|
|
|
|
let modal = this.ngbModal.open(PromptModalComponent)
|
|
|
- modal.componentInstance.prompt = `Password for ${connection.user}@${connection.host}`
|
|
|
+ modal.componentInstance.prompt = `Password for ${session.connection.user}@${session.connection.host}`
|
|
|
modal.componentInstance.password = true
|
|
|
- savedPassword = await modal.result
|
|
|
+ try {
|
|
|
+ savedPassword = await modal.result
|
|
|
+ } catch (_) {
|
|
|
+ return ''
|
|
|
+ }
|
|
|
return savedPassword
|
|
|
})
|
|
|
})
|
|
|
|
|
|
try {
|
|
|
- let shell = await new Promise((resolve, reject) => {
|
|
|
+ let shell: any = await new Promise<any>((resolve, reject) => {
|
|
|
ssh.shell({ term: 'xterm-256color' }, (err, shell) => {
|
|
|
if (err) {
|
|
|
reject(err)
|
|
|
@@ -172,14 +211,17 @@ export class SSHService {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
- let session = new SSHSession(shell, connection)
|
|
|
+ session.shell = shell
|
|
|
|
|
|
- return this.zone.run(() => this.app.openNewTab(
|
|
|
- SSHTabComponent,
|
|
|
- { session, sessionOptions: {} }
|
|
|
- ) as SSHTabComponent)
|
|
|
+ shell.on('greeting', greeting => {
|
|
|
+ log('Shell Greeting: ' + greeting)
|
|
|
+ })
|
|
|
+
|
|
|
+ shell.on('banner', banner => {
|
|
|
+ log('Shell Banner: ' + banner)
|
|
|
+ })
|
|
|
} catch (error) {
|
|
|
- console.log(error)
|
|
|
+ this.toastr.error(error.message)
|
|
|
throw error
|
|
|
}
|
|
|
}
|