diff --git a/src/fuser.rs b/src/fuser.rs index ea86805..13fa8f5 100644 --- a/src/fuser.rs +++ b/src/fuser.rs @@ -11,33 +11,37 @@ use std::{ time::{Duration, SystemTime}, }; -use fuser::Filesystem; +use fuser::{FileType, Filesystem}; use libc::{ - EACCES, EINVAL, EISDIR, ENOENT, ENOSYS, EPERM, O_ACCMODE, O_APPEND, O_RDONLY, O_TRUNC, - O_WRONLY, R_OK, W_OK, X_OK, gid_t, uid_t, + EACCES, EINVAL, EISDIR, ENOENT, ENOSYS, ENOTDIR, EPERM, O_ACCMODE, O_APPEND, O_RDONLY, O_TRUNC, + O_WRONLY, R_OK, W_OK, X_OK, c_int, gid_t, uid_t, }; use parking_lot::{RwLock, RwLockWriteGuard}; use crate::config::FuseConfig; +type WriteCallback = Box; + +struct StaticState { + creation_time: SystemTime, + user: u32, + group: u32, + block_size: u32, +} + +struct VariableState { + contents: String, + access_time: SystemTime, + modification_time: SystemTime, +} + struct Handle { + inode: u64, + uid: u32, flags: i32, cursor: i64, } -struct VariableFileState { - contents: String, - atime: SystemTime, - mtime: SystemTime, -} - -struct StaticFileState { - crtime: SystemTime, - uid: u32, - gid: u32, - blksize: u32, -} - struct Handles { handles: HashMap, next_handle: u64, @@ -51,23 +55,140 @@ impl Handles { } } -type WriteCallback = Box; - pub struct AutheliaFS { config: FuseConfig, write_callback: Option, - variable_file_state: Arc>, - static_file_state: Arc, + static_state: Arc, + variable_state: Arc>, handles: Arc>, } const TTL: Duration = Duration::from_secs(1); -const ROOT_INODE: u64 = 1; -const PARENT_INODE: u64 = 2; -const FILE_INODE: u64 = 3; -const ROOT_MODE: u16 = 0o550; // dr-xr-x--- -const FILE_MODE: u16 = 0o640; // -rw-r----- +#[derive(Clone, Copy)] +struct FileInfo { + inode: u64, + parent_inode: u64, + file_type: FileType, + permissions: u16, +} + +#[derive(Clone, Copy)] +enum File { + Root(FileInfo), + Parent(FileInfo), + Users(FileInfo), +} + +impl Deref for File { + type Target = FileInfo; + + fn deref(&self) -> &Self::Target { + match self { + Self::Root(info) | Self::Parent(info) | Self::Users(info) => info, + } + } +} + +const ROOT_DIR_INODE: u64 = 1; +const ROOT_DIR: File = File::Root(FileInfo { + inode: ROOT_DIR_INODE, + parent_inode: ROOT_DIR_INODE, + file_type: FileType::Directory, + permissions: 0o550, +}); + +const PARENT_DIR_INODE: u64 = 2; +const PARENT_DIR: File = File::Parent(FileInfo { + inode: PARENT_DIR_INODE, + parent_inode: ROOT_DIR_INODE, + file_type: FileType::Directory, + permissions: 0o550, +}); + +const USERS_FILE_INODE: u64 = 3; +const USERS_FILE: File = File::Users(FileInfo { + inode: USERS_FILE_INODE, + parent_inode: ROOT_DIR_INODE, + file_type: FileType::RegularFile, + permissions: 0o640, +}); + +impl File { + const fn from_inode(inode: u64) -> Option { + match inode { + ROOT_DIR_INODE => Some(ROOT_DIR), + PARENT_DIR_INODE => Some(PARENT_DIR), + USERS_FILE_INODE => Some(USERS_FILE), + _ => None, + } + } + + fn to_file_attr(self, fs: &AutheliaFS) -> fuser::FileAttr { + match self { + Self::Root(FileInfo { + inode, + file_type, + permissions, + .. + }) + | Self::Parent(FileInfo { + inode, + file_type, + permissions, + .. + }) => fuser::FileAttr { + ino: inode, + size: 1, + blocks: 1, + atime: fs.static_state.creation_time, + mtime: fs.static_state.creation_time, + ctime: fs.static_state.creation_time, + crtime: fs.static_state.creation_time, + kind: file_type, + perm: permissions, + nlink: 2, + uid: fs.static_state.user, + gid: fs.static_state.group, + rdev: 0, + blksize: fs.static_state.block_size, + flags: 0, + }, + Self::Users(FileInfo { + inode, + file_type, + permissions, + .. + }) => { + let lock = fs.variable_state.read(); + let contents_len = lock.contents.len() as u64; + let access_time = lock.access_time; + let modification_time = lock.modification_time; + drop(lock); + + let blocks = contents_len.div_ceil(u64::from(fs.static_state.block_size)); + + fuser::FileAttr { + ino: inode, + size: contents_len, + blocks, + atime: access_time, + mtime: modification_time, + ctime: fs.static_state.creation_time, + crtime: fs.static_state.creation_time, + kind: file_type, + perm: permissions, + nlink: 1, + uid: fs.static_state.user, + gid: fs.static_state.group, + rdev: 0, + blksize: fs.static_state.block_size, + flags: 0, + } + } + } + } +} pub fn stat(path: &str) -> std::io::Result { let c_path = CString::new(path).unwrap(); @@ -89,27 +210,45 @@ pub fn getgid() -> gid_t { unsafe { libc::getgid() } } +#[derive(Clone, Copy)] +enum AccessCheckResult { + Ok(File), + Err(c_int), +} + +enum HandleCheckResult { + Ok, + Err(c_int), +} + impl AutheliaFS { pub fn new(config: FuseConfig, write_callback: Option) -> Self { let contents = String::new(); let time = SystemTime::now(); - let stat = stat(config.mount_directory.to_str().unwrap()).unwrap(); + let uid = getuid(); let gid = getgid(); - let variable_file_state = Arc::new(RwLock::new(VariableFileState { - contents, - atime: time, - mtime: time, - })); + let block_size = u32::try_from( + stat(config.mount_directory.to_str().unwrap()) + .unwrap() + .st_blksize, + ) + .unwrap_or(4096); - let static_file_state = Arc::new(StaticFileState { - crtime: time, - uid, - gid, - blksize: u32::try_from(stat.st_blksize).unwrap_or(4096), + let static_file_state = Arc::new(StaticState { + creation_time: time, + user: uid, + group: gid, + block_size, }); + let variable_file_state = Arc::new(RwLock::new(VariableState { + contents, + access_time: time, + modification_time: time, + })); + let handles = Arc::new(RwLock::new(Handles { handles: HashMap::new(), next_handle: 1, @@ -118,110 +257,184 @@ impl AutheliaFS { Self { config, write_callback, - variable_file_state, - static_file_state, + variable_state: variable_file_state, + static_state: static_file_state, handles, } } pub fn mount(self) -> std::io::Result<()> { - let options = vec![]; let mountpoint = self.config.mount_directory.clone(); - fuser::mount2(self, mountpoint, &options) + fuser::mount2(self, mountpoint, &vec![]) } - fn get_root_attr(&self) -> fuser::FileAttr { - fuser::FileAttr { - ino: ROOT_INODE, - size: self.static_file_state.blksize.into(), - blocks: 1, - atime: self.static_file_state.crtime, - mtime: self.static_file_state.crtime, - ctime: self.static_file_state.crtime, - crtime: self.static_file_state.crtime, - kind: fuser::FileType::Directory, - perm: ROOT_MODE, - nlink: 2, - uid: self.static_file_state.uid, - gid: self.static_file_state.gid, - rdev: 0, - blksize: self.static_file_state.blksize, - flags: 0, + #[allow(clippy::fn_params_excessive_bools)] + #[allow(clippy::too_many_arguments)] + fn check_access( + &mut self, + req: &fuser::Request<'_>, + inode: u64, + wants_read: bool, + wants_write: bool, + wants_exec: bool, + dir_only: bool, + file_only: bool, + ) -> AccessCheckResult { + let Some(file) = File::from_inode(inode) else { + return AccessCheckResult::Err(ENOENT); + }; + + let requester_user = req.uid(); + let requested_group = req.gid(); + + let owner_user = self.static_state.user; + let owner_group = self.static_state.group; + + if file.inode != file.parent_inode { + match self.check_access(req, file.parent_inode, false, false, true, false, false) { + AccessCheckResult::Err(err) => return AccessCheckResult::Err(err), + AccessCheckResult::Ok(_) => {} + } } + + let mode = file.permissions; + + let perm_bits: u8 = if requester_user == owner_user { + ((mode >> 6) & 0b111) as u8 + } else if requested_group == owner_group { + ((mode >> 3) & 0b111) as u8 + } else { + (mode & 0b111) as u8 + }; + + if wants_read && (perm_bits & 0b100) == 0 { + return AccessCheckResult::Err(EACCES); + } + + if wants_write && (perm_bits & 0b010) == 0 { + return AccessCheckResult::Err(EACCES); + } + + if wants_exec && (perm_bits & 0b001) == 0 { + return AccessCheckResult::Err(EACCES); + } + + if file_only { + match file.file_type { + FileType::RegularFile => {} + FileType::Directory => return AccessCheckResult::Err(EISDIR), + _ => unimplemented!(), + } + } + + if dir_only { + match file.file_type { + FileType::Directory => {} + FileType::RegularFile => return AccessCheckResult::Err(ENOTDIR), + _ => unimplemented!(), + } + } + + AccessCheckResult::Ok(file) } - fn get_file_attr(&self, lock: &T) -> fuser::FileAttr - where - T: Deref, - { - let contents = lock.contents.as_bytes(); + fn check_file_handle( + &self, + req: &fuser::Request<'_>, + file_handle: u64, + inode: u64, + wants_read: bool, + wants_write: bool, + ) -> HandleCheckResult { + let lock = self.handles.read(); - let contents_len = contents.len() as u64; - let blocks = contents_len.div_ceil(u64::from(self.static_file_state.blksize)); + let Some(handle) = lock.handles.get(&file_handle) else { + return HandleCheckResult::Err(ENOENT); + }; - fuser::FileAttr { - ino: FILE_INODE, - size: contents_len, - blocks, - atime: lock.atime, - mtime: lock.mtime, - ctime: self.static_file_state.crtime, - crtime: self.static_file_state.crtime, - kind: fuser::FileType::RegularFile, - perm: FILE_MODE, - nlink: 1, - uid: self.static_file_state.uid, - gid: self.static_file_state.gid, - rdev: 0, - blksize: self.static_file_state.blksize, - flags: 0, + if handle.inode != inode || handle.uid != req.uid() { + return HandleCheckResult::Err(ENOENT); } + + if handle.flags & O_ACCMODE == O_WRONLY && wants_read { + return HandleCheckResult::Err(EPERM); + } + + if handle.flags & O_ACCMODE == O_RDONLY && wants_write { + return HandleCheckResult::Err(EPERM); + } + + HandleCheckResult::Ok } } impl Filesystem for AutheliaFS { fn lookup( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, parent: u64, name: &std::ffi::OsStr, reply: fuser::ReplyEntry, ) { - if parent != ROOT_INODE || *name != *self.config.user_database_name { - reply.error(ENOENT); - return; - } + let inode = match name.to_str() { + Some(".") if parent == ROOT_DIR.parent_inode => ROOT_DIR_INODE, + Some("..") if parent == PARENT_DIR.parent_inode => PARENT_DIR_INODE, + Some(name) + if name == self.config.user_database_name && parent == USERS_FILE.parent_inode => + { + USERS_FILE_INODE + } + _ => { + reply.error(ENOENT); + return; + } + }; - let attr = self.get_file_attr(&self.variable_file_state.read()); + let file = match self.check_access(req, inode, true, false, false, false, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(file) => file, + }; + + let attr = file.to_file_attr(self); reply.entry(&TTL, &attr, 0); } fn getattr( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, - _fh: Option, + fh: Option, reply: fuser::ReplyAttr, ) { - if ino != ROOT_INODE && ino != FILE_INODE { - reply.error(ENOENT); - return; + if let Some(fh) = fh { + match self.check_file_handle(req, fh, ino, true, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} + } } - let attr = if ino == ROOT_INODE { - self.get_root_attr() - } else { - let lock = self.variable_file_state.read(); - self.get_file_attr(&lock) + let file = match self.check_access(req, ino, true, false, false, false, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(file) => file, }; + let attr = file.to_file_attr(self); reply.attr(&TTL, &attr); } #[allow(clippy::similar_names)] fn setattr( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, mode: Option, uid: Option, @@ -238,20 +451,25 @@ impl Filesystem for AutheliaFS { reply: fuser::ReplyAttr, ) { if let Some(fh) = fh { - let handles = self.handles.read(); - if !handles.handles.contains_key(&fh) { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, false, true) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } } - if ino == ROOT_INODE { - reply.error(EPERM); - return; - } + let file = match self.check_access(req, ino, false, true, false, false, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(file) => file, + }; - if ino != FILE_INODE { - reply.error(ENOENT); + if !matches!(file, File::Users(_)) { + reply.error(EPERM); return; } @@ -267,55 +485,66 @@ impl Filesystem for AutheliaFS { if let Some(size) = size { if size == 0 { - let mut variable_file_state = self.variable_file_state.write(); + let mut variable_file_state = self.variable_state.write(); variable_file_state.contents.clear(); } else { - reply.error(ENOENT); + reply.error(ENOSYS); return; } } if mtime.is_some() || atime.is_some() { - let mut variable_file_state = self.variable_file_state.write(); + let mut variable_file_state = self.variable_state.write(); - variable_file_state.mtime = match mtime { + variable_file_state.modification_time = match mtime { Some(fuser::TimeOrNow::Now) => SystemTime::now(), Some(fuser::TimeOrNow::SpecificTime(time)) => time, - None => variable_file_state.mtime, + None => variable_file_state.modification_time, }; - variable_file_state.atime = match atime { + variable_file_state.access_time = match atime { Some(fuser::TimeOrNow::Now) => SystemTime::now(), Some(fuser::TimeOrNow::SpecificTime(time)) => time, - None => variable_file_state.atime, + None => variable_file_state.access_time, }; } - let attr = self.get_file_attr(&self.variable_file_state.read()); - + let attr = file.to_file_attr(self); reply.attr(&TTL, &attr); } - fn open(&mut self, _req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) { - if ino == ROOT_INODE { - reply.error(EISDIR); - return; - } - - if ino != FILE_INODE { - reply.error(ENOENT); - return; + fn open(&mut self, req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) { + match self.check_access( + req, + ino, + flags & O_ACCMODE != O_WRONLY, + flags & O_ACCMODE != O_RDONLY, + false, + false, + true, + ) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } let mut handles = self.handles.write(); let handle = handles.next_handle(); - - handles.handles.insert(handle, Handle { flags, cursor: 0 }); - + handles.handles.insert( + handle, + Handle { + inode: ino, + uid: req.uid(), + flags, + cursor: 0, + }, + ); drop(handles); if flags & O_TRUNC != 0 && flags & O_ACCMODE != O_RDONLY { - let mut variable_file_state = self.variable_file_state.write(); + let mut variable_file_state = self.variable_state.write(); variable_file_state.contents.clear(); } @@ -324,7 +553,7 @@ impl Filesystem for AutheliaFS { fn read( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, @@ -333,37 +562,27 @@ impl Filesystem for AutheliaFS { _lock_owner: Option, reply: fuser::ReplyData, ) { - if ino == ROOT_INODE { - reply.error(EISDIR); - return; + match self.check_file_handle(req, fh, ino, true, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - if ino != FILE_INODE { - reply.error(ENOENT); - return; + match self.check_access(req, ino, true, false, false, false, true) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } - let handles = self.handles.read(); - if !handles.handles.contains_key(&fh) { - reply.error(ENOENT); - return; - } - let flags = handles.handles.get(&fh).unwrap().flags; - drop(handles); + let mut variable_file_state = self.variable_state.write(); + variable_file_state.access_time = SystemTime::now(); - if flags & O_ACCMODE == O_WRONLY { - reply.error(EPERM); - return; - } - - #[allow(clippy::significant_drop_tightening)] - let mut variable_file_state = self.variable_file_state.write(); - variable_file_state.atime = SystemTime::now(); - - #[allow(clippy::significant_drop_tightening)] let variable_file_state = RwLockWriteGuard::downgrade(variable_file_state); let contents = variable_file_state.contents.as_bytes(); - let contents_len = i64::try_from(contents.len()).unwrap(); if offset < 0 || offset >= contents_len { @@ -373,13 +592,12 @@ impl Filesystem for AutheliaFS { let end = (offset + i64::from(size)).min(contents_len); let data = &contents[usize::try_from(offset).unwrap()..usize::try_from(end).unwrap()]; - reply.data(data); } fn write( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, @@ -389,31 +607,26 @@ impl Filesystem for AutheliaFS { _lock_owner: Option, reply: fuser::ReplyWrite, ) { - if ino == ROOT_INODE { - reply.error(EISDIR); - return; + match self.check_file_handle(req, fh, ino, false, true) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - if ino != FILE_INODE { - reply.error(ENOENT); - return; + match self.check_access(req, ino, false, true, false, false, true) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } let mut handles = self.handles.write(); + let handle = handles.handles.get_mut(&fh).unwrap(); - let handle = handles.handles.get_mut(&fh); - if handle.is_none() { - reply.error(ENOENT); - return; - } - - let handle = handle.unwrap(); - if handle.flags & O_ACCMODE == O_RDONLY { - reply.error(EPERM); - return; - } - - let mut variable_file_state = self.variable_file_state.write(); + let mut variable_file_state = self.variable_state.write(); let old_end = variable_file_state.contents.len(); @@ -428,8 +641,8 @@ impl Filesystem for AutheliaFS { usize::try_from(offset).unwrap() }; - variable_file_state.atime = SystemTime::now(); - variable_file_state.mtime = SystemTime::now(); + variable_file_state.access_time = SystemTime::now(); + variable_file_state.modification_time = SystemTime::now(); let Ok(new_data) = std::str::from_utf8(data) else { reply.error(EINVAL); @@ -448,6 +661,7 @@ impl Filesystem for AutheliaFS { variable_file_state.contents = new_contents; handle.cursor = i64::try_from(offset + new_data.len()).unwrap(); + drop(handles); if let Some(callback) = &self.write_callback { @@ -461,20 +675,26 @@ impl Filesystem for AutheliaFS { fn flush( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, _lock_owner: u64, reply: fuser::ReplyEmpty, ) { - if ino != FILE_INODE { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, false, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - if !self.handles.read().handles.contains_key(&fh) { - reply.error(ENOENT); - return; + match self.check_access(req, ino, false, false, false, false, true) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } reply.ok(); @@ -482,7 +702,7 @@ impl Filesystem for AutheliaFS { fn release( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, _flags: i32, @@ -490,36 +710,48 @@ impl Filesystem for AutheliaFS { _flush: bool, reply: fuser::ReplyEmpty, ) { - if ino != FILE_INODE { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, false, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - let handle = self.handles.write().handles.remove(&fh); - if handle.is_none() { - reply.error(ENOENT); - return; + match self.check_access(req, ino, false, false, false, false, true) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } + self.handles.write().handles.remove(&fh); reply.ok(); } fn fsync( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, _datasync: bool, reply: fuser::ReplyEmpty, ) { - if !self.handles.read().handles.contains_key(&fh) { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, true, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - if ino != FILE_INODE { - reply.error(ENOENT); - return; + match self.check_access(req, ino, true, false, false, false, true) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } reply.ok(); @@ -527,32 +759,30 @@ impl Filesystem for AutheliaFS { fn opendir( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, _flags: i32, reply: fuser::ReplyOpen, ) { - if ino == FILE_INODE { - reply.error(EINVAL); - return; - } - - if ino != ROOT_INODE { - reply.error(ENOENT); - return; + match self.check_access(req, ino, true, false, false, true, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } let mut handles = self.handles.write(); - let handle = handles.next_handle(); handles.handles.insert( handle, Handle { + inode: ino, + uid: req.uid(), flags: O_RDONLY, cursor: 0, }, ); - drop(handles); reply.opened(handle, 0); @@ -560,25 +790,26 @@ impl Filesystem for AutheliaFS { fn readdir( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, mut reply: fuser::ReplyDirectory, ) { - if !self.handles.read().handles.contains_key(&fh) { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, true, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - if ino == FILE_INODE { - reply.error(EINVAL); - return; - } - - if ino != ROOT_INODE { - reply.error(ENOENT); - return; + match self.check_access(req, ino, true, false, false, true, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } if offset < 0 { @@ -587,19 +818,19 @@ impl Filesystem for AutheliaFS { } let entries = [ - (ROOT_INODE, 1, fuser::FileType::Directory, "."), - (PARENT_INODE, 2, fuser::FileType::Directory, ".."), - ( - FILE_INODE, - 3, - fuser::FileType::RegularFile, - &self.config.user_database_name, - ), + (ROOT_DIR, "."), + (PARENT_DIR, ".."), + (USERS_FILE, &self.config.user_database_name), ]; let start = usize::try_from(offset).unwrap(); - for entry in entries.iter().skip(start) { - if reply.add(entry.0, entry.1, entry.2, entry.3) { + for (index, (file, name)) in entries.iter().enumerate().skip(start) { + if reply.add( + file.inode, + i64::try_from(index + 1).unwrap(), + file.file_type, + name, + ) { // Buffer full break; } @@ -610,25 +841,26 @@ impl Filesystem for AutheliaFS { fn readdirplus( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, offset: i64, mut reply: fuser::ReplyDirectoryPlus, ) { - if !self.handles.read().handles.contains_key(&fh) { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, true, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - if ino == FILE_INODE { - reply.error(EINVAL); - return; - } - - if ino != ROOT_INODE { - reply.error(ENOENT); - return; + match self.check_access(req, ino, true, false, false, true, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } if offset < 0 { @@ -636,22 +868,22 @@ impl Filesystem for AutheliaFS { return; } - let entries = vec![ - (ROOT_INODE, 1, ".", TTL, self.get_root_attr(), 0), - (PARENT_INODE, 2, "..", TTL, self.get_root_attr(), 0), - ( - FILE_INODE, - 3, - &self.config.user_database_name, - TTL, - self.get_file_attr(&self.variable_file_state.read()), - 0, - ), + let entries = [ + (ROOT_DIR, "."), + (PARENT_DIR, ".."), + (USERS_FILE, &self.config.user_database_name), ]; let start = usize::try_from(offset).unwrap(); - for entry in entries.iter().skip(start) { - if reply.add(entry.0, entry.1, entry.2, &entry.3, &entry.4, entry.5) { + for (index, (file, name)) in entries.iter().enumerate().skip(start) { + if reply.add( + file.inode, + i64::try_from(index + 1).unwrap(), + name, + &TTL, + &file.to_file_attr(self), + 0, + ) { // Buffer full break; } @@ -660,58 +892,71 @@ impl Filesystem for AutheliaFS { fn releasedir( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, _flags: i32, reply: fuser::ReplyEmpty, ) { - if ino != ROOT_INODE { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, true, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - let handle = self.handles.write().handles.remove(&fh); - if handle.is_none() { - reply.error(ENOENT); - return; + match self.check_access(req, ino, true, false, false, true, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } + self.handles.write().handles.remove(&fh); reply.ok(); } fn fsyncdir( &mut self, - _req: &fuser::Request<'_>, + req: &fuser::Request<'_>, ino: u64, fh: u64, _datasync: bool, reply: fuser::ReplyEmpty, ) { - let handles = self.handles.read(); - if !handles.handles.contains_key(&fh) { - reply.error(ENOENT); - return; + match self.check_file_handle(req, fh, ino, true, false) { + HandleCheckResult::Err(err) => { + reply.error(err); + return; + } + HandleCheckResult::Ok => {} } - drop(handles); - if ino != ROOT_INODE { - reply.error(ENOENT); - return; + match self.check_access(req, ino, true, false, false, true, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } reply.ok(); } - fn statfs(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyStatfs) { - if ino != ROOT_INODE && ino != FILE_INODE { - reply.error(ENOENT); - return; + fn statfs(&mut self, req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyStatfs) { + match self.check_access(req, ino, true, false, false, false, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + return; + } + AccessCheckResult::Ok(_) => {} } - let variable_file_state = self.variable_file_state.read(); + let variable_file_state = self.variable_state.read(); let blocks = (variable_file_state.contents.len() as u64) - .div_ceil(u64::from(self.static_file_state.blksize)); + .div_ceil(u64::from(self.static_state.block_size)); drop(variable_file_state); reply.statfs( @@ -720,57 +965,24 @@ impl Filesystem for AutheliaFS { 0, 2, 0, - self.static_file_state.blksize, + self.static_state.block_size, u8::MAX.into(), u32::try_from(blocks).unwrap(), ); } fn access(&mut self, req: &fuser::Request<'_>, ino: u64, mask: i32, reply: fuser::ReplyEmpty) { - if ino != ROOT_INODE && ino != FILE_INODE { - reply.error(libc::ENOENT); - return; - } - - let uid = req.uid(); - let gid = req.gid(); - - #[allow(clippy::similar_names)] - let owner_uid = self.static_file_state.uid; - #[allow(clippy::similar_names)] - let owner_gid = self.static_file_state.gid; - - let mode = if ino == ROOT_INODE { - ROOT_MODE - } else { - FILE_MODE - }; - let wants_read = (mask & R_OK) != 0; let wants_write = (mask & W_OK) != 0; let wants_exec = (mask & X_OK) != 0; - let perm_bits: u8 = if uid == owner_uid { - ((mode >> 6) & 0b111) as u8 - } else if gid == owner_gid { - ((mode >> 3) & 0b111) as u8 - } else { - (mode & 0b111) as u8 - }; - - if wants_read && (perm_bits & 0b100) == 0 { - reply.error(EACCES); - return; + match self.check_access(req, ino, wants_read, wants_write, wants_exec, false, false) { + AccessCheckResult::Err(err) => { + reply.error(err); + } + AccessCheckResult::Ok(_) => { + reply.ok(); + } } - if wants_write && (perm_bits & 0b010) == 0 { - reply.error(EACCES); - return; - } - if wants_exec && (perm_bits & 0b001) == 0 { - reply.error(EACCES); - return; - } - - reply.ok(); } }