feat: add proxy group selection via --group and [ProxyList:<name>]
This commit is contained in:
1 parent
473a7faf47
commit
bdb5cf5add
4 files changed
+149
-4
No files matched your search
@@ -157,6 +157,7 @@ Usage: proxychains4 [OPTIONS] <COMMAND> [ARGS...]
|
||||
|
||||
Options:
|
||||
-f, --file <PATH> Configuration file path
|
||||
--group <GROUP> Proxy group name (uses [ProxyList:<GROUP>])
|
||||
-q, --quiet Quiet mode (suppress output)
|
||||
-v, --verbose Verbose mode (debug output)
|
||||
-h, --help Show help
|
||||
@@ -226,6 +227,18 @@ socks4 192.168.1.2 1080
|
||||
# HTTP proxies
|
||||
http 192.168.1.3 8080
|
||||
http 192.168.1.4 8080 user pass
|
||||
|
||||
# Optional grouped lists
|
||||
[ProxyList:jp]
|
||||
socks5 10.0.0.2 1080
|
||||
|
||||
[ProxyList:us]
|
||||
socks5 10.0.0.3 1080
|
||||
```
|
||||
|
||||
Group usage example:
|
||||
```bash
|
||||
proxychains4 --group jp curl https://ifconfig.me
|
||||
```
|
||||
|
||||
### Configuration File Locations
|
||||
|
||||
@@ -35,6 +35,10 @@ struct Args {
|
||||
#[arg(short = 'v', long)]
|
||||
verbose: bool,
|
||||
|
||||
/// Proxy group name, matches config sections like [ProxyList:<group>]
|
||||
#[arg(long, value_name = "GROUP")]
|
||||
group: Option<String>,
|
||||
|
||||
/// The command to run
|
||||
#[arg(required = true, trailing_var_arg = true)]
|
||||
command: Vec<String>,
|
||||
@@ -86,6 +90,9 @@ fn main() {
|
||||
info!("Proxychains4 starting...");
|
||||
info!("Chain type: {}", config.chain_type);
|
||||
info!("Proxies: {}", config.proxy_count());
|
||||
if let Some(group) = args.group.as_deref() {
|
||||
info!("Proxy group: {}", group);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the command with platform-specific injection
|
||||
@@ -110,6 +117,10 @@ fn load_config(args: &Args) -> Result<Config, String> {
|
||||
parser = parser.with_path(path.clone());
|
||||
}
|
||||
|
||||
if let Some(ref group) = args.group {
|
||||
parser = parser.with_group(group.clone());
|
||||
}
|
||||
|
||||
parser.parse().map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -123,8 +134,13 @@ fn set_proxychains_env(config: &Config, args: &Args) {
|
||||
env::set_var("PROXYCHAINS_DNS", "1");
|
||||
}
|
||||
|
||||
// Note: The actual config file path is handled by the library
|
||||
// when it reads PROXYCHAINS_CONF_FILE
|
||||
if let Some(ref path) = args.config {
|
||||
env::set_var("PROXYCHAINS_CONF_FILE", path);
|
||||
}
|
||||
|
||||
if let Some(ref group) = args.group {
|
||||
env::set_var("PROXYCHAINS_PROXY_GROUP", group);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -322,6 +338,8 @@ mod tests {
|
||||
"-q",
|
||||
"-f",
|
||||
"/etc/proxychains.conf",
|
||||
"--group",
|
||||
"jp",
|
||||
"wget",
|
||||
"http://example.com",
|
||||
]);
|
||||
@@ -329,6 +347,7 @@ mod tests {
|
||||
let args = args.unwrap();
|
||||
assert!(args.quiet);
|
||||
assert_eq!(args.config, Some(PathBuf::from("/etc/proxychains.conf")));
|
||||
assert_eq!(args.group, Some("jp".to_string()));
|
||||
assert_eq!(args.command, vec!["wget", "http://example.com"]);
|
||||
}
|
||||
}
|
||||
@@ -44,3 +44,10 @@ localnet 10.0.0.0/255.0.0.0
|
||||
|
||||
# Example: Default Tor proxy
|
||||
# socks5 127.0.0.1 9050
|
||||
|
||||
# Optional grouped lists (select with: proxychains4 --group <name> ...)
|
||||
# [ProxyList:jp]
|
||||
# socks5 1.2.3.4 1080
|
||||
#
|
||||
# [ProxyList:us]
|
||||
# socks5 5.6.7.8 1080
|
||||
@@ -17,16 +17,21 @@ pub const ENV_QUIET_MODE: &str = "PROXYCHAINS_QUIET_MODE";
|
||||
pub const ENV_SOCKS5_HOST: &str = "PROXYCHAINS_SOCKS5_HOST";
|
||||
pub const ENV_SOCKS5_PORT: &str = "PROXYCHAINS_SOCKS5_PORT";
|
||||
pub const ENV_DNS: &str = "PROXYCHAINS_DNS";
|
||||
pub const ENV_PROXY_GROUP: &str = "PROXYCHAINS_PROXY_GROUP";
|
||||
|
||||
/// Configuration parser
|
||||
pub struct ConfigParser {
|
||||
config_path: Option<PathBuf>,
|
||||
proxy_group: Option<String>,
|
||||
}
|
||||
|
||||
impl ConfigParser {
|
||||
/// Create a new configuration parser
|
||||
pub fn new() -> Self {
|
||||
Self { config_path: None }
|
||||
Self {
|
||||
config_path: None,
|
||||
proxy_group: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the configuration file path
|
||||
@@ -35,6 +40,20 @@ impl ConfigParser {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set proxy group name. Matches sections like [ProxyList:<group>].
|
||||
pub fn with_group(mut self, group: impl Into<String>) -> Self {
|
||||
self.proxy_group = Some(group.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn selected_group(&self) -> Option<String> {
|
||||
self.proxy_group
|
||||
.clone()
|
||||
.or_else(|| env::var(ENV_PROXY_GROUP).ok())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Find the configuration file
|
||||
pub fn find_config_file(&self) -> Option<PathBuf> {
|
||||
// If path was explicitly set, use it
|
||||
@@ -115,6 +134,8 @@ impl ConfigParser {
|
||||
|
||||
let mut config = Config::default();
|
||||
let mut in_proxy_list = false;
|
||||
let selected_group = self.selected_group().map(|s| s.to_lowercase());
|
||||
let mut matched_selected_group = selected_group.is_none();
|
||||
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
@@ -127,7 +148,31 @@ impl ConfigParser {
|
||||
|
||||
// Check for section markers
|
||||
if trimmed.starts_with('[') {
|
||||
in_proxy_list = trimmed.to_lowercase().contains("proxylist");
|
||||
let section = trimmed
|
||||
.trim_start_matches('[')
|
||||
.trim_end_matches(']')
|
||||
.trim();
|
||||
let section_lower = section.to_lowercase();
|
||||
|
||||
if section_lower.starts_with("proxylist") {
|
||||
let group_name = section
|
||||
.split_once(':')
|
||||
.map(|(_, g)| g.trim().to_lowercase())
|
||||
.filter(|g| !g.is_empty())
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
|
||||
in_proxy_list = if let Some(ref selected) = selected_group {
|
||||
let is_match = &group_name == selected;
|
||||
if is_match {
|
||||
matched_selected_group = true;
|
||||
}
|
||||
is_match
|
||||
} else {
|
||||
true
|
||||
};
|
||||
} else {
|
||||
in_proxy_list = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -141,6 +186,15 @@ impl ConfigParser {
|
||||
// Apply environment variable overrides
|
||||
self.apply_env_overrides(&mut config)?;
|
||||
|
||||
if let Some(group) = selected_group {
|
||||
if !matched_selected_group {
|
||||
return Err(Error::Config(format!(
|
||||
"Proxy group not found: {}",
|
||||
group
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -314,7 +368,9 @@ mod dirs {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use std::str::FromStr;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[test]
|
||||
fn test_parse_proxy_type() {
|
||||
@@ -338,4 +394,54 @@ mod tests {
|
||||
assert!(localnet.contains(&Ipv4Addr::new(192, 168, 255, 255)));
|
||||
assert!(!localnet.contains(&Ipv4Addr::new(10, 0, 0, 1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_proxy_group() {
|
||||
let ts = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("proxychains_group_{}.conf", ts));
|
||||
let content = r#"
|
||||
dynamic_chain
|
||||
[ProxyList]
|
||||
socks5 127.0.0.1 1080
|
||||
[ProxyList:jp]
|
||||
socks5 127.0.0.2 1080
|
||||
"#;
|
||||
fs::write(&path, content).unwrap();
|
||||
|
||||
let config = ConfigParser::new()
|
||||
.with_path(path.clone())
|
||||
.with_group("jp")
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert_eq!(config.proxies.len(), 1);
|
||||
assert_eq!(config.proxies[0].ip, Ipv4Addr::new(127, 0, 0, 2));
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_proxy_group_returns_error() {
|
||||
let ts = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("proxychains_group_missing_{}.conf", ts));
|
||||
let content = r#"
|
||||
[ProxyList]
|
||||
socks5 127.0.0.1 1080
|
||||
"#;
|
||||
fs::write(&path, content).unwrap();
|
||||
|
||||
let err = ConfigParser::new()
|
||||
.with_path(path.clone())
|
||||
.with_group("us")
|
||||
.parse()
|
||||
.unwrap_err();
|
||||
assert!(format!("{}", err).contains("Proxy group not found"));
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user