Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ SHARED_LIBS = $(LDSO_PATHNAME)
ALL_LIBS = $(SHARED_LIBS)
PXCHAINS = proxychains4
PXCHAINS_D = proxychains4-daemon
PXCHAINS_CLI = src/proxychains4-cli
ALL_TOOLS = $(PXCHAINS) $(PXCHAINS_D)
ALL_CONFIGS = src/proxychains.conf
ZSH_COMPLETION = completions/zsh/_proxychains4
Expand All @@ -73,8 +74,11 @@ $(DESTDIR)$(sysconfdir)/%: src/%
$(DESTDIR)$(zshcompletiondir)/%: completions/zsh/%
$(INSTALL) -D -m 644 $< $@

$(DESTDIR)$(bindir)/proxychains4-cli: $(PXCHAINS_CLI)
$(INSTALL) -D -m 755 $< $@

install-libs: $(ALL_LIBS:%=$(DESTDIR)$(libdir)/%)
install-tools: $(ALL_TOOLS:%=$(DESTDIR)$(bindir)/%)
install-tools: $(ALL_TOOLS:%=$(DESTDIR)$(bindir)/%) $(DESTDIR)$(bindir)/proxychains4-cli
install-config: $(ALL_CONFIGS:src/%=$(DESTDIR)$(sysconfdir)/%)
install-zsh-completion: $(ZSH_COMPLETION:completions/zsh/%=$(DESTDIR)$(zshcompletiondir)/%)

Expand Down
1 change: 1 addition & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#define PROXYCHAINS_CONF_FILE_ENV_VAR "PROXYCHAINS_CONF_FILE"
#define PROXYCHAINS_QUIET_MODE_ENV_VAR "PROXYCHAINS_QUIET_MODE"
#define PROXYCHAINS_CONFIG_DATA_ENV_VAR "PROXYCHAINS_CONFIG_DATA"
#define PROXYCHAINS_CONF_FILE "proxychains.conf"
#define LOG_PREFIX "[proxychains] "
#ifndef SYSCONFDIR
Expand Down
24 changes: 18 additions & 6 deletions src/libproxychains.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,24 @@ static void get_chain_data(proxy_data * pd, unsigned int *proxy_count, chain_typ
tcp_connect_time_out = 10 * 1000;
*ct = DYNAMIC_TYPE;

env = get_config_path(getenv(PROXYCHAINS_CONF_FILE_ENV_VAR), buf, sizeof(buf));
if( ( file = fopen(env, "r") ) == NULL )
{
perror("couldnt read configuration file");
exit(1);
}
/* Try to get config from environment variable first */
char *config_data = getenv(PROXYCHAINS_CONFIG_DATA_ENV_VAR);
if (config_data && *config_data) {
/* Config data in env - use fmemopen to read it */
file = fmemopen(config_data, strlen(config_data), "r");
if (!file) {
fprintf(stderr, LOG_PREFIX "failed to open config from environment\n");
exit(1);
}
}
if (!file) {
env = get_config_path(getenv(PROXYCHAINS_CONF_FILE_ENV_VAR), buf, sizeof(buf));
file = fopen(env, "r");
}
if (!file) {
perror("couldnt read configuration file");
exit(1);
}

while(fgets(buf, sizeof(buf), file)) {
buff = buf;
Expand Down
17 changes: 11 additions & 6 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,19 @@ int main(int argc, char *argv[]) {
if(start_argv >= argc)
return usage(argv);

/* check if path of config file has not been passed via command line */
path = get_config_path(path, pbuf, sizeof(pbuf));
/* Handle config: check if config data is already in env, otherwise read from file */
char *config_data = getenv(PROXYCHAINS_CONFIG_DATA_ENV_VAR);

if(!quiet)
fprintf(stderr, LOG_PREFIX "config file found: %s\n", path);
if (!config_data || !*config_data) {
/* No config data in env - read from file */
path = get_config_path(path, pbuf, sizeof(pbuf));

if(!quiet)
fprintf(stderr, LOG_PREFIX "config file found: %s\n", path);

/* Set PROXYCHAINS_CONF_FILE to get proxychains lib to use new config file. */
setenv(PROXYCHAINS_CONF_FILE_ENV_VAR, path, 1);
/* Set PROXYCHAINS_CONF_FILE to get proxychains lib to use new config file. */
setenv(PROXYCHAINS_CONF_FILE_ENV_VAR, path, 1);
}

if(quiet)
setenv(PROXYCHAINS_QUIET_MODE_ENV_VAR, "1", 1);
Expand Down
248 changes: 248 additions & 0 deletions src/proxychains4-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#!/bin/sh
# proxychains4-cli - Dynamic config generation for proxychains4

# Static variables
# sync with proxychains4 version
PROXYCHAINS_CLI_VERSION="4.17"

# Defaults
chain_mode="strict"
chain_len=""
quiet=""
dns_mode="proxy"
dns_subnet="224"
read_timeout="15000"
connect_timeout="8000"
localnets=""
dnats=""
proxies=""
proxychains_path="proxychains4"
show_config=""

show_help() {
cat <<EOF
Usage: proxychains4-cli [options] program [args...]

Chain Options:
-c, --chain MODE Chain mode (strict/dynamic/random/round_robin)
-l, --chain-len N Chain length for random/round_robin

Output:
-q, --quiet Quiet mode

DNS Options:
-d, --dns MODE DNS mode (proxy/old/off/IP:PORT)
-S, --dns-subnet N Remote DNS subnet (0-255)

Timeouts:
-R, --read-timeout MS TCP read timeout in milliseconds
-T, --connect-timeout MS TCP connect timeout in milliseconds

Network:
-n, --localnet CIDR Bypass proxy for CIDR (repeatable)
--dnat "SRC DST" DNAT mapping (repeatable, quote required)

Proxy:
-P, --proxy URL Add proxy to chain (repeatable)
Format: protocol://[user:pass@]host:port
Protocols: http, socks4, socks5, raw
IPv6: socks5://[::1]:1080

Debug:
--config Print generated config and exit
--proxychains-path PATH Path to proxychains4 (default: proxychains4 in PATH)

Other:
-h, --help Show this help
-v, --version Show version

Examples:
proxychains4-cli -P socks5://127.0.0.1:1080 curl example.com
proxychains4-cli -P socks5://user:[email protected]:1080 wget file.tar.gz
proxychains4-cli -P "socks5://[::1]:1080" curl -v example.com
# Special chars in password: use URL encoding (%40=@ %3A=: %25=%)
proxychains4-cli -P 'socks5://user:p%40ss%[email protected]:1080' curl example.com
EOF
}

show_version() {
echo "proxychains4-cli ${PROXYCHAINS_CLI_VERSION}"
}

# URL decode: %XX -> char
url_decode() {
printf '%b' "$(echo "$1" | sed 's/+/ /g; s/%\([0-9A-Fa-f][0-9A-Fa-f]\)/\\x\1/g')"
}

# Parse proxy URL: protocol://[user:pass@]host:port
# Output: protocol\thost\tport[\tuser\tpass]
# Supports URL-encoded special chars in user/pass
parse_proxy_url() {
url="$1"

# Extract protocol
proto="${url%%://*}"
rest="${url#*://}"

# Validate protocol
case "$proto" in
http|socks4|socks5|raw) ;;
*) echo "Error: Invalid proxy protocol '$proto'. Use http/socks4/socks5/raw" >&2; return 1 ;;
esac

# Check for auth (user:pass@)
if echo "$rest" | grep -q '@'; then
auth="${rest%%@*}"
hostport="${rest#*@}"
user=$(url_decode "${auth%%:*}")
pass=$(url_decode "${auth#*:}")
else
hostport="$rest"
user=""
pass=""
fi

# Handle IPv6: [::1]:port
if echo "$hostport" | grep -q '^\['; then
# IPv6 format: [addr]:port
host=$(echo "$hostport" | sed 's/^\[\([^]]*\)\].*/\1/')
port=$(echo "$hostport" | sed 's/.*\]://')
else
# IPv4 or hostname: addr:port
host="${hostport%%:*}"
port="${hostport##*:}"
fi

# Validate port
if [ -z "$port" ] || ! echo "$port" | grep -qE '^[0-9]+$'; then
echo "Error: Invalid or missing port in '$url'" >&2
return 1
fi

# Output in proxychains.conf format
if [ -n "$user" ]; then
printf '%s\t%s\t%s\t%s\t%s\n' "$proto" "$host" "$port" "$user" "$pass"
else
printf '%s\t%s\t%s\n' "$proto" "$host" "$port"
fi
}

# Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
-c|--chain)
chain_mode="$2"
shift 2
;;
-l|--chain-len)
chain_len="$2"
shift 2
;;
-q|--quiet)
quiet=1
shift
;;
-d|--dns)
dns_mode="$2"
shift 2
;;
-S|--dns-subnet)
dns_subnet="$2"
shift 2
;;
-R|--read-timeout)
read_timeout="$2"
shift 2
;;
-T|--connect-timeout)
connect_timeout="$2"
shift 2
;;
-n|--localnet)
localnets="${localnets}${2}
"
shift 2
;;
--dnat)
dnats="${dnats}${2}
"
shift 2
;;
-P|--proxy)
entry=$(parse_proxy_url "$2") || exit 1
proxies="${proxies}${entry}
"
shift 2
;;
-h|--help)
show_help
exit 0
;;
-v|--version)
show_version
exit 0
;;
--config)
show_config=1
shift
;;
--proxychains-path)
proxychains_path="$2"
shift 2
;;
-*)
echo "Unknown option: $1" >&2
echo "Use -h for help" >&2
exit 1
;;
*)
# First non-option is program, stop parsing
break
;;
esac
done

