Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions crates/bssh-russh-sftp/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,24 @@ where
run_with_config(stream, handler, Config::default()).await
}

/// Run processing stream as SFTP with custom configuration
/// Run processing stream as SFTP with custom configuration.
///
/// This runs the SFTP request loop inline and returns when the client closes
/// the stream (EOF). Callers are responsible for spawning this onto a task if
/// they need it to run concurrently, and for closing the underlying SSH
/// channel once it returns.
pub async fn run_with_config<S, H>(mut stream: S, mut handler: H, cfg: Config)
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
H: Handler + Send + 'static,
{
tokio::spawn(async move {
loop {
match process_handler(&mut stream, &mut handler, &cfg).await {
Err(Error::UnexpectedEof) => break,
Err(err) => warn!("{}", err),
Ok(_) => (),
}
loop {
match process_handler(&mut stream, &mut handler, &cfg).await {
Err(Error::UnexpectedEof) => break,
Err(err) => warn!("{}", err),
Ok(_) => (),
}
}

debug!("sftp stream ended");
});
debug!("sftp stream ended");
}
33 changes: 24 additions & 9 deletions src/server/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,9 @@ impl russh::server::Handler for SshHandler {
// Clone what we need for the async block
let auth_provider = Arc::clone(&self.auth_provider);
let peer_addr = self.peer_addr;
let sftp_root = self.config.sftp_root.clone();
// Handle used to close the channel once the SFTP session ends.
let handle = session.handle();

// Signal success before spawning the SFTP handler
let _ = session.channel_success(channel_id);
Expand All @@ -1323,6 +1326,8 @@ impl russh::server::Handler for SshHandler {
user = %username,
"User not found after authentication for SFTP"
);
let _ = handle.eof(channel_id).await;
let _ = handle.close(channel_id).await;
return Ok(());
}
Err(e) => {
Expand All @@ -1331,6 +1336,8 @@ impl russh::server::Handler for SshHandler {
error = %e,
"Failed to get user info for SFTP"
);
let _ = handle.eof(channel_id).await;
let _ = handle.close(channel_id).await;
return Ok(());
}
};
Expand All @@ -1346,18 +1353,26 @@ impl russh::server::Handler for SshHandler {
// run without chroot, matching OpenSSH `sftp-server` defaults.
let sftp_handler = SftpHandler::new(
user_info.clone(),
self.config.sftp_root.clone(),
sftp_root,
user_info.home_dir,
);

// Run SFTP server on the channel stream
russh_sftp::server::run(channel.into_stream(), sftp_handler).await;

tracing::info!(
user = %username,
peer = ?peer_addr,
"SFTP session ended"
);
// Run the SFTP session on a detached task so this handler
// returns promptly (keeping the russh event loop pumping
// channel data). When the session ends, send EOF + CLOSE so
// clients that block on the server's channel-close handshake
// (e.g. sshj/JSch, as used by Cyberduck/PyCharm) don't hang
// waiting ~30s for a close that never arrives.
tokio::spawn(async move {
russh_sftp::server::run(channel.into_stream(), sftp_handler).await;
let _ = handle.eof(channel_id).await;
let _ = handle.close(channel_id).await;
tracing::info!(
user = %username,
peer = ?peer_addr,
"SFTP session ended"
);
});

Ok(())
}
Expand Down