#![forbid(unsafe_code)]
#![cfg_attr(
not(test),
deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::todo,
clippy::unimplemented,
clippy::panic
)
)]
#![allow(
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::missing_errors_doc
)]
pub mod scanners;
pub mod sources;
pub mod types;
pub mod util;
pub mod validator;
use gossan_core::{Config, ScanClient};
pub use types::{OriginCandidate, ValidationState};
pub async fn discover_origin(
domain: &str,
config: &Config,
) -> anyhow::Result<Vec<OriginCandidate>> {
let resolver = std::sync::Arc::new(gossan_core::net::build_resolver(config)?);
let client = std::sync::Arc::new(ScanClient::from_config(config, resolver)?);
let mut tasks: Vec<tokio::task::JoinHandle<anyhow::Result<Vec<OriginCandidate>>>> = Vec::new();
let d = domain.to_string();
let cfg = config.clone();
#[cfg(feature = "dns_misconfig")]
{
let domain_clone = d.clone();
tasks.push(tokio::spawn(async move {
scanners::dns_misconfig::scan(domain_clone).await
}));
}
#[cfg(feature = "ssl_cert")]
{
let domain_clone = d.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
scanners::ssl_cert::scan(domain_clone, &c).await
}));
}
#[cfg(feature = "http_header")]
{
let domain_clone = d.clone();
let config_clone = cfg.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
scanners::http_header::scan(domain_clone, &config_clone, &c).await
}));
}
#[cfg(feature = "favicon")]
{
let domain_clone = d.clone();
let config_clone = cfg.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
scanners::favicon::scan(domain_clone, &config_clone, &c).await
}));
}
#[cfg(feature = "dns_history")]
{
let domain_clone = d.clone();
let config_clone = cfg.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
scanners::dns_history::scan(domain_clone, &config_clone, &c).await
}));
}
{
let domain_clone = d.clone();
let config_clone = cfg.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
sources::censys::scan(&domain_clone, &config_clone, &c).await
}));
}
{
let domain_clone = d.clone();
let config_clone = cfg.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
sources::dnsdb::scan(&domain_clone, &config_clone, &c).await
}));
}
{
let domain_clone = d.clone();
let config_clone = cfg.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
sources::circl::scan(&domain_clone, &config_clone, &c).await
}));
}
{
let domain_clone = d.clone();
let config_clone = cfg.clone();
let c = std::sync::Arc::clone(&client);
tasks.push(tokio::spawn(async move {
sources::passivetotal::scan(&domain_clone, &config_clone, &c).await
}));
}
let mut candidates = Vec::new();
for task in tasks {
match task.await {
Ok(Ok(results)) => candidates.extend(results),
Ok(Err(e)) => {
tracing::warn!(error = %e, "origin scanner returned error");
}
Err(e) => {
tracing::warn!(error = %e, "origin scanner task panicked");
}
}
}
candidates = validator::validate(candidates, domain, config, &client).await;
Ok(candidates)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(any(
feature = "dns_misconfig",
feature = "ssl_cert",
feature = "http_header",
feature = "favicon",
feature = "dns_history"
)))]
#[tokio::test]
async fn discover_origin_returns_empty_without_scanners() {
let candidates = discover_origin("example.com", &Config::default())
.await
.unwrap();
assert!(candidates.is_empty());
}
#[cfg(any(
feature = "dns_misconfig",
feature = "ssl_cert",
feature = "http_header",
feature = "favicon",
feature = "dns_history"
))]
#[tokio::test]
async fn discover_origin_runs_without_panic() {
let result = discover_origin("example.com", &Config::default()).await;
assert!(result.is_ok() || result.is_err());
}
}