diff --git a/src/api/icons.rs b/src/api/icons.rs index 8674793..c22f9ff 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -1,12 +1,13 @@ use std::fs::{create_dir_all, remove_file, symlink_metadata, File}; use std::io::prelude::*; +use std::net::ToSocketAddrs; use std::time::{Duration, SystemTime}; use rocket::http::ContentType; use rocket::response::Content; use rocket::Route; -use reqwest::{header::HeaderMap, Client, Response}; +use reqwest::{header::HeaderMap, Client, Response, Url}; use rocket::http::Cookie; @@ -60,15 +61,9 @@ fn icon(domain: String) -> Content> { return Content(icon_type, FALLBACK_ICON.to_vec()); } - if let Some(blacklist) = CONFIG.icon_blacklist_regex() { - info!("Icon blacklist enabled: {:#?}", blacklist); - - let regex = Regex::new(&blacklist).expect("Valid Regex"); - - if regex.is_match(&domain) { - warn!("Blacklisted domain: {:#?}", domain); - return Content(icon_type, FALLBACK_ICON.to_vec()); - } + if check_icon_domain_is_blacklisted(&domain) { + warn!("Domain is blacklisted: {:#?}", domain); + return Content(icon_type, FALLBACK_ICON.to_vec()); } let icon = get_icon(&domain); @@ -76,6 +71,37 @@ fn icon(domain: String) -> Content> { Content(icon_type, icon) } +fn check_icon_domain_is_blacklisted(domain: &str) -> bool { + let mut is_blacklisted = false; + if CONFIG.icon_blacklist_non_global_ips() { + is_blacklisted = (domain, 0) + .to_socket_addrs() + .map(|x| { + for ip_port in x { + if !ip_port.ip().is_global() { + warn!("IP {} for domain '{}' is not a global IP!", ip_port.ip(), domain); + return true; + } + } + false + }) + .unwrap_or(false); + } + + // Skip the regex check if the previous one is true already + if !is_blacklisted { + if let Some(blacklist) = CONFIG.icon_blacklist_regex() { + let regex = Regex::new(&blacklist).expect("Valid Regex"); + if regex.is_match(&domain) { + warn!("Blacklisted domain: {:#?} matched {:#?}", domain, blacklist); + is_blacklisted = true; + } + } + } + + is_blacklisted +} + fn get_icon(domain: &str) -> Vec { let path = format!("{}/{}.png", CONFIG.icon_cache_folder(), domain); @@ -202,6 +228,7 @@ fn get_icon_url(domain: &str) -> Result<(Vec, String), Error> { if let Ok(content) = resp { // Extract the URL from the respose in case redirects occured (like @ gitlab.com) let url = content.url().clone(); + let raw_cookies = content.headers().get_all("set-cookie"); cookie_str = raw_cookies .iter() @@ -253,6 +280,10 @@ fn get_page(url: &str) -> Result { } fn get_page_with_cookies(url: &str, cookie_str: &str) -> Result { + if check_icon_domain_is_blacklisted(Url::parse(url).unwrap().host_str().unwrap_or_default()) { + err!("Favicon rel linked to a non blacklisted domain!"); + } + if cookie_str.is_empty() { CLIENT .get(url) diff --git a/src/config.rs b/src/config.rs index 3b8d521..4e80725 100644 --- a/src/config.rs +++ b/src/config.rs @@ -267,6 +267,9 @@ make_config! { /// Icon blacklist Regex |> Any domains or IPs that match this regex won't be fetched by the icon service. /// Useful to hide other servers in the local network. Check the WIKI for more details icon_blacklist_regex: String, true, option; + /// Icon blacklist non global IPs |> Any IP which is not defined as a global IP will be blacklisted. + /// Usefull to secure your internal environment: See https://en.wikipedia.org/wiki/Reserved_IP_addresses for a list of IPs which it will block + icon_blacklist_non_global_ips: bool, true, def, true; /// Disable Two-Factor remember |> Enabling this would force the users to use a second factor to login every time. /// Note that the checkbox would still be present, but ignored. diff --git a/src/main.rs b/src/main.rs index 384d6c1..c6134da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro_hygiene, decl_macro, vec_remove_item, try_trait)] +#![feature(proc_macro_hygiene, decl_macro, vec_remove_item, try_trait, ip)] #![recursion_limit = "256"] #[cfg(feature = "openssl")]