@@ -18,16 +18,18 @@ import * as ssh2 from 'ssh2';
1818import * as net from 'net' ;
1919import * as fs from '@theia/core/shared/fs-extra' ;
2020import SftpClient = require( 'ssh2-sftp-client' ) ;
21+ import * as SshConfig from 'ssh-config' ;
2122import { Emitter , Event , MessageService , QuickInputService } from '@theia/core' ;
2223import { inject , injectable } from '@theia/core/shared/inversify' ;
23- import { RemoteSSHConnectionProvider , RemoteSSHConnectionProviderOptions } from '../../electron-common/remote-ssh-connection-provider' ;
24+ import { RemoteSSHConnectionProvider , RemoteSSHConnectionProviderOptions , SSHConfig } from '../../electron-common/remote-ssh-connection-provider' ;
2425import { RemoteConnectionService } from '../remote-connection-service' ;
2526import { RemoteProxyServerProvider } from '../remote-proxy-server-provider' ;
2627import { RemoteConnection , RemoteExecOptions , RemoteExecResult , RemoteExecTester , RemoteStatusReport } from '../remote-types' ;
2728import { Deferred , timeout } from '@theia/core/lib/common/promise-util' ;
2829import { SSHIdentityFileCollector , SSHKey } from './ssh-identity-file-collector' ;
2930import { RemoteSetupService } from '../setup/remote-setup-service' ;
3031import { generateUuid } from '@theia/core/lib/common/uuid' ;
32+ import { EnvVariablesServer , EnvVariable } from '@theia/core/lib/common/env-variables' ;
3133
3234@injectable ( )
3335export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvider {
@@ -50,17 +52,68 @@ export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvi
5052 @inject ( MessageService )
5153 protected readonly messageService : MessageService ;
5254
55+ @inject ( EnvVariablesServer )
56+ protected readonly envVariablesServer : EnvVariablesServer ;
57+
5358 protected passwordRetryCount = 3 ;
5459 protected passphraseRetryCount = 3 ;
5560
61+ async matchSSHConfigHost ( host : string , user ?: string , customConfigFile ?: string ) : Promise < Record < string , string | string [ ] > | undefined > {
62+ const sshConfig = await this . getSSHConfig ( customConfigFile ) ;
63+ const host2 = host . trim ( ) . split ( ':' ) ;
64+
65+ const record = Object . fromEntries (
66+ Object . entries ( sshConfig . compute ( host2 [ 0 ] ) ) . map ( ( [ k , v ] ) => [ k . toLowerCase ( ) , v ] )
67+ ) ;
68+
69+ // Generate a regexp to find wildcards and process the hostname with the wildcards
70+ if ( record . host ) {
71+ const checkHost = new RegExp ( '^' + ( < string > record . host )
72+ . replace ( / ( [ ^ \w \* \? ] ) / g, '\\$1' )
73+ . replace ( / ( [ \? ] + ) / g, ( ...m ) => '(' + '.' . repeat ( m [ 1 ] . length ) + ')' )
74+ . replace ( / \* / g, '(.+)' ) + '$' ) ;
75+
76+ const match = host2 [ 0 ] . match ( checkHost ) ;
77+ if ( match ) {
78+ if ( record . hostname ) {
79+ record . hostname = ( < string > record . hostname ) . replace ( '%h' , match [ 1 ] ) ;
80+ }
81+ }
82+
83+ if ( host2 [ 1 ] ) {
84+ record . port = host2 [ 1 ] ;
85+ }
86+ }
87+
88+ return record ;
89+ }
90+
91+ async getSSHConfig ( customConfigFile ?: string ) : Promise < SSHConfig > {
92+ const reg = / \$ \{ e n v : ( .+ ?) \} / ;
93+ const promises : Promise < EnvVariable | undefined > [ ] = [ ] ;
94+ customConfigFile ?. replace ( reg , ( ...m ) => {
95+ promises . push ( this . envVariablesServer . getValue ( m [ 1 ] ) ) ;
96+ return m [ 0 ] ;
97+ } ) ;
98+
99+ const repVal = await Promise . all ( promises ) ;
100+ const sshConfigFilePath = customConfigFile ! . replace ( reg , ( ) => repVal . shift ( ) ?. value || '' ) ;
101+
102+ const buff : Buffer = await fs . promises . readFile ( sshConfigFilePath ) ;
103+
104+ const sshConfig = SshConfig . parse ( buff . toString ( ) ) ;
105+
106+ return sshConfig ;
107+ }
108+
56109 async establishConnection ( options : RemoteSSHConnectionProviderOptions ) : Promise < string > {
57110 const progress = await this . messageService . showProgress ( {
58111 text : 'Remote SSH'
59112 } ) ;
60113 const report : RemoteStatusReport = message => progress . report ( { message } ) ;
61114 report ( 'Connecting to remote system...' ) ;
62115 try {
63- const remote = await this . establishSSHConnection ( options . host , options . user ) ;
116+ const remote = await this . establishSSHConnection ( options . host , options . user , options . customConfigFile ) ;
64117 await this . remoteSetup . setup ( {
65118 connection : remote ,
66119 report,
@@ -82,10 +135,26 @@ export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvi
82135 }
83136 }
84137
85- async establishSSHConnection ( host : string , user : string ) : Promise < RemoteSSHConnection > {
138+ async establishSSHConnection ( host : string , user : string , customConfigFile ?: string ) : Promise < RemoteSSHConnection > {
86139 const deferred = new Deferred < RemoteSSHConnection > ( ) ;
87140 const sshClient = new ssh2 . Client ( ) ;
88- const identityFiles = await this . identityFileCollector . gatherIdentityFiles ( ) ;
141+ const sshHostConfig = await this . matchSSHConfigHost ( host , user , customConfigFile ) ;
142+ const identityFiles = await this . identityFileCollector . gatherIdentityFiles ( undefined , < string [ ] > sshHostConfig ?. identityfile ) ;
143+
144+ let algorithms : ssh2 . Algorithms | undefined = undefined ;
145+ if ( sshHostConfig ) {
146+ if ( ! user && sshHostConfig . user ) {
147+ user = < string > sshHostConfig . user ;
148+ }
149+ if ( sshHostConfig . hostname ) {
150+ host = sshHostConfig . hostname + ':' + ( sshHostConfig . port || '22' ) ;
151+ } else if ( sshHostConfig . port ) {
152+ host = sshHostConfig . host + ':' + ( sshHostConfig . port || '22' ) ;
153+ }
154+ if ( sshHostConfig . compression && ( < string > sshHostConfig . compression ) . toLowerCase ( ) === 'yes' ) {
155+ algorithms = { compress :
[ '[email protected] ' , 'zlib' ] } ; 156+ }
157+ }
89158 const hostUrl = new URL ( `ssh://${ host } ` ) ;
90159 const sshAuthHandler = this . getAuthHandler ( user , hostUrl . hostname , identityFiles ) ;
91160 sshClient
@@ -110,6 +179,7 @@ export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvi
110179 host : hostUrl . hostname ,
111180 port : hostUrl . port ? parseInt ( hostUrl . port , 10 ) : undefined ,
112181 username : user ,
182+ algorithms : algorithms ,
113183 authHandler : ( methodsLeft , successes , callback ) => ( sshAuthHandler ( methodsLeft , successes , callback ) , undefined )
114184 } ) ;
115185 return deferred . promise ;
0 commit comments