From 64b50513418d3d17875eddb0c1191993006b2404 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Fri, 23 Jun 2023 18:47:44 -0400 Subject: [PATCH] Fix permissions setting for new account folder creation in the `Filesystem` storage backend --- src/mailstore/filesystem.rs | 57 +++++++++++++++++++++++-------- src/mailstore/filesystem/error.rs | 20 ++++++++++- src/prelude.rs | 2 +- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/src/mailstore/filesystem.rs b/src/mailstore/filesystem.rs index 7545e4a..3e65ab4 100644 --- a/src/mailstore/filesystem.rs +++ b/src/mailstore/filesystem.rs @@ -2,7 +2,7 @@ use { super::*, crate::{ message::Parser as MessageParser, - prelude::{ClientCertificateStore, Certificate, ParseMailboxError}, + prelude::{Certificate, ClientCertificateStore}, }, rustls_pemfile::{read_one, Item}, std::{ @@ -10,11 +10,13 @@ use { fs::{self, File}, io::{self, BufReader, BufWriter, Write}, iter, - os::{fd::AsRawFd, unix::{fs::DirBuilderExt, prelude::OpenOptionsExt}}, + os::unix::fs::DirBuilderExt, path::{Path, PathBuf}, }, }; mod error; +use std::ffi::CString; + pub use error::Error; pub trait MultiDomain: MailStore { @@ -262,26 +264,27 @@ impl MailStore for Filesystem { let mut path = self.path.clone(); path.push(&mb.host.to_string()); path.push(&mb.username); - fs::create_dir_all(&path)?; + fs::DirBuilder::new() + .recursive(true) + .create(&path)?; + let p = CString::new(path.to_str().ok_or(Error::Utf8)?.to_string())?; + if let Some(pw) = pw::Passwd::getpw()? { + let groups = pw.groups()?; + if let Some(gr) = groups.iter().find(|g| g.name == mb.username) { + chown(p.clone(), pw.uid, gr.gid)?; + } + } + // We have to explicitly call `chown` after creating the directory, + // rather than setting permissions during creation, as the umask + // might squash some of the bits we're specifically trying to set. + chmod(p, 0o2770)?; if let Some(ref blurb) = mb.blurb { path.push("blurb"); let fd = File::options() .create(true) .write(true) .truncate(true) - .mode(0o2770) .open(&path)?; - if let Some(pw) = pw::Passwd::getpw()? - { - let groups = pw.groups()?; - if let Some(gr) = groups.iter().find(|g| g.name == mb.username) { - unsafe { - if libc::fchown(fd.as_raw_fd(), pw.uid, gr.gid) != 0 { - return Err(io::Error::last_os_error().into()); - } - } - } - } let mut writer = BufWriter::new(fd); writer.write_all(blurb.as_bytes())?; } @@ -421,6 +424,8 @@ impl ClientCertificateStore for Filesystem { #[cfg(test)] mod tests { + use std::os::unix::prelude::PermissionsExt; + use super::*; fn store() -> Filesystem { @@ -514,7 +519,29 @@ mod tests { .add_user("rob@misfin.example.org Rob Zombie") .unwrap(); assert!(store().has_mailuser("rob@misfin.example.org")); + let permissions = fs::metadata("test/mailstore/misfin.example.org/rob") + .unwrap() + .permissions(); + assert_eq!(permissions.mode(), 0o42770); assert!(store().remove_user("rob@misfin.example.org")); assert!(!store().has_mailuser("rob@misfin.example.org")); } } + +fn chown(path: CString, uid: u32, gid: u32) -> Result<(), io::Error> { + unsafe { + if libc::chown(path.as_ptr(), uid, gid) != 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) +} + +fn chmod(path: CString, mode: u32) -> Result<(), io::Error> { + unsafe { + if libc::chmod(path.as_ptr(), mode) != 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) +} diff --git a/src/mailstore/filesystem/error.rs b/src/mailstore/filesystem/error.rs index 7d073ab..5560645 100644 --- a/src/mailstore/filesystem/error.rs +++ b/src/mailstore/filesystem/error.rs @@ -1,21 +1,25 @@ use { crate::prelude::ParseMailboxError, - std::{fmt, io}, + std::{ffi::NulError, fmt, io, str::Utf8Error}, }; #[derive(Debug)] pub enum Error { Io(io::Error), MailBox(ParseMailboxError), + FFi(NulError), Permissions(pw::Error), + Utf8, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Io(e) => write!(f, "Filesystem error: {e}"), + Self::FFi(e) => write!(f, "Filesystem error: {e}"), Self::MailBox(e) => write!(f, "Filesystem error: {e}"), Self::Permissions(e) => write!(f, "Filesystem error: {e}"), + Self::Utf8 => write!(f, "Filesystem error: Utf8 failure"), } } } @@ -24,8 +28,10 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Io(e) => Some(e), + Self::FFi(e) => Some(e), Self::MailBox(e) => Some(e), Self::Permissions(e) => Some(e), + _ => None, } } } @@ -36,6 +42,12 @@ impl From for Error { } } +impl From for Error { + fn from(value: NulError) -> Self { + Self::FFi(value) + } +} + impl From for Error { fn from(value: ParseMailboxError) -> Self { Self::MailBox(value) @@ -47,3 +59,9 @@ impl From for Error { Self::Permissions(value) } } + +impl From for Error { + fn from(_value: Utf8Error) -> Self { + Self::Utf8 + } +} diff --git a/src/prelude.rs b/src/prelude.rs index 9031a38..48ff12d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -4,8 +4,8 @@ pub use super::{ gemtext::{GemtextNode, Parser as GemtextParser}, host::{Error as ParseHostError, Host}, mailbox::{Error as ParseMailboxError, Mailbox}, + mailstore::{Account, Domain, Filesystem, FilesystemError, Folder, MailStore}, mailuser::Mailuser, - mailstore::{Account, Domain, Folder, Filesystem, FilesystemError, MailStore}, message::{Error as ParseMessageError, Link, Message, Recipients}, //receiver, request::{Error as ParseRequestError, Request},