diff --git a/Makefile b/Makefile index 11d0df8..f66f3d1 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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)/%) diff --git a/src/common.h b/src/common.h index 91468bc..b079d90 100644 --- a/src/common.h +++ b/src/common.h @@ -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 diff --git a/src/libproxychains.c b/src/libproxychains.c index 40d8a11..e30216d 100644 --- a/src/libproxychains.c +++ b/src/libproxychains.c @@ -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; diff --git a/src/main.c b/src/main.c index 381c71c..ddb3979 100644 --- a/src/main.c +++ b/src/main.c @@ -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); diff --git a/src/proxychains4-cli b/src/proxychains4-cli new file mode 100755 index 000000000..b3e2159 --- /dev/null +++ b/src/proxychains4-cli @@ -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 < 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} "$@"