From 91e167940ca299c228c9de973487b1978d2e550d Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 01:33:11 +0100 Subject: config: harden the shared temp directory --- src/commands/utilities.rs | 14 ++++++------ src/config.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 1 + 3 files changed, 65 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/commands/utilities.rs b/src/commands/utilities.rs index 88829b2..f43b30e 100644 --- a/src/commands/utilities.rs +++ b/src/commands/utilities.rs @@ -100,7 +100,7 @@ async fn temp_op( .unwrap_or_default(); let dir = format!( "{}/op_{}", - crate::config::TEMP_PATH, + crate::config::temp_path(), uuid::Uuid::new_v4() ); tokio::fs::create_dir_all(&dir).await?; @@ -506,7 +506,7 @@ async fn run_download( } let fmt = fformat.unwrap_or("mp4"); let is_audio = matches!(fmt, "mp3" | "wav" | "m4a"); - let dl_dir = format!("{}/dl_{}", crate::config::TEMP_PATH, uuid::Uuid::new_v4()); + let dl_dir = format!("{}/dl_{}", crate::config::temp_path(), uuid::Uuid::new_v4()); tokio::fs::create_dir_all(&dl_dir).await.ok(); let out_template = match filename_user { @@ -651,7 +651,7 @@ pub async fn compress( #[description = "Force upload to Copyparty"] serwer: Option, ) -> Result<()> { ctx.defer().await?; - tokio::fs::create_dir_all(crate::config::TEMP_PATH) + tokio::fs::create_dir_all(crate::config::temp_path()) .await .ok(); @@ -727,7 +727,7 @@ pub async fn convert( #[description = "Force upload to Copyparty"] serwer: Option, ) -> Result<()> { ctx.defer().await?; - tokio::fs::create_dir_all(crate::config::TEMP_PATH) + tokio::fs::create_dir_all(crate::config::temp_path()) .await .ok(); @@ -899,7 +899,7 @@ pub async fn download( #[description = "Custom output filename"] filename_user: Option, ) -> Result<()> { ctx.defer().await?; - tokio::fs::create_dir_all(crate::config::TEMP_PATH) + tokio::fs::create_dir_all(crate::config::temp_path()) .await .ok(); run_download( @@ -974,7 +974,7 @@ pub async fn message_compression(ctx: Context<'_>, message: serenity::Message) - return Ok(()); } ctx.defer().await?; - tokio::fs::create_dir_all(crate::config::TEMP_PATH) + tokio::fs::create_dir_all(crate::config::temp_path()) .await .ok(); @@ -1030,7 +1030,7 @@ pub async fn message_download(ctx: Context<'_>, message: serenity::Message) -> R return Ok(()); } ctx.defer().await?; - tokio::fs::create_dir_all(crate::config::TEMP_PATH) + tokio::fs::create_dir_all(crate::config::temp_path()) .await .ok(); diff --git a/src/config.rs b/src/config.rs index 720c215..c05c3ae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,62 @@ use std::env; +use std::io; +use std::sync::OnceLock; -pub const TEMP_PATH: &str = "/tmp/senator_sperma"; +static TEMP_PATH: OnceLock = OnceLock::new(); + +/// Private per-process temp root. Initialized by [`init_temp_dir`] at startup. +/// Falls back to the historical path only for code paths that run before init +/// (tests). +pub fn temp_path() -> &'static str { + TEMP_PATH + .get() + .map(String::as_str) + .unwrap_or("/tmp/senator_sperma") +} + +/// Create/validate the temp directory with private permissions. +/// +/// A fixed, predictable path in `/tmp` is a symlink target for any local user. +/// Refuse a symlink (or non-directory) and force mode `0700` on the real +/// directory before the bot writes anything into it. +pub fn init_temp_dir() -> io::Result<()> { + let path = env::temp_dir().join("senator_sperma"); + match std::fs::symlink_metadata(&path) { + Ok(meta) if meta.file_type().is_symlink() => { + return Err(io::Error::other(format!( + "{} is a symlink; refusing to use it", + path.display() + ))); + } + Ok(meta) if !meta.is_dir() => { + return Err(io::Error::other(format!( + "{} exists and is not a directory", + path.display() + ))); + } + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::NotFound => { + let mut builder = std::fs::DirBuilder::new(); + #[cfg(unix)] + { + use std::os::unix::fs::DirBuilderExt; + builder.mode(0o700); + } + builder.create(&path)?; + } + Err(e) => return Err(e), + } + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700))?; + } + let encoded = path.to_string_lossy().into_owned(); + TEMP_PATH + .set(encoded) + .map_err(|_| io::Error::other("temp directory already initialized"))?; + Ok(()) +} #[derive(Debug, Clone)] pub struct Config { diff --git a/src/main.rs b/src/main.rs index 3483c9a..0dee2a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -146,6 +146,7 @@ async fn main() -> Result<(), Box> { .init(); dotenvy::dotenv().ok(); + config::init_temp_dir()?; config::check_env_vars(); let config = config::Config::from_env(); -- cgit v1.2.3