diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 01:33:11 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 01:33:11 +0100 |
| commit | 91e167940ca299c228c9de973487b1978d2e550d (patch) | |
| tree | 56100e163c5b892cf0c56e37558905ebe1aaea3b | |
| parent | docker: pin and verify a week-old yt-dlp release (diff) | |
| download | senator-sperma-91e167940ca299c228c9de973487b1978d2e550d.tar.gz senator-sperma-91e167940ca299c228c9de973487b1978d2e550d.zip | |
config: harden the shared temp directory
| -rw-r--r-- | src/commands/utilities.rs | 14 | ||||
| -rw-r--r-- | src/config.rs | 58 | ||||
| -rw-r--r-- | src/main.rs | 1 |
3 files changed, 65 insertions, 8 deletions
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( | |||
| 100 | .unwrap_or_default(); | 100 | .unwrap_or_default(); |
| 101 | let dir = format!( | 101 | let dir = format!( |
| 102 | "{}/op_{}", | 102 | "{}/op_{}", |
| 103 | crate::config::TEMP_PATH, | 103 | crate::config::temp_path(), |
| 104 | uuid::Uuid::new_v4() | 104 | uuid::Uuid::new_v4() |
| 105 | ); | 105 | ); |
| 106 | tokio::fs::create_dir_all(&dir).await?; | 106 | tokio::fs::create_dir_all(&dir).await?; |
| @@ -506,7 +506,7 @@ async fn run_download( | |||
| 506 | } | 506 | } |
| 507 | let fmt = fformat.unwrap_or("mp4"); | 507 | let fmt = fformat.unwrap_or("mp4"); |
| 508 | let is_audio = matches!(fmt, "mp3" | "wav" | "m4a"); | 508 | let is_audio = matches!(fmt, "mp3" | "wav" | "m4a"); |
| 509 | let dl_dir = format!("{}/dl_{}", crate::config::TEMP_PATH, uuid::Uuid::new_v4()); | 509 | let dl_dir = format!("{}/dl_{}", crate::config::temp_path(), uuid::Uuid::new_v4()); |
| 510 | tokio::fs::create_dir_all(&dl_dir).await.ok(); | 510 | tokio::fs::create_dir_all(&dl_dir).await.ok(); |
| 511 | 511 | ||
| 512 | let out_template = match filename_user { | 512 | let out_template = match filename_user { |
| @@ -651,7 +651,7 @@ pub async fn compress( | |||
| 651 | #[description = "Force upload to Copyparty"] serwer: Option<bool>, | 651 | #[description = "Force upload to Copyparty"] serwer: Option<bool>, |
| 652 | ) -> Result<()> { | 652 | ) -> Result<()> { |
| 653 | ctx.defer().await?; | 653 | ctx.defer().await?; |
| 654 | tokio::fs::create_dir_all(crate::config::TEMP_PATH) | 654 | tokio::fs::create_dir_all(crate::config::temp_path()) |
| 655 | .await | 655 | .await |
| 656 | .ok(); | 656 | .ok(); |
| 657 | 657 | ||
| @@ -727,7 +727,7 @@ pub async fn convert( | |||
| 727 | #[description = "Force upload to Copyparty"] serwer: Option<bool>, | 727 | #[description = "Force upload to Copyparty"] serwer: Option<bool>, |
| 728 | ) -> Result<()> { | 728 | ) -> Result<()> { |
| 729 | ctx.defer().await?; | 729 | ctx.defer().await?; |
| 730 | tokio::fs::create_dir_all(crate::config::TEMP_PATH) | 730 | tokio::fs::create_dir_all(crate::config::temp_path()) |
| 731 | .await | 731 | .await |
| 732 | .ok(); | 732 | .ok(); |
| 733 | 733 | ||
| @@ -899,7 +899,7 @@ pub async fn download( | |||
| 899 | #[description = "Custom output filename"] filename_user: Option<String>, | 899 | #[description = "Custom output filename"] filename_user: Option<String>, |
| 900 | ) -> Result<()> { | 900 | ) -> Result<()> { |
| 901 | ctx.defer().await?; | 901 | ctx.defer().await?; |
| 902 | tokio::fs::create_dir_all(crate::config::TEMP_PATH) | 902 | tokio::fs::create_dir_all(crate::config::temp_path()) |
| 903 | .await | 903 | .await |
| 904 | .ok(); | 904 | .ok(); |
| 905 | run_download( | 905 | run_download( |
| @@ -974,7 +974,7 @@ pub async fn message_compression(ctx: Context<'_>, message: serenity::Message) - | |||
| 974 | return Ok(()); | 974 | return Ok(()); |
| 975 | } | 975 | } |
| 976 | ctx.defer().await?; | 976 | ctx.defer().await?; |
| 977 | tokio::fs::create_dir_all(crate::config::TEMP_PATH) | 977 | tokio::fs::create_dir_all(crate::config::temp_path()) |
| 978 | .await | 978 | .await |
| 979 | .ok(); | 979 | .ok(); |
| 980 | 980 | ||
| @@ -1030,7 +1030,7 @@ pub async fn message_download(ctx: Context<'_>, message: serenity::Message) -> R | |||
| 1030 | return Ok(()); | 1030 | return Ok(()); |
| 1031 | } | 1031 | } |
| 1032 | ctx.defer().await?; | 1032 | ctx.defer().await?; |
| 1033 | tokio::fs::create_dir_all(crate::config::TEMP_PATH) | 1033 | tokio::fs::create_dir_all(crate::config::temp_path()) |
| 1034 | .await | 1034 | .await |
| 1035 | .ok(); | 1035 | .ok(); |
| 1036 | 1036 | ||
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 @@ | |||
| 1 | use std::env; | 1 | use std::env; |
| 2 | use std::io; | ||
| 3 | use std::sync::OnceLock; | ||
| 2 | 4 | ||
| 3 | pub const TEMP_PATH: &str = "/tmp/senator_sperma"; | 5 | static TEMP_PATH: OnceLock<String> = OnceLock::new(); |
| 6 | |||
| 7 | /// Private per-process temp root. Initialized by [`init_temp_dir`] at startup. | ||
| 8 | /// Falls back to the historical path only for code paths that run before init | ||
| 9 | /// (tests). | ||
| 10 | pub fn temp_path() -> &'static str { | ||
| 11 | TEMP_PATH | ||
| 12 | .get() | ||
| 13 | .map(String::as_str) | ||
| 14 | .unwrap_or("/tmp/senator_sperma") | ||
| 15 | } | ||
| 16 | |||
| 17 | /// Create/validate the temp directory with private permissions. | ||
| 18 | /// | ||
| 19 | /// A fixed, predictable path in `/tmp` is a symlink target for any local user. | ||
| 20 | /// Refuse a symlink (or non-directory) and force mode `0700` on the real | ||
| 21 | /// directory before the bot writes anything into it. | ||
| 22 | pub fn init_temp_dir() -> io::Result<()> { | ||
| 23 | let path = env::temp_dir().join("senator_sperma"); | ||
| 24 | match std::fs::symlink_metadata(&path) { | ||
| 25 | Ok(meta) if meta.file_type().is_symlink() => { | ||
| 26 | return Err(io::Error::other(format!( | ||
| 27 | "{} is a symlink; refusing to use it", | ||
| 28 | path.display() | ||
| 29 | ))); | ||
| 30 | } | ||
| 31 | Ok(meta) if !meta.is_dir() => { | ||
| 32 | return Err(io::Error::other(format!( | ||
| 33 | "{} exists and is not a directory", | ||
| 34 | path.display() | ||
| 35 | ))); | ||
| 36 | } | ||
| 37 | Ok(_) => {} | ||
| 38 | Err(e) if e.kind() == io::ErrorKind::NotFound => { | ||
| 39 | let mut builder = std::fs::DirBuilder::new(); | ||
| 40 | #[cfg(unix)] | ||
| 41 | { | ||
| 42 | use std::os::unix::fs::DirBuilderExt; | ||
| 43 | builder.mode(0o700); | ||
| 44 | } | ||
| 45 | builder.create(&path)?; | ||
| 46 | } | ||
| 47 | Err(e) => return Err(e), | ||
| 48 | } | ||
| 49 | #[cfg(unix)] | ||
| 50 | { | ||
| 51 | use std::os::unix::fs::PermissionsExt; | ||
| 52 | std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700))?; | ||
| 53 | } | ||
| 54 | let encoded = path.to_string_lossy().into_owned(); | ||
| 55 | TEMP_PATH | ||
| 56 | .set(encoded) | ||
| 57 | .map_err(|_| io::Error::other("temp directory already initialized"))?; | ||
| 58 | Ok(()) | ||
| 59 | } | ||
| 4 | 60 | ||
| 5 | #[derive(Debug, Clone)] | 61 | #[derive(Debug, Clone)] |
| 6 | pub struct Config { | 62 | 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<dyn std::error::Error>> { | |||
| 146 | .init(); | 146 | .init(); |
| 147 | 147 | ||
| 148 | dotenvy::dotenv().ok(); | 148 | dotenvy::dotenv().ok(); |
| 149 | config::init_temp_dir()?; | ||
| 149 | config::check_env_vars(); | 150 | config::check_env_vars(); |
| 150 | let config = config::Config::from_env(); | 151 | let config = config::Config::from_env(); |
| 151 | 152 | ||
