use crate::utils::status_colorizer;
use crate::{client, parser, progress};
use crate::{DEFAULT_CONFIG_NAME, DEFAULT_STATUS_CODES, DEFAULT_WORDLIST, VERSION};
use ansi_term::Color::Cyan;
use clap::value_t;
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget};
use lazy_static::lazy_static;
use reqwest::{Client, StatusCode};
use serde::Deserialize;
use std::collections::HashMap;
use std::env::{current_dir, current_exe};
use std::fs::read_to_string;
use std::path::PathBuf;
use std::process::exit;
lazy_static! {
pub static ref CONFIGURATION: Configuration = Configuration::new();
pub static ref PROGRESS_BAR: MultiProgress = MultiProgress::with_draw_target(ProgressDrawTarget::stdout());
pub static ref PROGRESS_PRINTER: ProgressBar = progress::add_bar("", 0, true);
}
#[derive(Debug, Clone, Deserialize)]
pub struct Configuration {
#[serde(default = "wordlist")]
pub wordlist: String,
#[serde(default)]
pub config: String,
#[serde(default)]
pub proxy: String,
#[serde(default)]
pub target_url: String,
#[serde(default = "statuscodes")]
pub statuscodes: Vec<u16>,
#[serde(skip)]
pub client: Client,
#[serde(default = "threads")]
pub threads: usize,
#[serde(default = "timeout")]
pub timeout: u64,
#[serde(default)]
pub verbosity: u8,
#[serde(default)]
pub quiet: bool,
#[serde(default)]
pub output: String,
#[serde(default = "useragent")]
pub useragent: String,
#[serde(default)]
pub redirects: bool,
#[serde(default)]
pub insecure: bool,
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default)]
pub headers: HashMap<String, String>,
#[serde(default)]
pub queries: Vec<(String, String)>,
#[serde(default)]
pub norecursion: bool,
#[serde(default)]
pub addslash: bool,
#[serde(default)]
pub stdin: bool,
#[serde(default = "depth")]
pub depth: usize,
#[serde(default)]
pub sizefilters: Vec<u64>,
#[serde(default)]
pub dontfilter: bool,
}
fn timeout() -> u64 {
7
}
fn threads() -> usize {
50
}
fn statuscodes() -> Vec<u16> {
DEFAULT_STATUS_CODES
.iter()
.map(|code| code.as_u16())
.collect()
}
fn wordlist() -> String {
String::from(DEFAULT_WORDLIST)
}
fn useragent() -> String {
format!("feroxbuster/{}", VERSION)
}
fn depth() -> usize {
4
}
impl Default for Configuration {
fn default() -> Self {
let timeout = timeout();
let useragent = useragent();
let client = client::initialize(timeout, &useragent, false, false, &HashMap::new(), None);
Configuration {
client,
timeout,
useragent,
dontfilter: false,
quiet: false,
stdin: false,
verbosity: 0,
addslash: false,
insecure: false,
norecursion: false,
redirects: false,
proxy: String::new(),
config: String::new(),
output: String::new(),
target_url: String::new(),
queries: Vec::new(),
extensions: Vec::new(),
sizefilters: Vec::new(),
headers: HashMap::new(),
threads: threads(),
depth: depth(),
wordlist: wordlist(),
statuscodes: statuscodes(),
}
}
}
impl Configuration {
pub fn new() -> Self {
let mut config = Configuration::default();
let config_file = PathBuf::new()
.join("/etc/feroxbuster")
.join(DEFAULT_CONFIG_NAME);
Self::parse_and_merge_config(config_file, &mut config);
if let Some(config_dir) = dirs::config_dir() {
let config_file = config_dir.join("feroxbuster").join(DEFAULT_CONFIG_NAME);
Self::parse_and_merge_config(config_file, &mut config);
};
if let Ok(exe_path) = current_exe() {
if let Some(bin_dir) = exe_path.parent() {
let config_file = bin_dir.join(DEFAULT_CONFIG_NAME);
Self::parse_and_merge_config(config_file, &mut config);
};
};
if let Ok(cwd) = current_dir() {
let config_file = cwd.join(DEFAULT_CONFIG_NAME);
Self::parse_and_merge_config(config_file, &mut config);
}
let args = parser::initialize().get_matches();
if args.value_of("threads").is_some() {
let threads = value_t!(args.value_of("threads"), usize).unwrap_or_else(|e| e.exit());
config.threads = threads;
}
if args.value_of("depth").is_some() {
let depth = value_t!(args.value_of("depth"), usize).unwrap_or_else(|e| e.exit());
config.depth = depth;
}
if args.value_of("wordlist").is_some() {
config.wordlist = String::from(args.value_of("wordlist").unwrap());
}
if args.value_of("output").is_some() {
config.output = String::from(args.value_of("output").unwrap());
}
if args.values_of("statuscodes").is_some() {
config.statuscodes = args
.values_of("statuscodes")
.unwrap()
.map(|code| {
StatusCode::from_bytes(code.as_bytes())
.unwrap_or_else(|e| {
eprintln!("[!] Error encountered: {}", e);
exit(1)
})
.as_u16()
})
.collect();
}
if args.values_of("extensions").is_some() {
config.extensions = args
.values_of("extensions")
.unwrap()
.map(|val| val.to_string())
.collect();
}
if args.values_of("sizefilters").is_some() {
config.sizefilters = args
.values_of("sizefilters")
.unwrap()
.map(|size| {
size.parse::<u64>().unwrap_or_else(|e| {
eprintln!("[!] Error encountered: {}", e);
exit(1)
})
})
.collect();
}
if args.is_present("quiet") {
config.quiet = args.is_present("quiet");
}
if args.is_present("dontfilter") {
config.dontfilter = args.is_present("dontfilter");
}
if args.occurrences_of("verbosity") > 0 {
config.verbosity = args.occurrences_of("verbosity") as u8;
}
if args.is_present("norecursion") {
config.norecursion = args.is_present("norecursion");
}
if args.is_present("addslash") {
config.addslash = args.is_present("addslash");
}
if args.is_present("stdin") {
config.stdin = args.is_present("stdin");
} else {
config.target_url = String::from(args.value_of("url").unwrap());
}
if args.value_of("proxy").is_some() {
config.proxy = String::from(args.value_of("proxy").unwrap());
}
if args.value_of("useragent").is_some() {
config.useragent = String::from(args.value_of("useragent").unwrap());
}
if args.value_of("timeout").is_some() {
let timeout = value_t!(args.value_of("timeout"), u64).unwrap_or_else(|e| e.exit());
config.timeout = timeout;
}
if args.is_present("redirects") {
config.redirects = args.is_present("redirects");
}
if args.is_present("insecure") {
config.insecure = args.is_present("insecure");
}
if args.values_of("headers").is_some() {
for val in args.values_of("headers").unwrap() {
let mut split_val = val.split(':');
let name = split_val.next().unwrap().trim();
let value = split_val.collect::<Vec<&str>>().join(":");
config.headers.insert(name.to_string(), value.to_string());
}
}
if args.values_of("queries").is_some() {
for val in args.values_of("queries").unwrap() {
let mut split_val = val.split('=');
let name = split_val.next().unwrap().trim();
let value = split_val.collect::<Vec<&str>>().join("=");
config.queries.push((name.to_string(), value.to_string()));
}
}
if !config.proxy.is_empty()
|| config.timeout != timeout()
|| config.useragent != useragent()
|| config.redirects
|| config.insecure
|| !config.headers.is_empty()
{
if config.proxy.is_empty() {
config.client = client::initialize(
config.timeout,
&config.useragent,
config.redirects,
config.insecure,
&config.headers,
None,
)
} else {
config.client = client::initialize(
config.timeout,
&config.useragent,
config.redirects,
config.insecure,
&config.headers,
Some(&config.proxy),
)
}
}
config
}
fn parse_and_merge_config(config_file: PathBuf, mut config: &mut Self) {
if config_file.exists() {
let conf_str = match config_file.to_str() {
Some(cs) => String::from(cs),
None => String::new(),
};
if let Some(settings) = Self::parse_config(config_file) {
config.config = conf_str;
Self::merge_config(&mut config, settings);
}
}
}
fn merge_config(settings: &mut Self, settings_to_merge: Self) {
settings.threads = settings_to_merge.threads;
settings.wordlist = settings_to_merge.wordlist;
settings.statuscodes = settings_to_merge.statuscodes;
settings.proxy = settings_to_merge.proxy;
settings.timeout = settings_to_merge.timeout;
settings.verbosity = settings_to_merge.verbosity;
settings.quiet = settings_to_merge.quiet;
settings.output = settings_to_merge.output;
settings.useragent = settings_to_merge.useragent;
settings.redirects = settings_to_merge.redirects;
settings.insecure = settings_to_merge.insecure;
settings.extensions = settings_to_merge.extensions;
settings.headers = settings_to_merge.headers;
settings.queries = settings_to_merge.queries;
settings.norecursion = settings_to_merge.norecursion;
settings.addslash = settings_to_merge.addslash;
settings.stdin = settings_to_merge.stdin;
settings.depth = settings_to_merge.depth;
settings.sizefilters = settings_to_merge.sizefilters;
settings.dontfilter = settings_to_merge.dontfilter;
}
fn parse_config(config_file: PathBuf) -> Option<Self> {
if let Ok(content) = read_to_string(config_file) {
match toml::from_str(content.as_str()) {
Ok(config) => {
return Some(config);
}
Err(e) => {
println!(
"{} {} {}",
status_colorizer("ERROR"),
Cyan.paint("config::parse_config"),
e
);
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::write;
use tempfile::TempDir;
fn setup_config_test() -> Configuration {
let data = r#"
wordlist = "/some/path"
statuscodes = [201, 301, 401]
threads = 40
timeout = 5
proxy = "http://127.0.0.1:8080"
quiet = true
verbosity = 1
output = "/some/otherpath"
redirects = true
insecure = true
extensions = ["html", "php", "js"]
headers = {stuff = "things", mostuff = "mothings"}
queries = [["name","value"], ["rick", "astley"]]
norecursion = true
addslash = true
stdin = true
dontfilter = true
depth = 1
sizefilters = [4120]
"#;
let tmp_dir = TempDir::new().unwrap();
let file = tmp_dir.path().join(DEFAULT_CONFIG_NAME);
write(&file, data).unwrap();
Configuration::parse_config(file).unwrap()
}
#[test]
fn default_configuration() {
let config = Configuration::default();
assert_eq!(config.wordlist, wordlist());
assert_eq!(config.proxy, String::new());
assert_eq!(config.target_url, String::new());
assert_eq!(config.config, String::new());
assert_eq!(config.statuscodes, statuscodes());
assert_eq!(config.threads, threads());
assert_eq!(config.depth, depth());
assert_eq!(config.timeout, timeout());
assert_eq!(config.verbosity, 0);
assert_eq!(config.quiet, false);
assert_eq!(config.dontfilter, false);
assert_eq!(config.norecursion, false);
assert_eq!(config.stdin, false);
assert_eq!(config.addslash, false);
assert_eq!(config.redirects, false);
assert_eq!(config.insecure, false);
assert_eq!(config.queries, Vec::new());
assert_eq!(config.extensions, Vec::<String>::new());
assert_eq!(config.sizefilters, Vec::<u64>::new());
assert_eq!(config.headers, HashMap::new());
}
#[test]
fn config_reads_wordlist() {
let config = setup_config_test();
assert_eq!(config.wordlist, "/some/path");
}
#[test]
fn config_reads_statuscodes() {
let config = setup_config_test();
assert_eq!(config.statuscodes, vec![201, 301, 401]);
}
#[test]
fn config_reads_threads() {
let config = setup_config_test();
assert_eq!(config.threads, 40);
}
#[test]
fn config_reads_depth() {
let config = setup_config_test();
assert_eq!(config.depth, 1);
}
#[test]
fn config_reads_timeout() {
let config = setup_config_test();
assert_eq!(config.timeout, 5);
}
#[test]
fn config_reads_proxy() {
let config = setup_config_test();
assert_eq!(config.proxy, "http://127.0.0.1:8080");
}
#[test]
fn config_reads_quiet() {
let config = setup_config_test();
assert_eq!(config.quiet, true);
}
#[test]
fn config_reads_verbosity() {
let config = setup_config_test();
assert_eq!(config.verbosity, 1);
}
#[test]
fn config_reads_output() {
let config = setup_config_test();
assert_eq!(config.output, "/some/otherpath");
}
#[test]
fn config_reads_redirects() {
let config = setup_config_test();
assert_eq!(config.redirects, true);
}
#[test]
fn config_reads_insecure() {
let config = setup_config_test();
assert_eq!(config.insecure, true);
}
#[test]
fn config_reads_norecursion() {
let config = setup_config_test();
assert_eq!(config.norecursion, true);
}
#[test]
fn config_reads_stdin() {
let config = setup_config_test();
assert_eq!(config.stdin, true);
}
#[test]
fn config_reads_dontfilter() {
let config = setup_config_test();
assert_eq!(config.dontfilter, true);
}
#[test]
fn config_reads_addslash() {
let config = setup_config_test();
assert_eq!(config.addslash, true);
}
#[test]
fn config_reads_extensions() {
let config = setup_config_test();
assert_eq!(config.extensions, vec!["html", "php", "js"]);
}
#[test]
fn config_reads_sizefilters() {
let config = setup_config_test();
assert_eq!(config.sizefilters, vec![4120]);
}
#[test]
fn config_reads_headers() {
let config = setup_config_test();
let mut headers = HashMap::new();
headers.insert("stuff".to_string(), "things".to_string());
headers.insert("mostuff".to_string(), "mothings".to_string());
assert_eq!(config.headers, headers);
}
#[test]
fn config_reads_queries() {
let config = setup_config_test();
let mut queries = vec![];
queries.push(("name".to_string(), "value".to_string()));
queries.push(("rick".to_string(), "astley".to_string()));
assert_eq!(config.queries, queries);
}
}