From 7dd9bda17b92bbb09660c36c3c728fe2cf023945 Mon Sep 17 00:00:00 2001 From: Steven Vergenz <1882376+stevenvergenz@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:21:16 -0700 Subject: [PATCH] Do spamhaus check --- src/api/client_server/media.rs | 29 +++++++++++++++++++++++++---- src/config/mod.rs | 1 + 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/api/client_server/media.rs b/src/api/client_server/media.rs index c6d3cb8a..8c80bbae 100644 --- a/src/api/client_server/media.rs +++ b/src/api/client_server/media.rs @@ -7,6 +7,7 @@ use crate::{ service::media::{FileMeta, UrlPreviewData}, config::UrlPreviewPermission, services, utils, Error, Result, Ruma}; +use hickory_resolver::error::ResolveErrorKind; use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE}; use ruma::{ api::{ @@ -126,7 +127,7 @@ async fn download_html( Ok(data) } -fn url_request_allowed(addr: &IpAddr) -> bool { +fn is_ip_external(addr: &IpAddr) -> bool { // could be implemented with reqwest when it supports IP filtering: // https://github.com/seanmonstar/reqwest/issues/1515 @@ -179,18 +180,38 @@ fn url_request_allowed(addr: &IpAddr) -> bool { /// Generate URL preview data from the given URL async fn request_url_preview(url: &Url) -> Result { - // resolve host to IP to ensure it's not a local IP (host guaranteed to not be None) + // host guaranteed to not be None by get_media_preview_route + let host = url.host_str().unwrap(); + + // resolve host to IP to ensure it's not an internal IP let dns_resolver = services().globals.dns_resolver(); - match dns_resolver.lookup_ip(url.host_str().unwrap()).await { + match dns_resolver.lookup_ip(host).await { Err(_) => { return Err(Error::BadServerResponse("Failed to resolve media preview host")); }, - Ok(lookup) if lookup.iter().any(|ip| !url_request_allowed(&ip)) => { + Ok(lookup) if lookup.iter().any(|ip| !is_ip_external(&ip)) => { return Err(Error::BadRequest(ErrorKind::Unknown, "Requesting from this address forbidden")); }, Ok(_) => { }, } + // Spamhaus API is over DNS. Query the API domain, no result = no block + // https://docs.spamhaus.com/datasets/docs/source/70-access-methods/data-query-service/040-dqs-queries.html + if services().globals.url_previews().use_spamhaus_denylist { + let resolver = services().globals.dns_resolver(); + match resolver.lookup_ip(format!("{host}.dbl.spamhaus.org")).await { + Err(e) => { + if let ResolveErrorKind::NoRecordsFound { .. } = e.kind() { } + else { + tracing::log::warn!("Failed to check Spamhaus denylist: {}", e); + } + }, + Ok(_) => { + return Err(Error::BadRequest(ErrorKind::Unknown, "Domain fails reputation check")); + }, + } + } + let client = services().globals.default_client(); let response = client.head(url.as_str()).send().await?; diff --git a/src/config/mod.rs b/src/config/mod.rs index 29d8bc1f..1e9dbccb 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -110,6 +110,7 @@ pub struct WellKnownConfig { pub struct UrlPreviewConfig { pub default: UrlPreviewPermission, pub exceptions: Vec, + pub use_spamhaus_denylist: bool, } #[derive(Clone, Debug, Deserialize, Default)]