#! /usr/bin/env bash # Copyright Gerhard Rieger and contributors (see file CHANGES) # Published under the GNU General Public License V.2, see file COPYING # Shell script to build a chain of Socat instances connected via TCP sockets. # This allows to drive, e.g., PROXY-CONNECT over SSL, or SSL over serial. # Currently only a chain made from 3 addresses, resulting in two instances, is # implemented. # The 2nd address must be one of OPENSSL (SSL), PROXY-CONNECT (PROXY), # SOCKS4, SOCKS4A, SOCKS5 # This is beta! # Examples: # Drive HTTP CONNECT (PROXY) over SSL # (establish an SSL tunnel to a proxy server, request being forwarded to a # telnet server): # socat-chain.sh \ # STDIO \ # PROXY:::23 \ # OPENSSL::8443 # Accept connections that arrive on port 7777, encrypt the data, and send it # via socks server to final target: # socat-chain.sh \ # TCP-L:7777,reuseaddr,fork \ # OPENSSL,verify=0 \ # SOCKS4:::8443 # Receive SSL coming from a serial lie # socat-chain.sh \ # /dev/ttyS0,cfmakeraw \ # SSL-L,cafile=server.pem,verify=0 \ # TCP4:localhost:80 # Formally, this is what happens: # socat-chain.sh addr1 addr2 addr3 # results in something like: # socat TCP-L:RANDOM addr3 & # socat addr1 addr2:localhost:RANDOM # or on passive/listening addr2: # socat addr2:RANDOM addr3 & # socat addr1 TCP:localhost:RANDOM ECHO="echo -e" usage () { $ECHO "Usage: $0 " $ECHO " is typically a passive (listening) address like" $ECHO " TCP-L:1234" $ECHO " must be one of OPENSSL, PROXY, SOCK4, SOCKS4A, or SOCKS5," $ECHO " or SSL-L (passive/listening)" $ECHO " Given server hostname and port are ignored and replaced by internal" $ECHO " communication point" $ECHO " is typically a client address with protocol like OPENSSL" $ECHO " :" $ECHO " -d* -S -t -T are passed to socat" $ECHO " -V prints the socat commands before starting them" $ECHO "Example to drive SOCKS over TLS:" $ECHO " $0 \\" $ECHO " TCP4-L:1234,reuseaddr,fork \\" $ECHO " SOCKS::: \\" $ECHO " OPENSSL:10.2.3.4:12345,cafile=..." $ECHO " Clients that connect to port 1234 will be forwarded to : using socks" $ECHO " over TLS" } LOCALHOST=127.0.0.1 VERBOSE= QUIET= OPTS= while [ "$1" ]; do case "X$1" in X-h) usage; exit ;; X-V) VERBOSE=1 ;; X-q) QUIET=1; OPTS="-d0" ;; X-d*|X-l?*) OPTS="$OPTS $1" ;; X-b|X-S|X-t|X-T|X-l) OPT=$1; shift; OPTS="$OPTS $OPT $1" ;; X-) break ;; X-*) echo "$0: Unknown option \"$1\"" >&2 usage >&2 exit 1 ;; *) break ;; esac shift done ARG0="$1" ARG1="$2" ARG2="$3" if [ -z "$ARG0" -o -z "$ARG1" -o -z "$ARG2" ]; then echo "$0: Three addresses required" >&2 usage >&2 exit 1 fi mkprogname () { ARG="$1" if [[ "$ARG" =~ .*[:].* ]]; then NAME="${ARG%%:*}" elif [[ "$ARG" =~ .*[,].* ]]; then NAME="${ARG%%,*}" elif [ "X$ARG" = X- ]; then NAME=stdio else NAME="$ARG" fi NAME="${NAME,,*}" echo $NAME } # You may place a fork option in the first address # in which case the following internal listeners do fork too FORK= case "$ARG0" in *,fork,*|*,fork) FORK=fork ;; esac # Split middle address for insertion of additional parts if [[ "$ARG1" =~ .*,.* ]]; then ARG1A="${ARG1%%,*}" ARG1B="${ARG1#*,}" else ARG1A="$ARG1" ARG1B= fi case "$0" in */*) SOCAT=${0%/*}/socat ;; *) SOCAT=socat ;; esac PORT=$($SOCAT -d -d TCP4-L:0,accept-timeout=0.000001 /dev/null 2>&1 |grep listening |sed 's/.*:\([1-9][0-9]*\)$/\1/') if [ -z "$PORT" ]; then echo "$0: Failed to determine free TCP port" >&2 exit 1 fi PASSIVE= # is the second address passive/listening/server? case "${ARG1A^^*}" in OPENSSL|OPENSSL:*|SSL|SSL:.*) OPTS1A= #if [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^,]*\)\(.*\) ]]; then # bash 3 if [[ $ARG1A =~ ^([^:]*):([^:]*):([^,]*)(.*) ]]; then OPTS1A="${BASH_REMATCH[4]}" #elif [[ $ARG1A =~ ^\([^,]*\)\(.*\) ]]; then # bash 3 elif [[ $ARG1A =~ ^([^,]*)(.*) ]]; then OPTS1A="${BASH_REMATCH[2]}" else echo "$0: \"$ARG1A\": invalid arguments" >&2 exit 1 fi PROG1="${BASH_REMATCH[1]}" NAME1=$(mkprogname "${BASH_REMATCH[1]}") NAME2=$(mkprogname "$ARG2") ARG1A=$PROG1:$LOCALHOST:$PORT$OPTS1A ;; PROXY-CONNECT:*|PROXY:*) #if ! [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^:]*\):\([^,]*\)\(.*\) ]]; then # bash 3 if ! [[ $ARG1A =~ ^([^:]*):([^:]*):([^:]*):([^,]*)(.*) ]]; then echo "$0: \"$ARG1A\": invalid arguments" >&2 exit 1 fi #echo "0:\"${BASH_REMATCH[0]}\" 1:\"${BASH_REMATCH[1]}\" 2:\"${BASH_REMATCH[2]}\" 3:\"${BASH_REMATCH[3]}\" 4:\"${BASH_REMATCH[4]}\"" PROG1="${BASH_REMATCH[1]}" NAME1=$(mkprogname "${PROG1,,*}") NAME2=$(mkprogname "$ARG2") OPTS1A="${BASH_REMATCH[5]}" ARG1A="$PROG1:$LOCALHOST:${BASH_REMATCH[3]}:${BASH_REMATCH[4]},proxyport=$PORT,$OPTS1A" ;; SOCKS:*|SOCKS4:*|SOCKS4A*) #if ! [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^:]*\):\([^:,]*\),* ]]; then # bash 3 if ! [[ $ARG1A =~ ^([^:]*):([^:]*):([^:]*):([^:,]*),* ]]; then echo "$0: \"$ARG1A\": invalid arguments" >&2 exit 1 fi PROG1="${BASH_REMATCH[1]}" NAME1=$(mkprogname "${PROG1,,*}") NAME2=$(mkprogname "$ARG2") OPTS1A="${BASH_REMATCH[5]}" ARG1A="$PROG1:$LOCALHOST:${BASH_REMATCH[3]}:${BASH_REMATCH[4]},socksport=$PORT,$OPTS1A" ;; SOCKS5:*|SOCKS5-CONNECT*) #if ! [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^:]*\):\([^:,]*\):\([^:,]*\),* ]]; then # bash 3 if ! [[ $ARG1A =~ ^([^:]*):([^:]*):([^:]*):([^:,]*):([^:,]*),* ]]; then echo "$0: \"$ARG1A\": invalid arguments" >&2 exit 1 fi PROG1="${BASH_REMATCH[1]}" NAME1=$(mkprogname "${PROG1,,*}") NAME2=$(mkprogname "$ARG2") OPTS1A="${BASH_REMATCH[6]}" ARG1A="$PROG1:$LOCALHOST:$PORT:${BASH_REMATCH[4]}:${BASH_REMATCH[5]},$OPTS1A" ;; # Passive (server) addresses OPENSSL-LISTEN|OPENSSL-LISTEN:*|SSL-L|SSL-L:.*) PASSIVE=1 OPTS1A= #if [[ $ARG1A =~ ^\([^:]*\):\([^,]*\)\(.*\) ]]; then # bash 3 if [[ $ARG1A =~ ^([^:]*):([^,]*)(.*) ]]; then OPTS1A="${BASH_REMATCH[3]}" #elif [[ $ARG1A =~ ^\([^,]*\)\(.*\) ]]; then # bash 3 elif [[ $ARG1A =~ ^([^,]*)(.*) ]]; then OPTS1A="${BASH_REMATCH[2]}" else echo "$0: \"$ARG1A\": invalid arguments" >&2 exit 1 fi PROG1="${BASH_REMATCH[1]}" NAME1=$(mkprogname "$ARG0") NAME2=$(mkprogname "${BASH_REMATCH[1]}") ARG1A=$PROG1:$PORT$OPTS1A ;; *) echo "$0: Unsupported address \"$ARG1A\"" >&2 usage >&2 exit 1 ;; esac ADDR1A="$ARG0" if [ -z "$PASSIVE" ]; then ADDR1B="$ARG1A,bind=$LOCALHOST,$ARG1B" ADDR2A="TCP4-L:$PORT,reuseaddr,$FORK,bind=$LOCALHOST,range=$LOCALHOST/32" else ADDR1B="TCP4:$LOCALHOST:$PORT,bind=$LOCALHOST" ADDR2A="$ARG1A,reuseaddr,$FORK,bind=$LOCALHOST,range=$LOCALHOST/32,$ARG1B" fi ADDR2B="$ARG2" pid1= pid2= trap '[ "$pid1" ] && kill $pid1 2>/dev/null; [ "$pid2" ] && kill $pid2 2>/dev/null' EXIT set -bm trap 'rc=$?; if ! kill -n 0 $pid2 2>/dev/null; then [ -z "$QUIET" -a $rc -ne 0 ] && echo "$0: socat-$NAME2 exited with rc=$rc" >&2; exit $rc; fi' SIGCHLD # Start instance 2 first, because instance 1 ("left") connects to 2 if [ "$VERBOSE" ]; then $ECHO "$SOCAT $OPTS -lp socat-$NAME2 \\ \"$ADDR2A\" \\ \"$ADDR2B\" &" fi $SOCAT $OPTS -lp socat-$NAME2 \ "$ADDR2A" \ "$ADDR2B" & pid2=$! sleep 0.1 #trap 'if ! kill -n 0 $pid1 2>/dev/null; then [ -z "$QUIET" ] && echo "$0: socat-$NAME1 exited with rc=$?" >&2; kill $pid2 2>/dev/null; exit 1; elif ! kill -n 0 $pid2 2>/dev/null; then [ -z "$QUIET" ] && echo "$0: socat-$NAME2 exited with rc=$?" >&2; kill $pid1 2>/dev/null; exit 1; fi' SIGCHLD if [ "$VERBOSE" ]; then $ECHO "$SOCAT $OPTS -lp socat-$NAME1 \\ \"$ADDR1A\" \\ \"$ADDR1B\"" fi $SOCAT $OPTS -lp socat-$NAME1 \ "$ADDR1A" \ "$ADDR1B" #pid1=$! rc1=$? kill $pid2 2>/dev/null wait 2>/dev/null #wait -f exit $rc1