aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 01:33:11 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 01:33:11 +0100
commit91e167940ca299c228c9de973487b1978d2e550d (patch)
tree56100e163c5b892cf0c56e37558905ebe1aaea3b
parentdocker: pin and verify a week-old yt-dlp release (diff)
downloadsenator-sperma-91e167940ca299c228c9de973487b1978d2e550d.tar.gz
senator-sperma-91e167940ca299c228c9de973487b1978d2e550d.zip
config: harden the shared temp directory
-rw-r--r--src/commands/utilities.rs14
-rw-r--r--src/config.rs58
-rw-r--r--src/main.rs1
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 @@
1use std::env; 1use std::env;
2use std::io;
3use std::sync::OnceLock;
2 4
3pub const TEMP_PATH: &str = "/tmp/senator_sperma"; 5static 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).
10pub 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.
22pub 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)]
6pub struct Config { 62pub 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