@@ -152,6 +152,77 @@ function handleSocks4(sock: net.Socket, sshClient: Client, buf: Buffer): void {
152152 } )
153153}
154154
155+ function connectViaProxy (
156+ proxy : { type : 'socks5' | 'socks4' | 'http' ; host : string ; port : number ; username ?: string ; password ?: string } ,
157+ targetHost : string , targetPort : number
158+ ) : Promise < net . Socket > {
159+ return new Promise ( ( resolve , reject ) => {
160+ const sock = net . connect ( proxy . port , proxy . host , ( ) => {
161+ if ( proxy . type === 'http' ) {
162+ const auth = proxy . username && proxy . password
163+ ? `\r\nProxy-Authorization: Basic ${ Buffer . from ( `${ proxy . username } :${ proxy . password } ` ) . toString ( 'base64' ) } `
164+ : ''
165+ sock . write ( `CONNECT ${ targetHost } :${ targetPort } HTTP/1.1\r\nHost: ${ targetHost } :${ targetPort } ${ auth } \r\n\r\n` )
166+ sock . once ( 'data' , ( chunk ) => {
167+ const resp = chunk . toString ( )
168+ if ( resp . includes ( '200' ) ) resolve ( sock )
169+ else { sock . destroy ( ) ; reject ( new Error ( `HTTP proxy rejected: ${ resp . split ( '\r\n' ) [ 0 ] } ` ) ) }
170+ } )
171+ } else {
172+ // SOCKS4/5
173+ if ( proxy . type === 'socks5' ) {
174+ const hasAuth = proxy . username && proxy . password
175+ const authMethods = hasAuth ? Buffer . from ( [ 0x05 , 0x02 , 0x00 , 0x02 ] ) : Buffer . from ( [ 0x05 , 0x01 , 0x00 ] )
176+ sock . write ( authMethods )
177+ sock . once ( 'data' , ( greeting ) => {
178+ if ( greeting [ 1 ] === 0x02 && hasAuth ) {
179+ const uBuf = Buffer . from ( proxy . username ! )
180+ const pBuf = Buffer . from ( proxy . password ! )
181+ const authBuf = Buffer . concat ( [ Buffer . from ( [ 0x01 , uBuf . length ] ) , uBuf , Buffer . from ( [ pBuf . length ] ) , pBuf ] )
182+ sock . write ( authBuf )
183+ sock . once ( 'data' , ( authResp ) => {
184+ if ( authResp [ 1 ] !== 0x00 ) { sock . destroy ( ) ; return reject ( new Error ( 'SOCKS5 auth failed' ) ) }
185+ sendSocks5Connect ( sock , targetHost , targetPort , resolve , reject )
186+ } )
187+ } else if ( greeting [ 1 ] === 0x00 ) {
188+ sendSocks5Connect ( sock , targetHost , targetPort , resolve , reject )
189+ } else {
190+ sock . destroy ( ) ; reject ( new Error ( 'SOCKS5 no acceptable auth method' ) )
191+ }
192+ } )
193+ } else {
194+ // SOCKS4
195+ const portBuf = Buffer . alloc ( 2 )
196+ portBuf . writeUInt16BE ( targetPort , 0 )
197+ const ipBuf = Buffer . from ( [ 0 , 0 , 0 , 1 ] ) // SOCKS4a: invalid IP
198+ const userBuf = Buffer . from ( proxy . username ?? '' )
199+ const hostBuf = Buffer . from ( targetHost )
200+ const req = Buffer . concat ( [ Buffer . from ( [ 0x04 , 0x01 ] ) , portBuf , ipBuf , userBuf , Buffer . from ( [ 0x00 ] ) , hostBuf , Buffer . from ( [ 0x00 ] ) ] )
201+ sock . write ( req )
202+ sock . once ( 'data' , ( resp ) => {
203+ if ( resp [ 1 ] === 0x5a ) resolve ( sock )
204+ else { sock . destroy ( ) ; reject ( new Error ( `SOCKS4 rejected: code ${ resp [ 1 ] } ` ) ) }
205+ } )
206+ }
207+ }
208+ } )
209+ sock . on ( 'error' , ( err ) => reject ( new Error ( `Proxy connection failed: ${ err . message } ` ) ) )
210+ sock . setTimeout ( 15000 , ( ) => { sock . destroy ( ) ; reject ( new Error ( 'Proxy connection timeout' ) ) } )
211+ } )
212+ }
213+
214+ function sendSocks5Connect ( sock : net . Socket , host : string , port : number , resolve : ( s : net . Socket ) => void , reject : ( e : Error ) => void ) {
215+ const hostBuf = Buffer . from ( host )
216+ const portBuf = Buffer . alloc ( 2 )
217+ portBuf . writeUInt16BE ( port , 0 )
218+ const req = Buffer . concat ( [ Buffer . from ( [ 0x05 , 0x01 , 0x00 , 0x03 , hostBuf . length ] ) , hostBuf , portBuf ] )
219+ sock . write ( req )
220+ sock . once ( 'data' , ( resp ) => {
221+ if ( resp [ 1 ] === 0x00 ) { sock . setTimeout ( 0 ) ; resolve ( sock ) }
222+ else { sock . destroy ( ) ; reject ( new Error ( `SOCKS5 connect failed: code ${ resp [ 1 ] } ` ) ) }
223+ } )
224+ }
225+
155226export function setupSshHandlers (
156227 ipcMain : IpcMain ,
157228 getWindow : ( ) => BrowserWindow | null
@@ -180,6 +251,13 @@ export function setupSshHandlers(
180251 privateKey ?: string
181252 passphrase ?: string
182253 }
254+ proxy ?: {
255+ type : 'socks5' | 'socks4' | 'http'
256+ host : string
257+ port : number
258+ username ?: string
259+ password ?: string
260+ }
183261 }
184262 ) => {
185263 return new Promise ( ( resolve , reject ) => {
@@ -299,15 +377,26 @@ export function setupSshHandlers(
299377 ) )
300378
301379 } else {
302- // ── Direct connection ─────────────────────────────────────────────────
303- const client = new Client ( )
304- client . on ( 'error' , ( err ) => settle ( { success : false , error : err . message } ) )
305- client . on ( 'ready' , ( ) => openShell ( client , null ) )
306- client . connect ( buildConnectConfig (
307- payload . host , payload . port , payload . username ,
308- payload . password , payload . privateKey , payload . passphrase ,
309- payload . readyTimeout ?? 30000 , payload . keepaliveInterval ?? 30000
310- ) )
380+ // ── Direct connection (possibly through proxy) ─────────────────────
381+ const connectDirect = ( sock ?: net . Socket ) => {
382+ const client = new Client ( )
383+ client . on ( 'error' , ( err ) => settle ( { success : false , error : err . message } ) )
384+ client . on ( 'ready' , ( ) => openShell ( client , null ) )
385+ client . connect ( buildConnectConfig (
386+ payload . host , payload . port , payload . username ,
387+ payload . password , payload . privateKey , payload . passphrase ,
388+ payload . readyTimeout ?? 30000 , payload . keepaliveInterval ?? 30000 ,
389+ sock
390+ ) )
391+ }
392+
393+ if ( payload . proxy ) {
394+ connectViaProxy ( payload . proxy , payload . host , payload . port )
395+ . then ( ( proxySock ) => connectDirect ( proxySock ) )
396+ . catch ( ( err ) => settle ( { success : false , error : `Proxy: ${ err . message } ` } ) )
397+ } else {
398+ connectDirect ( )
399+ }
311400 }
312401 } )
313402 }
0 commit comments