@@ -143,6 +143,11 @@ interface ChannelWindowChangeRequestMessage extends ChannelRequestMessage {
143143 height_px : number ;
144144}
145145
146+ interface ChannelExecRequestMessage extends ChannelRequestMessage {
147+ request_type : "exec" ;
148+ command : string ;
149+ }
150+
146151interface ChannelSuccessMessage extends SSHMessageBase {
147152 type : "CHANNEL_SUCCESS" ;
148153 recipient_channel : number ;
@@ -162,7 +167,7 @@ type SSHMessage =
162167 | UserAuthRequestMessage | UserAuthFailureMessage | UserAuthSuccessMessage | UserAuthBannerMessage
163168 | ChannelOpenMessage | ChannelOpenConfirmationMessage | ChannelOpenFailureMessage |
164169 ChannelRequestMessage | ChannelSuccessMessage | ChannelFailureMessage |
165- ChannelWindowChangeRequestMessage |
170+ ChannelWindowChangeRequestMessage | ChannelExecRequestMessage |
166171 ChannelDataMessage ;
167172
168173type SSHMessageType = SSHMessage [ "type" ] ;
@@ -462,6 +467,9 @@ const message_deserialisers: Record<number, (reader: MessageReader) => SSHMessag
462467 const height_px = reader . read_uint32 ( ) ;
463468
464469 return { type : "CHANNEL_REQUEST" , recipient_channel, request_type, want_reply, cols, rows, width_px, height_px } ;
470+ } else if ( request_type === "exec" ) {
471+ const command = reader . read_string ( ) ;
472+ return { type : "CHANNEL_REQUEST" , recipient_channel, request_type, want_reply, command } ;
465473 }
466474
467475 return { type : "CHANNEL_REQUEST" , recipient_channel, request_type, want_reply } ;
@@ -1323,6 +1331,54 @@ export default {
13231331
13241332 await send_encrypted_message_atomic ( success_message ) ;
13251333 }
1334+ } else if ( message . request_type === "exec" ) {
1335+ if ( ! channel . terminal ) {
1336+ // create a terminal if it doesn't exist, since exec can be used without pty-req
1337+ channel . terminal = new SSHTerminal ( message . recipient_channel ) ;
1338+ }
1339+
1340+ const command = ( message as ChannelExecRequestMessage ) . command ;
1341+
1342+ // spawn the exec process
1343+ // TODO: when piping exists then pipe channel data to process stdin
1344+ let spawn_result : SpawnResult ;
1345+ try {
1346+ spawn_result = kernel . spawn (
1347+ command ,
1348+ [ ] ,
1349+ undefined ,
1350+ false ,
1351+ channel . terminal
1352+ ) ;
1353+ } catch ( e ) {
1354+ console . error ( "Failed to spawn process for exec request:" , e ) ;
1355+
1356+ if ( message . want_reply ) {
1357+ const failure_message : ChannelFailureMessage = {
1358+ type : "CHANNEL_FAILURE" ,
1359+ recipient_channel : message . recipient_channel
1360+ } ;
1361+
1362+ await send_encrypted_message_atomic ( failure_message ) ;
1363+ }
1364+
1365+ return ;
1366+ }
1367+
1368+ // acknowledge the spawn
1369+ if ( message . want_reply ) {
1370+ const success_message : ChannelSuccessMessage = {
1371+ type : "CHANNEL_SUCCESS" ,
1372+ recipient_channel : message . recipient_channel
1373+ }
1374+
1375+ await send_encrypted_message_atomic ( success_message ) ;
1376+ }
1377+
1378+ // handle process termination
1379+ spawn_result . completion . then ( ( exit_code ) => {
1380+ spawn_result . process . kill ( exit_code ) ;
1381+ } ) ;
13261382 } else {
13271383 // unsupported request type, ignore
13281384 if ( message . want_reply ) {
0 commit comments