From 611f29f32567171205bd2a9e398ddac107f04fb4 Mon Sep 17 00:00:00 2001 From: RafayAhmad7548 Date: Sat, 5 Jul 2025 20:09:01 +0500 Subject: [PATCH] new file and progress --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 88 ++++------------------------------------ src/sftp.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 81 deletions(-) create mode 100644 src/sftp.rs diff --git a/Cargo.lock b/Cargo.lock index 7a19278..c711862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,6 +534,7 @@ dependencies = [ name = "flux-sftp" version = "0.1.0" dependencies = [ + "chrono", "russh", "russh-sftp", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 138143d..ab36e39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +chrono = "0.4.41" russh = "0.52.1" russh-sftp = "2.1.1" tokio = { version = "1.45.1", features = ["full"] } diff --git a/src/main.rs b/src/main.rs index bf34fd7..68074ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ -use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration}; - -use russh::{keys::ssh_key::{rand_core::OsRng, PublicKey}, server::{Auth, Handler as SshHandler, Msg, Server, Session}, Channel, ChannelId, Error}; -use russh_sftp::{protocol::{File, FileAttributes, Handle, Name, Status, StatusCode}, server::Handler as SftpHandler}; +mod sftp; +use std::{net::SocketAddr, sync::Arc, time::Duration}; +use russh::{keys::ssh_key::{rand_core::OsRng, PublicKey}, server::{Auth, Handler as SshHandler, Msg, Server, Session}, Channel, ChannelId}; +use sftp::SftpSession; struct SftpServer; @@ -20,7 +20,7 @@ struct SshSession { } impl SshHandler for SshSession { - type Error = Error; + type Error = russh::Error; async fn auth_publickey_offered( &mut self, @@ -67,8 +67,8 @@ impl SshHandler for SshSession { ) -> Result<(), Self::Error> { if name == "sftp" { session.channel_success(channel_id)?; - let root_dir = format!("/srv/sftp/{}", self.user.take().unwrap()); - let sftp_handler = SftpSession { cwd: root_dir.clone(), root_dir, handle_map: HashMap::new() }; + let jail_dir = format!("/srv/sftp/{}", self.user.take().unwrap()); + let sftp_handler = SftpSession::new(jail_dir); russh_sftp::server::run(self.channel.take().ok_or(Self::Error::WrongChannel)?.into_stream(), sftp_handler).await; } else { @@ -78,80 +78,6 @@ impl SshHandler for SshSession { } } -struct SftpSession { - root_dir: String, - cwd: String, - handle_map: HashMap -} - -impl SftpHandler for SftpSession { - type Error = StatusCode; - - fn unimplemented(&self) -> Self::Error { - Self::Error::OpUnsupported - } - - async fn realpath( - &mut self, - id: u32, - path: String, - ) -> Result { - let paths = path.split('/'); - for path_part in paths { - match path_part { - ".." => { - if self.cwd != self.root_dir { - if let Some(pos) = self.cwd.rfind('/') { - self.cwd.truncate(pos); - } - } - }, - "." => {}, - _ => self.cwd.push_str(&format!("/{}", path_part)) - } - } - - Ok(Name { id, files: vec![File::dummy(&self.cwd)] }) - } - - async fn opendir( - &mut self, - id: u32, - path: String, - ) -> Result { - self.handle_map.insert(path.clone(), false); - Ok(Handle { id, handle: path }) - } - - async fn readdir( - &mut self, - id: u32, - handle: String, - ) -> Result { - if !self.handle_map.get(&handle).unwrap() { - *self.handle_map.get_mut(&handle).unwrap() = true; - return Ok(Name { id, files: vec![File::new("test", FileAttributes::default())] }) - } - Err(StatusCode::Eof) - } - - async fn close( - &mut self, - id: u32, - handle: String, - ) -> Result { - self.handle_map.remove(&handle); - Ok(Status { - id, - status_code: StatusCode::Ok, - error_message: "Ok".to_string(), - language_tag: "en-US".to_string(), - }) - } - -} - - #[tokio::main] async fn main() { diff --git a/src/sftp.rs b/src/sftp.rs new file mode 100644 index 0000000..10a6327 --- /dev/null +++ b/src/sftp.rs @@ -0,0 +1,114 @@ +use std::{collections::HashMap, os::unix::fs::MetadataExt}; + +use chrono::{Local, TimeZone}; +use russh_sftp::{protocol::{File, FileAttributes, Handle, Name, Status, StatusCode}, server::Handler as SftpHandler}; + +use tokio::fs::{self, ReadDir}; + +pub struct SftpSession { + jail_dir: String, + cwd: String, + handles: HashMap +} + +impl SftpSession { + pub fn new(jail_dir: String) -> Self { + SftpSession { jail_dir, cwd: String::from("/"), handles: HashMap::new() } + } +} + +impl SftpHandler for SftpSession { + type Error = StatusCode; + + fn unimplemented(&self) -> Self::Error { + StatusCode::OpUnsupported + } + + async fn realpath( + &mut self, + id: u32, + path: String, + ) -> Result { + let path_parts = path.split('/'); + for path_part in path_parts { + match path_part { + ".." => { + if self.cwd != "/" { + if let Some(pos) = self.cwd.rfind('/') { + self.cwd.truncate(pos); + } + } + }, + "." => {}, + _ => self.cwd.push_str(&format!("/{}", path_part)) + } + } + + Ok(Name { id, files: vec![File::dummy(&self.cwd)] }) + } + + async fn opendir( + &mut self, + id: u32, + path: String, + ) -> Result { + let path = format!("{}/{}", self.jail_dir, path); + match fs::read_dir(&path).await { + Ok(entries) => { + self.handles.insert(path.clone(), entries); + Ok(Handle { id, handle: path }) + } + Err(e) => { + println!("Error in reading dir: {}", e); + Err(StatusCode::NoSuchFile) + } + } + } + + async fn readdir( + &mut self, + id: u32, + handle: String, + ) -> Result { + match self.handles.get_mut(&handle).unwrap().next_entry().await { + Ok(Some(entry)) => { + let metadata = entry.metadata().await.unwrap(); + let dt = Local.timestamp_opt(metadata.mtime(), 0).unwrap(); + let longname = format!("{} {} {}", metadata.size(), dt.format("%b %e %Y"), entry.file_name().to_string_lossy()); + Ok(Name { id, files: vec![ + File { + filename: entry.file_name().to_string_lossy().into(), + longname: longname, + attrs: FileAttributes { + size: Some(metadata.size()), + atime: Some(metadata.atime() as u32), + mtime: Some(metadata.mtime() as u32), + ..Default::default() + } + } + ] }) + } + Ok(None) => Err(StatusCode::Eof), + Err(e) => { + println!("Error listing file: {}", e); + Err(StatusCode::Failure) + } + } + } + + async fn close( + &mut self, + id: u32, + handle: String, + ) -> Result { + self.handles.remove(&handle); + Ok(Status { + id, + status_code: StatusCode::Ok, + error_message: "Ok".to_string(), + language_tag: "en-US".to_string(), + }) + } + +} +