diff --git a/Cargo.lock b/Cargo.lock index 7a2d82d8..551b8426 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1292,7 +1292,7 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ffplayout" -version = "0.22.3" +version = "0.23.0" dependencies = [ "chrono", "clap", @@ -1314,7 +1314,7 @@ dependencies = [ [[package]] name = "ffplayout-api" -version = "0.22.3" +version = "0.23.0" dependencies = [ "actix-files", "actix-multipart", @@ -1330,6 +1330,7 @@ dependencies = [ "faccess", "ffplayout-lib", "futures-util", + "home", "jsonwebtoken", "lazy_static", "lexical-sort", @@ -1357,7 +1358,7 @@ dependencies = [ [[package]] name = "ffplayout-lib" -version = "0.22.3" +version = "0.23.0" dependencies = [ "chrono", "crossbeam-channel", @@ -3587,7 +3588,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.22.3" +version = "0.23.0" dependencies = [ "chrono", "crossbeam-channel", diff --git a/Cargo.toml b/Cargo.toml index df1ca2fa..c619d299 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"] resolver = "2" [workspace.package] -version = "0.22.3" +version = "0.23.0" license = "GPL-3.0" repository = "https://github.com/ffplayout/ffplayout" authors = ["Jonathan Baecker "] diff --git a/ffplayout-api/Cargo.toml b/ffplayout-api/Cargo.toml index 96d1b1ab..ef2c9855 100644 --- a/ffplayout-api/Cargo.toml +++ b/ffplayout-api/Cargo.toml @@ -27,6 +27,7 @@ clap = { version = "4.3", features = ["derive"] } derive_more = "0.99" faccess = "0.2" futures-util = { version = "0.3", default-features = false, features = ["std"] } +home = "0.5" jsonwebtoken = "9" lazy_static = "1.4" lexical-sort = "0.3" diff --git a/ffplayout-api/src/api/routes.rs b/ffplayout-api/src/api/routes.rs index 514c5a62..3eab3073 100644 --- a/ffplayout-api/src/api/routes.rs +++ b/ffplayout-api/src/api/routes.rs @@ -855,7 +855,7 @@ pub async fn gen_playlist( let mut path_list = vec![]; for path in paths { - let (p, _, _) = norm_abs_path(&config.storage.path, path); + let (p, _, _) = norm_abs_path(&config.storage.path, path)?; path_list.push(p); } @@ -1024,7 +1024,7 @@ async fn get_file( let (config, _) = playout_config(&pool.into_inner(), &id).await?; let storage_path = config.storage.path; let file_path = req.match_info().query("filename"); - let (path, _, _) = norm_abs_path(&storage_path, file_path); + let (path, _, _) = norm_abs_path(&storage_path, file_path)?; let file = actix_files::NamedFile::open(path)?; Ok(file diff --git a/ffplayout-api/src/utils/errors.rs b/ffplayout-api/src/utils/errors.rs index 3ca1b79e..dc5d418d 100644 --- a/ffplayout-api/src/utils/errors.rs +++ b/ffplayout-api/src/utils/errors.rs @@ -12,6 +12,9 @@ pub enum ServiceError { #[display(fmt = "Conflict: {_0}")] Conflict(String), + #[display(fmt = "Forbidden: {_0}")] + Forbidden(String), + #[display(fmt = "Unauthorized: {_0}")] Unauthorized(String), @@ -31,6 +34,7 @@ impl ResponseError for ServiceError { } ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message), ServiceError::Conflict(ref message) => HttpResponse::Conflict().json(message), + ServiceError::Forbidden(ref message) => HttpResponse::Forbidden().json(message), ServiceError::Unauthorized(ref message) => HttpResponse::Unauthorized().json(message), ServiceError::NoContent(ref message) => HttpResponse::NoContent().json(message), ServiceError::ServiceUnavailable(ref message) => { diff --git a/ffplayout-api/src/utils/files.rs b/ffplayout-api/src/utils/files.rs index 14903cd9..827362b3 100644 --- a/ffplayout-api/src/utils/files.rs +++ b/ffplayout-api/src/utils/files.rs @@ -6,6 +6,7 @@ use std::{ use actix_multipart::Multipart; use actix_web::{web, HttpResponse}; use futures_util::TryStreamExt as _; +use lazy_static::lazy_static; use lexical_sort::{natural_lexical_cmp, PathSort}; use rand::{distributions::Alphanumeric, Rng}; use relative_path::RelativePath; @@ -54,11 +55,30 @@ pub struct VideoFile { duration: f64, } +lazy_static! { + pub static ref HOME_DIR: String = home::home_dir() + .unwrap_or("/home/h1wl3n2og".into()) // any random not existing folder + .as_os_str() + .to_string_lossy() + .to_string(); +} + +const FOLDER_WHITELIST: &[&str; 6] = &[ + "/media", + "/mnt", + "/playlists", + "/tv-media", + "/usr/share/ffplayout", + "/var/lib/ffplayout", +]; + /// Normalize absolut path /// /// This function takes care, that it is not possible to break out from root_path. -/// It also gives always a relative path back. -pub fn norm_abs_path(root_path: &Path, input_path: &str) -> (PathBuf, String, String) { +pub fn norm_abs_path( + root_path: &Path, + input_path: &str, +) -> Result<(PathBuf, String, String), ServiceError> { let path_relative = RelativePath::new(&root_path.to_string_lossy()) .normalize() .to_string() @@ -91,7 +111,15 @@ pub fn norm_abs_path(root_path: &Path, input_path: &str) -> (PathBuf, String, St let path = &root_path.join(&source_relative); - (path.to_path_buf(), path_suffix, source_relative) + if !FOLDER_WHITELIST.iter().any(|f| path.starts_with(f)) + && !path.starts_with(&HOME_DIR.to_string()) + { + return Err(ServiceError::Forbidden( + "Access forbidden: Folder cannot be opened.".to_string(), + )); + } + + Ok((path.to_path_buf(), path_suffix, source_relative)) } /// File Browser @@ -114,7 +142,7 @@ pub async fn browser( let mut extensions = config.storage.extensions; extensions.append(&mut channel_extensions); - let (path, parent, path_component) = norm_abs_path(&config.storage.path, &path_obj.source); + let (path, parent, path_component) = norm_abs_path(&config.storage.path, &path_obj.source)?; let parent_path = if !path_component.is_empty() { path.parent().unwrap() @@ -212,7 +240,7 @@ pub async fn create_directory( path_obj: &PathObject, ) -> Result { let (config, _) = playout_config(conn, &id).await?; - let (path, _, _) = norm_abs_path(&config.storage.path, &path_obj.source); + let (path, _, _) = norm_abs_path(&config.storage.path, &path_obj.source)?; if let Err(e) = fs::create_dir_all(&path).await { return Err(ServiceError::BadRequest(e.to_string())); @@ -283,8 +311,8 @@ pub async fn rename_file( move_object: &MoveObject, ) -> Result { let (config, _) = playout_config(conn, &id).await?; - let (source_path, _, _) = norm_abs_path(&config.storage.path, &move_object.source); - let (mut target_path, _, _) = norm_abs_path(&config.storage.path, &move_object.target); + let (source_path, _, _) = norm_abs_path(&config.storage.path, &move_object.source)?; + let (mut target_path, _, _) = norm_abs_path(&config.storage.path, &move_object.target)?; if !source_path.exists() { return Err(ServiceError::BadRequest("Source file not exist!".into())); @@ -318,7 +346,7 @@ pub async fn remove_file_or_folder( source_path: &str, ) -> Result<(), ServiceError> { let (config, _) = playout_config(conn, &id).await?; - let (source, _, _) = norm_abs_path(&config.storage.path, source_path); + let (source, _, _) = norm_abs_path(&config.storage.path, source_path)?; if !source.exists() { return Err(ServiceError::BadRequest("Source does not exists!".into())); @@ -351,7 +379,7 @@ pub async fn remove_file_or_folder( async fn valid_path(conn: &Pool, id: i32, path: &str) -> Result { let (config, _) = playout_config(conn, &id).await?; - let (test_path, _, _) = norm_abs_path(&config.storage.path, path); + let (test_path, _, _) = norm_abs_path(&config.storage.path, path)?; if !test_path.is_dir() { return Err(ServiceError::BadRequest("Target folder not exists!".into())); diff --git a/ffplayout-api/src/utils/playlist.rs b/ffplayout-api/src/utils/playlist.rs index bb5390e7..7feee196 100644 --- a/ffplayout-api/src/utils/playlist.rs +++ b/ffplayout-api/src/utils/playlist.rs @@ -14,7 +14,8 @@ pub async fn read_playlist( date: String, ) -> Result { let (config, _) = playout_config(conn, &id).await?; - let mut playlist_path = PathBuf::from(&config.playlist.path); + let (path, _, _) = norm_abs_path(&config.playlist.path, "")?; + let mut playlist_path = path; let d: Vec<&str> = date.split('-').collect(); playlist_path = playlist_path .join(d[0]) @@ -96,7 +97,7 @@ pub async fn generate_playlist( for path in &source.paths { let (safe_path, _, _) = - norm_abs_path(&config.storage.path, &path.to_string_lossy()); + norm_abs_path(&config.storage.path, &path.to_string_lossy())?; paths.push(safe_path); }