# Check if program specified (unless --config)
if [ $# -eq 0 ] && [ -z "$show_config" ]; then
echo "Error: No program specified" >&2
echo "Use -h for help" >&2
exit 1
fi

# Check if at least one proxy specified
if [ -z "$proxies" ]; then
echo "Error: No proxy specified. Use -P to add proxy." >&2
exit 1
fi

# Build DNS config line
dns_config=""
case "$dns_mode" in
proxy) dns_config="proxy_dns";;
old) dns_config="proxy_dns_old";;
off|none|"") ;;
*) dns_config="proxy_dns_daemon $dns_mode";;
esac

# Generate config content
config_content="${chain_mode}_chain
${chain_len:+chain_len = $chain_len}
${quiet:+quiet_mode}
${dns_config}
${dns_subnet:+remote_dns_subnet $dns_subnet}
${read_timeout:+tcp_read_time_out $read_timeout}
${connect_timeout:+tcp_connect_time_out $connect_timeout}
$(printf '%s' "$localnets" | while IFS= read -r net; do [ -n "$net" ] && echo "localnet $net"; done)
$(printf '%s' "$dnats" | while IFS= read -r m; do [ -n "$m" ] && echo "dnat $m"; done)
[ProxyList]
$(printf '%s' "$proxies")"

# If --config, print and exit
if [ -n "$show_config" ]; then
printf '%s\n' "$config_content"
exit 0
fi

# Execute with config via environment variable
export PROXYCHAINS_CONFIG_DATA="$config_content"
exec "$proxychains_path" ${quiet:+-q} "$@"