diff --git a/README.md b/README.md index a25697e..3bf8327 100644 --- a/README.md +++ b/README.md @@ -105,9 +105,12 @@ Jedes Laufwerkspaar bekommt sein **eigenes** Unterverzeichnis unter `settings.mo root, siehe oben) - `/run/media` ist auf den meisten Systemen bereits die übliche Konvention für eingebundene Wechseldatenträger/Netzlaufwerke (z. B. udisks2/GNOME) und liegt auf `tmpfs`, muss also nie persistieren. Der Mountpoint selbst (und alle nötigen Elternverzeichnisse) werden -bei jedem `mount`/`watch`-Lauf automatisch angelegt, falls sie fehlen. Ist für ein Paar -`owner_user` gesetzt, bekommt genau dieser Nutzer über die Mount-Optionen (`uid=`/`gid=`, siehe -"Architekturentscheidungen" unten) vollen Zugriff auf den Inhalt - die Trennung passiert also +bei jedem `mount`/`watch`-Lauf automatisch angelegt, falls sie fehlen - `mount_base_dir` selbst +fest mit `0755` (durchquerbar für jeden lokalen Nutzer, unabhängig vom Umask des root-Prozesses), +das jeweilige Backing-Verzeichnis (siehe unten) bei gesetztem `owner_user` zusätzlich fest +`chown`t und `0700` (nur der Owner). Ist für ein Paar `owner_user` gesetzt, bekommt genau dieser +Nutzer bei CIFS/WebDAV zusätzlich über die Mount-Optionen (`uid=`/`gid=`, siehe +"Architekturentscheidungen" unten) vollen Zugriff auf den *Inhalt* - die Trennung passiert also über Zugriffsrechte, nicht über getrennte Mountpoint-Namensräume pro Nutzer. Der Standard lässt sich in `config.toml` unter `[settings] mount_base_dir = "..."` jederzeit @@ -207,7 +210,9 @@ Eintragen ausgegeben: vollen Zugriff darauf, sofern nicht per `--owner-user` (nicht-interaktiv) oder im interaktiven Prompt bewusst ein anderer Nutzer angegeben bzw. abgelehnt wird. Bei NFS gibt es keine clientseitige `uid=`/`gid=`-Option, Zugriff bestimmt dort ausschließlich der Server über - die tatsächlichen Datei-Eigentümer/-Rechte des Exports. + die tatsächlichen Datei-Eigentümer/-Rechte des Exports - `owner_user` wirkt sich hier nur auf + das lokale Backing-Verzeichnis selbst aus (`chown`+`0700`, s. o.), nicht auf das, was der + Server tatsächlich exportiert. - **davfs2-Konfiguration**: SmartMount setzt in `davfs2.conf` automatisch `gui_optimize 1` (bündelt PROPFIND-Anfragen, wichtig für grafische Dateimanager) sowie `buf_size 16384` (deutlich über dem Standard von 16 KiB) - Letzteres behebt ein bekanntes Praxisproblem, bei diff --git a/src/mount/target.rs b/src/mount/target.rs index 03e0168..90d38d2 100644 --- a/src/mount/target.rs +++ b/src/mount/target.rs @@ -15,6 +15,7 @@ //! kurz während eines Umschaltens (siehe [`crate::reconcile`]) mehr als ein Backing-Verzeichnis //! gemountet - im Ruhezustand immer nur eines, wie ursprünglich vorgesehen. +use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use crate::config::{CloudSide, DrivePair, GlobalSettings, LocalSide, MountKind}; @@ -159,6 +160,21 @@ fn resolve_uid_gid(username: &str) -> Result<(u32, u32)> { Ok((run_id(username, "-u")?, run_id(username, "-g")?)) } +/// Macht `owner` (Nutzer + dessen primäre Gruppe) zum alleinigen Besitzer von `dir` (`0700`). +/// Für das Backing-Verzeichnis eines Paares mit gesetztem `owner_user` - unabhängig vom +/// Mount-Typ, also auch für NFS, wo es (anders als bei CIFS/WebDAV) keine `uid=`/`gid=`- +/// Mount-Option gibt, die die nach dem Mounten sichtbaren Rechte übersteuern könnte: das +/// Verzeichnis selbst gehört so wenigstens `owner` - reicht z. B. aus, um es zu betreten, bevor/ +/// falls etwas gemountet ist, ersetzt aber nicht die serverseitige NFS-Export-Rechtevergabe +/// (siehe README, Abschnitt "Voller Zugriff für einen bestimmten Nutzer"). +pub(crate) fn ensure_owner_only_dir(dir: &Path, owner: &str) -> Result<()> { + let (uid, gid) = resolve_uid_gid(owner)?; + std::os::unix::fs::chown(dir, Some(uid), Some(gid)).map_err(|e| Error::io(dir, e))?; + std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700)) + .map_err(|e| Error::io(dir, e))?; + Ok(()) +} + fn run_id(username: &str, flag: &str) -> Result { let output = std::process::Command::new("id") .arg(flag) @@ -263,6 +279,13 @@ pub fn activate_symlink(pair: &DrivePair, side: Side) -> Result<()> { if let Some(parent) = link_path.parent() { std::fs::create_dir_all(parent).map_err(|e| Error::io(parent, e))?; + // `mount_base_dir` ist von allen Paaren (mit ggf. unterschiedlichem `owner_user`) + // gemeinsam genutzt - hart auf `0755` setzen statt sich auf das Umask des root-Prozesses + // zu verlassen, damit jeder lokale Nutzer bis zum eigentlichen Mountpoint durchqueren + // kann (die eigentliche Zugriffsbeschränkung passiert weiter unten je Paar, siehe + // `ensure_owner_only_dir`/die `uid=`/`gid=`-Mount-Optionen). + std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o755)) + .map_err(|e| Error::io(parent, e))?; } let target = backing_dir(pair, side); @@ -466,4 +489,41 @@ mod tests { let err = activate_symlink(&pair, Side::Local).unwrap_err(); assert!(err.to_string().contains("real directory")); } + + #[test] + fn ensure_owner_only_dir_sets_mode_0700() { + let user = std::env::var("USER").expect("USER env var set in test environment"); + let dir = tempfile::tempdir().expect("tempdir"); + + ensure_owner_only_dir(dir.path(), &user).expect("ensure_owner_only_dir"); + + let mode = std::fs::metadata(dir.path()) + .expect("metadata") + .permissions() + .mode(); + assert_eq!(mode & 0o777, 0o700); + } + + #[test] + fn ensure_owner_only_dir_fails_clearly_for_unknown_user() { + let dir = tempfile::tempdir().expect("tempdir"); + let err = ensure_owner_only_dir(dir.path(), "no-such-user-xyz").unwrap_err(); + assert!(err.to_string().contains("not found")); + } + + #[test] + fn activate_symlink_sets_shared_parent_dir_to_0755() { + let dir = tempfile::tempdir().expect("tempdir"); + let parent = dir.path().join("mount-base"); + let mut pair = sample_pair(); + pair.mount_point = parent.join("pair-1"); + + activate_symlink(&pair, Side::Local).expect("activate local"); + + let mode = std::fs::metadata(&parent) + .expect("metadata") + .permissions() + .mode(); + assert_eq!(mode & 0o777, 0o755); + } } diff --git a/src/reconcile/mod.rs b/src/reconcile/mod.rs index af0c1c6..ed59c47 100644 --- a/src/reconcile/mod.rs +++ b/src/reconcile/mod.rs @@ -282,6 +282,12 @@ async fn mount_side( target::build_target_with_cached_local_ip(pair, settings, side, cached_local_ip)?; std::fs::create_dir_all(&mount_target.mount_point) .map_err(|e| crate::error::Error::io(&mount_target.mount_point, e))?; + // Backing-Verzeichnis auf `owner_user` chownen (0700) - relevant vor allem für NFS, wo es + // (anders als CIFS/WebDAV) keine `uid=`/`gid=`-Mount-Option gibt, die den Zugriff nach dem + // Mounten sonst regeln würde (siehe `target::ensure_owner_only_dir`). + if let Some(owner) = &pair.owner_user { + target::ensure_owner_only_dir(&mount_target.mount_point, owner)?; + } let backend = mount::backend_for(target::side_kind(pair, side)); backend.check_available()?;