1#![forbid(unsafe_code)]
2#![cfg_attr(
4 not(test),
5 deny(
6 clippy::unwrap_used,
7 clippy::expect_used,
8 clippy::todo,
9 clippy::unimplemented,
10 clippy::panic
11 )
12)]
13#![allow(
14 clippy::module_name_repetitions,
15 clippy::must_use_candidate,
16 clippy::missing_errors_doc
17)]
18
19pub mod scanners;
40pub mod sources;
41pub mod types;
42pub mod util;
43pub mod validator;
44
45use gossan_core::{Config, ScanClient};
46pub use types::{OriginCandidate, ValidationState};
47
48pub async fn discover_origin(
54 domain: &str,
55 config: &Config,
56) -> anyhow::Result<Vec<OriginCandidate>> {
57 let resolver = std::sync::Arc::new(gossan_core::net::build_resolver(config)?);
59 let client = std::sync::Arc::new(ScanClient::from_config(config, resolver)?);
60
61 let mut tasks: Vec<tokio::task::JoinHandle<anyhow::Result<Vec<OriginCandidate>>>> = Vec::new();
62
63 let d = domain.to_string();
64 let cfg = config.clone();
65
66 #[cfg(feature = "dns_misconfig")]
67 {
68 let domain_clone = d.clone();
69 tasks.push(tokio::spawn(async move {
70 scanners::dns_misconfig::scan(domain_clone).await
71 }));
72 }
73
74 #[cfg(feature = "ssl_cert")]
75 {
76 let domain_clone = d.clone();
77 let c = std::sync::Arc::clone(&client);
78 tasks.push(tokio::spawn(async move {
79 scanners::ssl_cert::scan(domain_clone, &c).await
80 }));
81 }
82
83 #[cfg(feature = "http_header")]
84 {
85 let domain_clone = d.clone();
86 let config_clone = cfg.clone();
87 let c = std::sync::Arc::clone(&client);
88 tasks.push(tokio::spawn(async move {
89 scanners::http_header::scan(domain_clone, &config_clone, &c).await
90 }));
91 }
92
93 #[cfg(feature = "favicon")]
94 {
95 let domain_clone = d.clone();
96 let config_clone = cfg.clone();
97 let c = std::sync::Arc::clone(&client);
98 tasks.push(tokio::spawn(async move {
99 scanners::favicon::scan(domain_clone, &config_clone, &c).await
100 }));
101 }
102
103 #[cfg(feature = "dns_history")]
104 {
105 let domain_clone = d.clone();
106 let config_clone = cfg.clone();
107 let c = std::sync::Arc::clone(&client);
108 tasks.push(tokio::spawn(async move {
109 scanners::dns_history::scan(domain_clone, &config_clone, &c).await
110 }));
111 }
112
113 {
115 let domain_clone = d.clone();
116 let config_clone = cfg.clone();
117 let c = std::sync::Arc::clone(&client);
118 tasks.push(tokio::spawn(async move {
119 sources::censys::scan(&domain_clone, &config_clone, &c).await
120 }));
121 }
122 {
123 let domain_clone = d.clone();
124 let config_clone = cfg.clone();
125 let c = std::sync::Arc::clone(&client);
126 tasks.push(tokio::spawn(async move {
127 sources::dnsdb::scan(&domain_clone, &config_clone, &c).await
128 }));
129 }
130 {
131 let domain_clone = d.clone();
132 let config_clone = cfg.clone();
133 let c = std::sync::Arc::clone(&client);
134 tasks.push(tokio::spawn(async move {
135 sources::circl::scan(&domain_clone, &config_clone, &c).await
136 }));
137 }
138 {
139 let domain_clone = d.clone();
140 let config_clone = cfg.clone();
141 let c = std::sync::Arc::clone(&client);
142 tasks.push(tokio::spawn(async move {
143 sources::passivetotal::scan(&domain_clone, &config_clone, &c).await
144 }));
145 }
146
147 let mut candidates = Vec::new();
148
149 for task in tasks {
150 match task.await {
151 Ok(Ok(results)) => candidates.extend(results),
152 Ok(Err(e)) => {
153 tracing::warn!(error = %e, "origin scanner returned error");
154 }
155 Err(e) => {
156 tracing::warn!(error = %e, "origin scanner task panicked");
157 }
158 }
159 }
160
161 candidates = validator::validate(candidates, domain, config, &client).await;
163
164 Ok(candidates)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[cfg(not(any(
173 feature = "dns_misconfig",
174 feature = "ssl_cert",
175 feature = "http_header",
176 feature = "favicon",
177 feature = "dns_history"
178 )))]
179 #[tokio::test]
180 async fn discover_origin_returns_empty_without_scanners() {
181 let candidates = discover_origin("example.com", &Config::default())
182 .await
183 .unwrap();
184 assert!(candidates.is_empty());
185 }
186
187 #[cfg(any(
189 feature = "dns_misconfig",
190 feature = "ssl_cert",
191 feature = "http_header",
192 feature = "favicon",
193 feature = "dns_history"
194 ))]
195 #[tokio::test]
196 async fn discover_origin_runs_without_panic() {
197 let result = discover_origin("example.com", &Config::default()).await;
198 assert!(result.is_ok() || result.is_err());
199 }
200}