pub mod alpn_phase;
pub mod certificate_phase;
pub mod cipher_phase;
pub mod client_cas_phase;
pub mod client_sim_phase;
pub mod fingerprint_phase;
pub mod groups_phase;
pub mod http_headers_phase;
pub mod intolerance_phase;
pub mod protocol_phase;
pub mod signature_phase;
pub mod vulnerability_phase;
pub use alpn_phase::AlpnPhase;
pub use certificate_phase::CertificatePhase;
pub use cipher_phase::CipherPhase;
pub use client_cas_phase::ClientCasPhase;
pub use client_sim_phase::ClientSimPhase;
pub use fingerprint_phase::FingerprintPhase;
pub use groups_phase::GroupsPhase;
pub use http_headers_phase::HttpHeadersPhase;
pub use intolerance_phase::IntolerancePhase;
pub use protocol_phase::ProtocolPhase;
pub use signature_phase::SignaturePhase;
pub use vulnerability_phase::VulnerabilityPhase;
use crate::scanner::ScanResults;
use crate::utils::mtls::MtlsConfig;
use crate::utils::network::Target;
use crate::{Args, Result};
use async_trait::async_trait;
use std::sync::Arc;
pub trait ScanProgressReporter: Send + Sync {
fn on_phase_start(&self, phase_name: &str);
fn on_phase_complete(&self, phase_name: &str);
}
pub struct TerminalProgressReporter;
impl TerminalProgressReporter {
pub fn new() -> Self {
Self
}
}
impl Default for TerminalProgressReporter {
fn default() -> Self {
Self::new()
}
}
impl ScanProgressReporter for TerminalProgressReporter {
fn on_phase_start(&self, phase_name: &str) {
use colored::*;
println!("\n{}", format!("{}...", phase_name).yellow().bold());
}
fn on_phase_complete(&self, _phase_name: &str) {
}
}
pub struct SilentProgressReporter;
impl SilentProgressReporter {
pub fn new() -> Self {
Self
}
}
impl Default for SilentProgressReporter {
fn default() -> Self {
Self::new()
}
}
impl ScanProgressReporter for SilentProgressReporter {
fn on_phase_start(&self, _phase_name: &str) {
}
fn on_phase_complete(&self, _phase_name: &str) {
}
}
#[async_trait]
pub trait ScanPhase: Send + Sync {
fn name(&self) -> &'static str;
fn should_run(&self, args: &Args) -> bool;
async fn execute(&self, context: &mut ScanContext) -> Result<()>;
}
pub struct ScanContext {
pub target: Target,
pub results: ScanResults,
pub args: Arc<Args>,
pub mtls_config: Option<MtlsConfig>,
}
impl ScanContext {
pub fn new(target: Target, args: Arc<Args>, mtls_config: Option<MtlsConfig>) -> Self {
let target_str = format!("{}:{}", target.hostname, target.port);
Self {
target,
results: ScanResults {
target: target_str,
scan_time_ms: 0,
..Default::default()
},
args,
mtls_config,
}
}
pub fn target(&self) -> Target {
self.target.clone()
}
}
pub struct PhaseOrchestrator {
phases: Vec<Box<dyn ScanPhase>>,
reporter: Option<Arc<dyn ScanProgressReporter>>,
}
impl PhaseOrchestrator {
pub fn new() -> Self {
Self {
phases: Vec::new(),
reporter: None,
}
}
pub fn with_reporter(reporter: Arc<dyn ScanProgressReporter>) -> Self {
Self {
phases: Vec::new(),
reporter: Some(reporter),
}
}
pub fn set_reporter(mut self, reporter: Arc<dyn ScanProgressReporter>) -> Self {
self.reporter = Some(reporter);
self
}
pub fn add_phase(mut self, phase: Box<dyn ScanPhase>) -> Self {
self.phases.push(phase);
self
}
pub async fn execute(&self, mut context: ScanContext) -> Result<ScanResults> {
for phase in &self.phases {
if phase.should_run(&context.args) {
if let Some(ref reporter) = self.reporter {
reporter.on_phase_start(phase.name());
}
phase.execute(&mut context).await?;
if let Some(ref reporter) = self.reporter {
reporter.on_phase_complete(phase.name());
}
}
}
Ok(context.results)
}
}
impl Default for PhaseOrchestrator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
struct CountingReporter {
start_count: AtomicUsize,
complete_count: AtomicUsize,
}
impl CountingReporter {
fn new() -> Self {
Self {
start_count: AtomicUsize::new(0),
complete_count: AtomicUsize::new(0),
}
}
fn start_count(&self) -> usize {
self.start_count.load(Ordering::SeqCst)
}
fn complete_count(&self) -> usize {
self.complete_count.load(Ordering::SeqCst)
}
}
impl ScanProgressReporter for CountingReporter {
fn on_phase_start(&self, _phase_name: &str) {
self.start_count.fetch_add(1, Ordering::SeqCst);
}
fn on_phase_complete(&self, _phase_name: &str) {
self.complete_count.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn test_terminal_reporter_creation() {
let reporter = TerminalProgressReporter::new();
reporter.on_phase_start("Test Phase");
reporter.on_phase_complete("Test Phase");
}
#[test]
fn test_silent_reporter_creation() {
let reporter = SilentProgressReporter::new();
reporter.on_phase_start("Test Phase");
reporter.on_phase_complete("Test Phase");
}
#[test]
fn test_counting_reporter() {
let reporter = CountingReporter::new();
assert_eq!(reporter.start_count(), 0);
assert_eq!(reporter.complete_count(), 0);
reporter.on_phase_start("Phase 1");
assert_eq!(reporter.start_count(), 1);
reporter.on_phase_complete("Phase 1");
assert_eq!(reporter.complete_count(), 1);
reporter.on_phase_start("Phase 2");
reporter.on_phase_complete("Phase 2");
assert_eq!(reporter.start_count(), 2);
assert_eq!(reporter.complete_count(), 2);
}
#[test]
fn test_orchestrator_with_reporter() {
let reporter = Arc::new(CountingReporter::new());
let orchestrator = PhaseOrchestrator::with_reporter(reporter.clone());
assert!(orchestrator.reporter.is_some());
}
#[test]
fn test_orchestrator_set_reporter() {
let reporter = Arc::new(SilentProgressReporter::new());
let orchestrator = PhaseOrchestrator::new().set_reporter(reporter);
assert!(orchestrator.reporter.is_some());
}
#[test]
fn test_orchestrator_default_no_reporter() {
let orchestrator = PhaseOrchestrator::new();
assert!(orchestrator.reporter.is_none());
}
}