From 057cefdb5c35712bc954cdf55b183adf21e1db2a Mon Sep 17 00:00:00 2001 From: oupson Date: Wed, 5 Oct 2022 11:15:47 +0200 Subject: [PATCH] Implemented a few fuse functions --- Cargo.lock | 5 +- .../src/filename/filename_encoded.rs | 22 +- rustcryptfs-lib/src/lib.rs | 2 +- rustcryptfs-linux/Cargo.toml | 3 +- rustcryptfs-linux/src/encrypted_filesystem.rs | 256 +++++++++++++++++- 5 files changed, 277 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 787dc4c..d86c2fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,9 +373,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "log" @@ -556,6 +556,7 @@ name = "rustcryptfs-linux" version = "0.1.0" dependencies = [ "fuser", + "libc", "log", "rustcryptfs-lib", "serde_json", diff --git a/rustcryptfs-lib/src/filename/filename_encoded.rs b/rustcryptfs-lib/src/filename/filename_encoded.rs index 2edc65e..3d17c79 100644 --- a/rustcryptfs-lib/src/filename/filename_encoded.rs +++ b/rustcryptfs-lib/src/filename/filename_encoded.rs @@ -1,5 +1,7 @@ use std::path::Path; +use sha2::{Digest, Sha256}; + /// EncodedFilename #[derive(Debug, PartialEq)] pub enum EncodedFilename { @@ -14,13 +16,18 @@ impl EncodedFilename { { let path = file.as_ref(); - let filename = path.file_name().unwrap().to_str().expect("Failed to get filename"); + let filename = path + .file_name() + .unwrap() + .to_str() + .expect("Failed to get filename"); if filename.starts_with("gocryptfs.longname.") { if !filename.ends_with(".name") { let long = std::fs::read_to_string( path.parent().unwrap().join(format!("{}.name", filename)), - ).unwrap(); + ) + .unwrap(); Ok(EncodedFilename::LongFilename(LongFilename { filename: filename.to_string(), filename_content: long, @@ -43,7 +50,16 @@ pub struct LongFilename { impl From for EncodedFilename { fn from(filename: String) -> Self { if filename.len() > 255 { - unimplemented!() + let mut hasher = Sha256::new(); + hasher.update(filename.as_bytes()); + + Self::LongFilename(LongFilename { + filename: format!( + "gocryptfs.longname.{}.name", + base64::encode_config(hasher.finalize(), base64::URL_SAFE_NO_PAD) + ), + filename_content: filename, + }) } else { Self::ShortFilename(filename) } diff --git a/rustcryptfs-lib/src/lib.rs b/rustcryptfs-lib/src/lib.rs index 759ebc5..7aa6eca 100644 --- a/rustcryptfs-lib/src/lib.rs +++ b/rustcryptfs-lib/src/lib.rs @@ -45,7 +45,7 @@ impl GocryptFs { }) } - pub fn filename_decoder(&self) -> &FilenameDecoder { + pub fn filename_decoder<'s> (&'s self) -> &'s FilenameDecoder { &self.filename_decoder } diff --git a/rustcryptfs-linux/Cargo.toml b/rustcryptfs-linux/Cargo.toml index 4f9f09c..df25158 100644 --- a/rustcryptfs-linux/Cargo.toml +++ b/rustcryptfs-linux/Cargo.toml @@ -10,4 +10,5 @@ fuser = "0.11" log = "0.4" rustcryptfs-lib = { path = "../rustcryptfs-lib" } thiserror = "1.0" -serde_json = "1.0" \ No newline at end of file +serde_json = "1.0" +libc = "0.2" \ No newline at end of file diff --git a/rustcryptfs-linux/src/encrypted_filesystem.rs b/rustcryptfs-linux/src/encrypted_filesystem.rs index 2098494..c86ce9e 100644 --- a/rustcryptfs-linux/src/encrypted_filesystem.rs +++ b/rustcryptfs-linux/src/encrypted_filesystem.rs @@ -1,12 +1,26 @@ -use std::path::Path; +use std::{ + collections::BTreeMap, + ffi::OsStr, + fs::FileType as StdFileType, + os::unix::prelude::{FileTypeExt, MetadataExt, OsStrExt, PermissionsExt}, + path::{Path, PathBuf}, + sync::atomic::{AtomicU64, Ordering}, + time::{Duration, SystemTime}, +}; -use fuser::Filesystem; +use fuser::{FileAttr, FileType, Filesystem, FUSE_ROOT_ID}; use rustcryptfs_lib::GocryptFs; use crate::error::Result; +const BLOCK_SIZE: u64 = 4096; + +type InodeCache = BTreeMap; + pub struct EncryptedFs { fs: GocryptFs, + inode_cache: InodeCache, + file_handle: AtomicU64, } impl EncryptedFs { @@ -14,9 +28,21 @@ impl EncryptedFs { where P: AsRef, { + let path = path.as_ref(); + + log::info!("Opening dir ..."); let fs = GocryptFs::open(path, password)?; - Ok(Self { fs }) + log::info!("Done"); + + let mut inode_cache = BTreeMap::new(); + inode_cache.insert(FUSE_ROOT_ID, path.to_path_buf()); + + Ok(Self { + fs, + inode_cache, + file_handle: AtomicU64::new(0), + }) } pub fn mount

(self, mountpoint: P) @@ -25,6 +51,228 @@ impl EncryptedFs { { fuser::mount2(self, mountpoint, &[]).unwrap(); } + + fn get_path(&self, ino: u64) -> Option { + // TODO CHECK PERM + + // TODO AVOID CLONE + self.inode_cache.get(&ino).map(|p| p.clone()) + } + + fn get_real_size(size: u64) -> u64 { + let x = ((size as f64 - 48.0) / 4128.0).floor() as u64; + + x * 4096 + size - 48 - x * 4096 + } + + fn get_file_type(file_type: StdFileType) -> FileType { + if file_type.is_file() { + FileType::RegularFile + } else if file_type.is_dir() { + FileType::Directory + } else if file_type.is_symlink() { + FileType::Symlink + } else if file_type.is_socket() { + FileType::Socket + } else if file_type.is_char_device() { + FileType::CharDevice + } else if file_type.is_block_device() { + FileType::BlockDevice + } else if file_type.is_fifo() { + FileType::NamedPipe + } else { + unimplemented!() + } + } + + fn get_attr

(path: P, ino: u64) -> FileAttr + where + P: AsRef, + { + let meta = std::fs::metadata(&path).unwrap(); + + let file_type = Self::get_file_type(meta.file_type()); + + FileAttr { + ino, + size: EncryptedFs::get_real_size(meta.size()), + blocks: BLOCK_SIZE / meta.size(), + atime: meta.accessed().unwrap(), + mtime: meta.modified().unwrap(), + ctime: SystemTime::now(), //SystemTime::from(meta.ctime()), + crtime: SystemTime::now(), + kind: file_type, + perm: meta.permissions().mode() as u16, + nlink: meta.nlink() as u32, + uid: meta.uid(), + gid: meta.gid(), + rdev: meta.rdev() as u32, + blksize: meta.blksize() as u32, + flags: 0, + } + } } -impl Filesystem for EncryptedFs {} +trait InodeCacheExt { + fn get_or_insert_inode(&mut self, file_path: PathBuf) -> (u64, PathBuf); +} + +impl InodeCacheExt for InodeCache { + // TODO Try to avoid clone + fn get_or_insert_inode(&mut self, file_path: PathBuf) -> (u64, PathBuf) { + if let Some((ino, path)) = { + self.iter() + .find_map(|(i, p)| if p.eq(&file_path) { Some((i, p)) } else { None }) + } { + (*ino, path.clone()) + } else { + let ino = self.len() as u64 + 1; + self.insert(ino, file_path); + + (ino, self.get(&ino).unwrap().clone()) + } + } +} + +impl Filesystem for EncryptedFs { + fn access(&mut self, _req: &fuser::Request<'_>, ino: u64, mask: i32, reply: fuser::ReplyEmpty) { + log::debug!("access, ino : {}, mask : {}", ino, mask); + if let Some(path) = self.get_path(ino) { + reply.ok() + } else { + reply.error(libc::ENOENT) + } + } + + fn getattr(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) { + log::debug!("getattr, ino : {}", ino); + if let Some(path) = self.get_path(ino) { + log::debug!("access, path = {:?}", path); + reply.attr(&Duration::new(0, 0), &Self::get_attr(path, ino)) + } else { + reply.error(libc::ENOENT) + } + } + + fn lookup( + &mut self, + _req: &fuser::Request<'_>, + parent: u64, + name: &std::ffi::OsStr, + reply: fuser::ReplyEntry, + ) { + if let Some(parent) = &self.get_path(parent) { + let iv = std::fs::read(parent.join("gocryptfs.diriv")).unwrap(); + let dir_decoder = self.fs.filename_decoder().get_decoder_for_dir(&iv); + + let encrypted_name = dir_decoder + .encrypt_filename(&name.to_string_lossy()) + .unwrap(); + + let encrypted_name = match encrypted_name { + rustcryptfs_lib::filename::EncodedFilename::ShortFilename(s) => s, + rustcryptfs_lib::filename::EncodedFilename::LongFilename(l) => l.filename, + }; + + let file_path = parent.join(encrypted_name); + + if file_path.exists() { + let (ino, file_path) = self.inode_cache.get_or_insert_inode(file_path); + + reply.entry(&Duration::new(0, 0), &Self::get_attr(file_path, ino), 0) + } else { + reply.error(libc::ENOENT) + } + } else { + reply.error(libc::ENOENT) + } + } + + fn opendir( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + _flags: i32, + reply: fuser::ReplyOpen, + ) { + let fh = self.file_handle.fetch_add(1, Ordering::SeqCst); + reply.opened(fh, 0); + } + + fn readdir( + &mut self, + _req: &fuser::Request<'_>, + ino: u64, + _fh: u64, + offset: i64, + mut reply: fuser::ReplyDirectory, + ) { + if let Some(folder_path) = &self.get_path(ino) { + log::debug!("folder_path :{:?}", folder_path); + + let iv = std::fs::read(folder_path.join("gocryptfs.diriv")).unwrap(); + + let dir_decoder = self.fs.filename_decoder().get_decoder_for_dir(&iv); + + for (index, (meta, encrypted_name, name)) in std::fs::read_dir(folder_path) + .unwrap() + .flat_map(|e| e.ok()) + .flat_map(|dir| extract_name(dir, folder_path, &dir_decoder)) + .skip(offset as usize) + .enumerate() + { + let (inode, _) = self + .inode_cache + .get_or_insert_inode(folder_path.join(&encrypted_name)); + + let file_type = Self::get_file_type(meta.file_type()); + + let buffer_full: bool = reply.add( + inode, + offset + index as i64 + 1, + file_type, + OsStr::from_bytes(name.as_bytes()), + ); + + if buffer_full { + break; + } + } + + reply.ok() + } else { + reply.error(libc::ENOENT) + } + } +} + +fn extract_name( + dir: std::fs::DirEntry, + folder_path: &PathBuf, + dir_decoder: &rustcryptfs_lib::filename::DirFilenameDecoder, +) -> Option<(std::fs::Metadata, String, String)> { + let filename = dir.file_name(); + let filename = filename.to_str().unwrap(); + if filename != "gocryptfs.conf" && filename != "gocryptfs.diriv" { + if filename.starts_with("gocryptfs.longname.") { + if !filename.ends_with(".name") { + let filename = + std::fs::read_to_string(folder_path.join(format!("{}.name", filename))) + .unwrap(); + dir_decoder + .decode_filename(filename.as_str()) + .map(|n| (dir.metadata().unwrap(), filename, n)) + .ok() + } else { + None + } + } else { + dir_decoder + .decode_filename(filename) + .map(|n| (dir.metadata().unwrap(), filename.to_string(), n)) + .ok() + } + } else { + None + } +}