1use clap::Parser;
2use csv::WriterBuilder;
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6use crate::commands::Command;
7use remote::spawn_remote_scan;
8use scan::{GenerateConfig, ScanConfig, ScanError, spawn_generate, spawn_local_scan};
9
10#[derive(Parser, Serialize, Deserialize)]
11pub struct ScanCommand {
12 #[arg(long)]
14 pub remote: Option<String>,
15
16 #[command(flatten)]
18 pub generate: GenerateConfig,
19
20 #[command(flatten)]
22 pub config: ScanConfig,
23
24 #[arg(short = 'o', long = "output-file", value_name = "PATH")]
26 pub output_file: Option<PathBuf>,
27
28 #[arg(long = "columns", value_name = "COLUMNS", value_delimiter = ',')]
30 pub columns: Option<Vec<String>>,
31}
32
33impl ScanCommand {
34 pub async fn run(&self) -> Result<(), ScanError> {
35 let config = self.config.clone();
36 let generate = self.generate.clone();
37
38 let default_columns = vec![
39 "target".to_string(),
40 "alias".to_string(),
41 "alias_prefix".to_string(),
42 "port".to_string(),
43 ];
44 let columns = self
45 .columns
46 .clone()
47 .filter(|c| !c.is_empty())
48 .unwrap_or(default_columns);
49
50 let mut csv_writer = if let Some(path) = &self.output_file {
51 let writer = WriterBuilder::new()
52 .has_headers(false)
53 .from_path(path)
54 .map_err(|e| ScanError::OutputError(e.to_string()))?;
55 Some((writer, false))
56 } else {
57 None
58 };
59
60 let (sender, mut results) = match &self.remote {
62 Some(addr) => spawn_remote_scan(config, addr).await?,
63 None => spawn_local_scan(config).await?,
64 };
65
66 spawn_generate(generate, sender).await?;
68
69 while let Some(result) = results.recv().await {
71 tracing::info!("{}", result);
72
73 if let Some((writer, headers_written)) = csv_writer.as_mut() {
74 if !*headers_written {
75 writer
76 .write_record(columns.iter())
77 .map_err(|e| ScanError::OutputError(e.to_string()))?;
78 *headers_written = true;
79 }
80
81 let row = result.to_row(&columns);
82 writer
83 .write_record(row)
84 .map_err(|e| ScanError::OutputError(e.to_string()))?;
85 }
86 }
87
88 if let Some((mut writer, _)) = csv_writer {
89 writer
90 .flush()
91 .map_err(|e| ScanError::OutputError(e.to_string()))?;
92 }
93
94 Ok(())
95 }
96}
97
98impl Command for ScanCommand {
99 async fn run(&self) -> Result<(), String> {
100 self.run().await.map_err(|e| e.to_string())
101 }
102}