@@ -31,8 +31,10 @@ use zeroize::Zeroizing;
3131use super :: auth:: AuthProvider ;
3232use super :: config:: ServerConfig ;
3333use super :: exec:: CommandExecutor ;
34+ use super :: pty:: PtyConfig as PtyMasterConfig ;
3435use super :: session:: { ChannelState , PtyConfig , SessionId , SessionInfo , SessionManager } ;
3536use super :: sftp:: SftpHandler ;
37+ use super :: shell:: ShellSession ;
3638use crate :: shared:: rate_limit:: RateLimiter ;
3739
3840/// SSH handler for a single client connection.
@@ -716,22 +718,124 @@ impl russh::server::Handler for SshHandler {
716718
717719 /// Handle shell request.
718720 ///
719- /// Placeholder implementation - will be implemented in a future issue .
721+ /// Starts an interactive shell session for the authenticated user .
720722 fn shell_request (
721723 & mut self ,
722724 channel_id : ChannelId ,
723725 session : & mut Session ,
724726 ) -> impl std:: future:: Future < Output = Result < ( ) , Self :: Error > > + Send {
725- tracing:: debug!( "Shell request" ) ;
727+ tracing:: debug!( channel = ?channel_id , "Shell request" ) ;
726728
727- if let Some ( channel_state) = self . channels . get_mut ( & channel_id) {
728- channel_state. set_shell ( ) ;
729- }
729+ // Get authenticated user info
730+ let username = match self . session_info . as_ref ( ) . and_then ( |s| s. user . clone ( ) ) {
731+ Some ( user) => user,
732+ None => {
733+ tracing:: warn!(
734+ channel = ?channel_id,
735+ "Shell request without authenticated user"
736+ ) ;
737+ let _ = session. channel_failure ( channel_id) ;
738+ return async { Ok ( ( ) ) } . boxed ( ) ;
739+ }
740+ } ;
730741
731- // Placeholder - reject for now
732- // Will be implemented in #129
733- let _ = session. channel_failure ( channel_id) ;
734- async { Ok ( ( ) ) }
742+ // Get PTY configuration (if set during pty_request)
743+ let pty_config = self
744+ . channels
745+ . get ( & channel_id)
746+ . and_then ( |state| state. pty . as_ref ( ) )
747+ . map ( |pty| {
748+ PtyMasterConfig :: new (
749+ pty. term . clone ( ) ,
750+ pty. col_width ,
751+ pty. row_height ,
752+ pty. pix_width ,
753+ pty. pix_height ,
754+ )
755+ } )
756+ . unwrap_or_default ( ) ;
757+
758+ // Clone what we need for the async block
759+ let auth_provider = Arc :: clone ( & self . auth_provider ) ;
760+ let handle = session. handle ( ) ;
761+ let peer_addr = self . peer_addr ;
762+
763+ // Get mutable reference to channel state
764+ let channels = & mut self . channels ;
765+
766+ // Signal success before starting shell
767+ let _ = session. channel_success ( channel_id) ;
768+
769+ async move {
770+ // Get user info from auth provider
771+ let user_info = match auth_provider. get_user_info ( & username) . await {
772+ Ok ( Some ( info) ) => info,
773+ Ok ( None ) => {
774+ tracing:: error!(
775+ user = %username,
776+ "User not found after authentication for shell"
777+ ) ;
778+ let _ = handle. close ( channel_id) . await ;
779+ return Ok ( ( ) ) ;
780+ }
781+ Err ( e) => {
782+ tracing:: error!(
783+ user = %username,
784+ error = %e,
785+ "Failed to get user info for shell"
786+ ) ;
787+ let _ = handle. close ( channel_id) . await ;
788+ return Ok ( ( ) ) ;
789+ }
790+ } ;
791+
792+ tracing:: info!(
793+ user = %username,
794+ peer = ?peer_addr,
795+ term = %pty_config. term,
796+ size = %format!( "{}x{}" , pty_config. col_width, pty_config. row_height) ,
797+ "Starting shell session"
798+ ) ;
799+
800+ // Create shell session
801+ let mut shell_session = match ShellSession :: new ( channel_id, pty_config) {
802+ Ok ( session) => session,
803+ Err ( e) => {
804+ tracing:: error!(
805+ user = %username,
806+ error = %e,
807+ "Failed to create shell session"
808+ ) ;
809+ let _ = handle. close ( channel_id) . await ;
810+ return Ok ( ( ) ) ;
811+ }
812+ } ;
813+
814+ // Start shell session
815+ if let Err ( e) = shell_session. start ( & user_info, handle. clone ( ) ) . await {
816+ tracing:: error!(
817+ user = %username,
818+ error = %e,
819+ "Failed to start shell session"
820+ ) ;
821+ let _ = handle. close ( channel_id) . await ;
822+ return Ok ( ( ) ) ;
823+ }
824+
825+ // Store shell session in channel state
826+ if let Some ( channel_state) = channels. get_mut ( & channel_id) {
827+ channel_state. set_shell_session ( shell_session) ;
828+ }
829+
830+ tracing:: info!(
831+ user = %username,
832+ peer = ?peer_addr,
833+ "Shell session started"
834+ ) ;
835+
836+ Ok ( ( ) )
837+ }
838+ . boxed ( )
735839 }
736840
737841 /// Handle subsystem request.
@@ -849,19 +953,98 @@ impl russh::server::Handler for SshHandler {
849953 }
850954
851955 /// Handle incoming data from the client.
956+ ///
957+ /// Forwards data to the active shell session if one exists.
852958 fn data (
853959 & mut self ,
854- _channel_id : ChannelId ,
960+ channel_id : ChannelId ,
855961 data : & [ u8 ] ,
856962 _session : & mut Session ,
857963 ) -> impl std:: future:: Future < Output = Result < ( ) , Self :: Error > > + Send {
858964 tracing:: trace!(
965+ channel = ?channel_id,
859966 bytes = %data. len( ) ,
860967 "Received data"
861968 ) ;
862969
863- // Placeholder - data handling will be implemented with exec/shell/sftp
864- async { Ok ( ( ) ) }
970+ // Get the data sender if there's an active shell session
971+ let data_sender = self
972+ . channels
973+ . get ( & channel_id)
974+ . and_then ( |state| state. shell_session . as_ref ( ) )
975+ . and_then ( |shell| shell. data_sender ( ) ) ;
976+
977+ if let Some ( tx) = data_sender {
978+ let data = data. to_vec ( ) ;
979+ return async move {
980+ if let Err ( e) = tx. send ( data) . await {
981+ tracing:: debug!(
982+ channel = ?channel_id,
983+ error = %e,
984+ "Error forwarding data to shell"
985+ ) ;
986+ }
987+ Ok ( ( ) )
988+ }
989+ . boxed ( ) ;
990+ }
991+
992+ async { Ok ( ( ) ) } . boxed ( )
993+ }
994+
995+ /// Handle window size change request.
996+ ///
997+ /// Updates the PTY window size for active shell sessions.
998+ #[ allow( clippy:: too_many_arguments) ]
999+ fn window_change_request (
1000+ & mut self ,
1001+ channel_id : ChannelId ,
1002+ col_width : u32 ,
1003+ row_height : u32 ,
1004+ pix_width : u32 ,
1005+ pix_height : u32 ,
1006+ _session : & mut Session ,
1007+ ) -> impl std:: future:: Future < Output = Result < ( ) , Self :: Error > > + Send {
1008+ tracing:: debug!(
1009+ channel = ?channel_id,
1010+ cols = col_width,
1011+ rows = row_height,
1012+ "Window change request"
1013+ ) ;
1014+
1015+ // Update stored PTY config
1016+ if let Some ( state) = self . channels . get_mut ( & channel_id) {
1017+ if let Some ( ref mut pty) = state. pty {
1018+ pty. col_width = col_width;
1019+ pty. row_height = row_height;
1020+ pty. pix_width = pix_width;
1021+ pty. pix_height = pix_height;
1022+ }
1023+ }
1024+
1025+ // Get the PTY mutex if there's an active shell session
1026+ let pty_mutex = self
1027+ . channels
1028+ . get ( & channel_id)
1029+ . and_then ( |state| state. shell_session . as_ref ( ) )
1030+ . map ( |shell| Arc :: clone ( shell. pty ( ) ) ) ;
1031+
1032+ if let Some ( pty) = pty_mutex {
1033+ return async move {
1034+ let mut pty_guard = pty. lock ( ) . await ;
1035+ if let Err ( e) = pty_guard. resize ( col_width, row_height) {
1036+ tracing:: debug!(
1037+ channel = ?channel_id,
1038+ error = %e,
1039+ "Error resizing shell PTY"
1040+ ) ;
1041+ }
1042+ Ok ( ( ) )
1043+ }
1044+ . boxed ( ) ;
1045+ }
1046+
1047+ async { Ok ( ( ) ) } . boxed ( )
8651048 }
8661049
8671050 /// Handle channel EOF from the client.
0 commit comments