use crate::io::{list_files, process_files};
use clap::{Parser, Subcommand};
use env_logger::Env;
use log::{debug, error, info};
use std::path::PathBuf;
use std::time::Instant;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use libc::{RLIMIT_NOFILE, rlimit, setrlimit};
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn set_nofile_limit(soft: u64, hard: u64) -> Result<(), String> {
let lim = rlimit {
rlim_cur: soft,
rlim_max: hard,
};
let res = unsafe { setrlimit(RLIMIT_NOFILE, &lim) };
if res == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().to_string())
}
}
#[derive(Parser)]
#[command(name = "sigstrike")]
#[command(about = "An example app with subcommands", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(
long,
default_value = "error",
help = "Logging level (trace, debug, info, warn, error)",
global = true
)]
logging_level: String,
}
#[derive(Subcommand)]
enum Commands {
Process {
#[arg(
long,
help = "Input path to a file or directory containing Cobalt Strike beacons."
)]
input_path: PathBuf,
#[arg(
long,
help = "Output path for results. If not specified, results will be printed to stdout."
)]
output_path: Option<PathBuf>,
},
Crawl {
#[arg(long, help = "Path to input file containing URLs")]
input_path: PathBuf,
#[arg(long, help = "Path to output file (JSONL format)")]
output_path: PathBuf,
#[arg(long, default_value_t = 100)]
max_concurrent: usize,
#[arg(
long,
default_value_t = 2,
help = "Maximum number of retries for each URL"
)]
max_retries: usize,
#[arg(
long,
default_value_t = 10,
help = "Timeout in seconds for each request"
)]
timeout: u64,
},
}
pub fn parse_beacons(input_path: PathBuf, output_path: Option<PathBuf>) {
let input_str_path = input_path.display().to_string();
if input_path.is_file() {
let files: Vec<String> = Vec::from([input_str_path.clone()]);
if let Err(e) = process_files(files, output_path) {
error!("Error processing file {}: {}", &input_str_path, e);
}
} else if input_path.is_dir() {
let files = list_files(&input_str_path);
info!(
"Found {} files in directory: {}",
files.len(),
&input_str_path
);
if files.is_empty() {
error!("No files found in directory: {input_str_path}");
}
if let Err(e) = process_files(files, output_path) {
error!(
"Error processing files in directory {}: {}",
&input_str_path, e
);
}
} else {
error!(
"Input path is neither a file nor a directory: {}",
&input_str_path
);
};
}
pub async fn run_cli(start_arg: usize) {
let start = Instant::now();
let cli = Cli::parse_from(std::env::args().skip(start_arg));
let env = Env::default().default_filter_or(cli.logging_level);
env_logger::Builder::from_env(env).init();
#[cfg(any(target_os = "linux", target_os = "macos"))]
match set_nofile_limit(65535, 65535) {
Ok(()) => debug!("File limit set"),
Err(e) => error!("Failed to set limit: {e}"),
}
match cli.command {
Commands::Process {
input_path,
output_path,
} => {
parse_beacons(input_path, output_path);
}
Commands::Crawl {
input_path,
output_path,
max_concurrent,
max_retries,
timeout,
} => {
let crawl_result = crate::crawler::crawl(
&input_path,
&output_path,
max_concurrent,
max_retries,
timeout,
)
.await;
if let Err(e) = crawl_result {
error!("Error running crawler: {e}");
}
}
}
let end = Instant::now();
let duration = end.duration_since(start);
info!("Total execution time: {duration:?}");
}