diff --git a/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs b/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs index f26cadb8..e005700d 100644 --- a/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs +++ b/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs @@ -31,6 +31,7 @@ mod auth { pub struct User { username: String, home: Option, + read_only: bool, } #[derive(Deserialize, Clone, Debug)] @@ -40,6 +41,8 @@ mod auth { username: String, password: Option, home: Option, + #[serde(default)] + read_only: bool, }, } @@ -47,13 +50,15 @@ mod auth { struct UserCreds { pub password: Option, pub home: Option, + pub read_only: bool, } impl User { - fn new(username: &str, home: &Option) -> Self { + fn new(username: &str, home: &Option, read_only: bool) -> Self { User { username: username.to_owned(), home: home.clone(), + read_only, } } } @@ -65,6 +70,10 @@ mod auth { Some(p) => Some(p.as_path()), } } + + fn read_only(&self) -> bool { + self.read_only + } } impl fmt::Display for User { @@ -99,7 +108,12 @@ mod auth { fn list_entry_to_map_entry(user_info: Credentials) -> Result<(String, UserCreds), Box> { let map_entry = match user_info { - Credentials::Plaintext { username, password, home } => (username.clone(), UserCreds { password, home }), + Credentials::Plaintext { + username, + password, + home, + read_only, + } => (username.clone(), UserCreds { password, home, read_only }), }; Ok(map_entry) } @@ -125,7 +139,7 @@ mod auth { let pass_check_result = match &creds.password { Some(ref given_password) => { if Self::check_password(given_password, &actual_creds.password).is_ok() { - Some(Ok(User::new(username, &actual_creds.home))) + Some(Ok(User::new(username, &actual_creds.home, actual_creds.read_only))) } else { Some(Err(AuthenticationError::BadPassword)) } @@ -137,7 +151,7 @@ mod auth { None => Err(AuthenticationError::BadPassword), Some(pass_res) => { if pass_res.is_ok() { - Ok(User::new(username, &actual_creds.home)) + Ok(User::new(username, &actual_creds.home, actual_creds.read_only)) } else { pass_res } diff --git a/crates/unftp-sbe-fs/src/lib.rs b/crates/unftp-sbe-fs/src/lib.rs index 041db946..16be23b9 100644 --- a/crates/unftp-sbe-fs/src/lib.rs +++ b/crates/unftp-sbe-fs/src/lib.rs @@ -153,12 +153,15 @@ impl StorageBackend for Filesystem { async fn put + Send, R: tokio::io::AsyncRead + Send + Sync + 'static + Unpin>( &self, - _user: &User, + user: &User, bytes: R, path: P, start_pos: u64, ) -> Result { // TODO: Add permission checks + if user.read_only() { + return Err(Error::new(ErrorKind::PermissionDenied, "Write access denied")); + } let path = strip_prefixes(path.as_ref()); let mut oo = cap_std::fs::OpenOptions::new(); @@ -176,7 +179,11 @@ impl StorageBackend for Filesystem { } #[tracing_attributes::instrument] - async fn del + Send + Debug>(&self, _user: &User, path: P) -> Result<()> { + async fn del + Send + Debug>(&self, user: &User, path: P) -> Result<()> { + if user.read_only() { + return Err(Error::new(ErrorKind::PermissionDenied, "Write access denied")); + } + let path = strip_prefixes(path.as_ref()); cap_fs::remove_file(self.root_fd.clone(), path) .await @@ -184,7 +191,11 @@ impl StorageBackend for Filesystem { } #[tracing_attributes::instrument] - async fn rmd + Send + Debug>(&self, _user: &User, path: P) -> Result<()> { + async fn rmd + Send + Debug>(&self, user: &User, path: P) -> Result<()> { + if user.read_only() { + return Err(Error::new(ErrorKind::PermissionDenied, "Write access denied")); + } + let path = strip_prefixes(path.as_ref()); cap_fs::remove_dir(self.root_fd.clone(), path) .await @@ -192,7 +203,11 @@ impl StorageBackend for Filesystem { } #[tracing_attributes::instrument] - async fn mkd + Send + Debug>(&self, _user: &User, path: P) -> Result<()> { + async fn mkd + Send + Debug>(&self, user: &User, path: P) -> Result<()> { + if user.read_only() { + return Err(Error::new(ErrorKind::PermissionDenied, "Write access denied")); + } + let path = strip_prefixes(path.as_ref()); cap_fs::create_dir(self.root_fd.clone(), path) .await @@ -200,7 +215,11 @@ impl StorageBackend for Filesystem { } #[tracing_attributes::instrument] - async fn rename + Send + Debug>(&self, _user: &User, from: P, to: P) -> Result<()> { + async fn rename + Send + Debug>(&self, user: &User, from: P, to: P) -> Result<()> { + if user.read_only() { + return Err(Error::new(ErrorKind::PermissionDenied, "Write access denied")); + } + let from = from.as_ref().strip_prefix("/").unwrap_or(from.as_ref()); let to = to.as_ref().strip_prefix("/").unwrap_or(to.as_ref()); diff --git a/src/auth/user.rs b/src/auth/user.rs index b6da5f91..1529ab32 100644 --- a/src/auth/user.rs +++ b/src/auth/user.rs @@ -27,6 +27,11 @@ pub trait UserDetail: Send + Sync + Display + Debug { fn home(&self) -> Option<&Path> { None } + + /// Should the user have read-only access, regardless of the Unix file permissions? + fn read_only(&self) -> bool { + false + } } /// DefaultUser is a default implementation of the `UserDetail` trait that doesn't hold any user