Version 1.8.0.0
[socat.git] / socat-chain.sh
blob865446d5b3fb093f9ddf36039b41ceedaea926ae
1 #! /usr/bin/env bash
2 # Copyright Gerhard Rieger and contributors (see file CHANGES)
3 # Published under the GNU General Public License V.2, see file COPYING
5 # Shell script to build a chain of Socat instances connected via TCP sockets.
6 # This allows to drive, e.g., PROXY-CONNECT over SSL, or SSL over serial.
7 # Currently only a chain made from 3 addresses, resulting in two instances, is
8 # implemented.
9 # The 2nd address must be one of OPENSSL (SSL), PROXY-CONNECT (PROXY),
10 # SOCKS4, SOCKS4A, SOCKS5
12 # This is beta!
14 # Examples:
16 # Drive HTTP CONNECT (PROXY) over SSL
17 # (establish an SSL tunnel to a proxy server, request being forwarded to a
18 # telnet server):
19 # socat-chain.sh \
20 # STDIO \
21 # PROXY::<telnet-server>:23 \
22 # OPENSSL:<proxy-server>:8443
24 # Accept connections that arrive on port 7777, encrypt the data, and send it
25 # via socks server to final target:
26 # socat-chain.sh \
27 # TCP-L:7777,reuseaddr,fork \
28 # OPENSSL,verify=0 \
29 # SOCKS4:<socks-server>:<ssl-server>:8443
31 # Receive SSL coming from a serial lie
32 # socat-chain.sh \
33 # /dev/ttyS0,cfmakeraw \
34 # SSL-L,cafile=server.pem,verify=0 \
35 # TCP4:localhost:80
37 # Formally, this is what happens:
38 # socat-chain.sh addr1 addr2 addr3
39 # results in something like:
40 # socat TCP-L:RANDOM addr3 &
41 # socat addr1 addr2:localhost:RANDOM
42 # or on passive/listening addr2:
43 # socat addr2:RANDOM addr3 &
44 # socat addr1 TCP:localhost:RANDOM
46 ECHO="echo -e"
48 usage () {
49 $ECHO "Usage: $0 <options> <address1> <address2> <address3>"
50 $ECHO " <address1> is typically a passive (listening) address like"
51 $ECHO " TCP-L:1234"
52 $ECHO " <address2> must be one of OPENSSL, PROXY, SOCK4, SOCKS4A, or SOCKS5,"
53 $ECHO " or SSL-L (passive/listening)"
54 $ECHO " Given server hostname and port are ignored and replaced by internal"
55 $ECHO " communication point"
56 $ECHO " <address3> is typically a client address with protocol like OPENSSL"
57 $ECHO " <options>:"
58 $ECHO " -d* -S <sigmask> -t <timeout> -T <timeout> are passed to socat"
59 $ECHO " -V prints the socat commands before starting them"
60 $ECHO "Example to drive SOCKS over TLS:"
61 $ECHO " $0 \\"
62 $ECHO " TCP4-L:1234,reuseaddr,fork \\"
63 $ECHO " SOCKS::<server>:<port> \\"
64 $ECHO " OPENSSL:10.2.3.4:12345,cafile=..."
65 $ECHO " Clients that connect to port 1234 will be forwarded to <server>:<port> using socks"
66 $ECHO " over TLS"
70 LOCALHOST=127.0.0.1
72 VERBOSE= QUIET= OPTS=
73 while [ "$1" ]; do
74 case "X$1" in
75 X-h) usage; exit ;;
76 X-V) VERBOSE=1 ;;
77 X-q) QUIET=1; OPTS="-d0" ;;
78 X-d*|X-l?*) OPTS="$OPTS $1" ;;
79 X-b|X-S|X-t|X-T|X-l) OPT=$1; shift; OPTS="$OPTS $OPT $1" ;;
80 X-) break ;;
81 X-*) echo "$0: Unknown option \"$1\"" >&2
82 usage >&2
83 exit 1 ;;
84 *) break ;;
85 esac
86 shift
87 done
89 ARG0="$1"
90 ARG1="$2"
91 ARG2="$3"
93 if [ -z "$ARG0" -o -z "$ARG1" -o -z "$ARG2" ]; then
94 echo "$0: Three addresses required" >&2
95 usage >&2
96 exit 1
100 mkprogname () {
101 ARG="$1"
102 if [[ "$ARG" =~ .*[:].* ]]; then
103 NAME="${ARG%%:*}"
104 elif [[ "$ARG" =~ .*[,].* ]]; then
105 NAME="${ARG%%,*}"
106 elif [ "X$ARG" = X- ]; then
107 NAME=stdio
108 else
109 NAME="$ARG"
111 NAME="${NAME,,*}"
112 echo $NAME
116 # You may place a fork option in the first address
117 # in which case the following internal listeners do fork too
118 FORK=
119 case "$ARG0" in
120 *,fork,*|*,fork) FORK=fork ;;
121 esac
123 # Split middle address for insertion of additional parts
124 if [[ "$ARG1" =~ .*,.* ]]; then
125 ARG1A="${ARG1%%,*}"
126 ARG1B="${ARG1#*,}"
127 else
128 ARG1A="$ARG1"
129 ARG1B=
132 case "$0" in
133 */*) SOCAT=${0%/*}/socat ;;
134 *) SOCAT=socat ;;
135 esac
137 PORT=$($SOCAT -d -d TCP4-L:0,accept-timeout=0.000001 /dev/null 2>&1 |grep listening |sed 's/.*:\([1-9][0-9]*\)$/\1/')
138 if [ -z "$PORT" ]; then
139 echo "$0: Failed to determine free TCP port" >&2
140 exit 1
143 PASSIVE= # is the second address passive/listening/server?
144 case "${ARG1A^^*}" in
145 OPENSSL|OPENSSL:*|SSL|SSL:.*)
146 OPTS1A=
147 #if [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^,]*\)\(.*\) ]]; then # bash 3
148 if [[ $ARG1A =~ ^([^:]*):([^:]*):([^,]*)(.*) ]]; then
149 OPTS1A="${BASH_REMATCH[4]}"
150 #elif [[ $ARG1A =~ ^\([^,]*\)\(.*\) ]]; then # bash 3
151 elif [[ $ARG1A =~ ^([^,]*)(.*) ]]; then
152 OPTS1A="${BASH_REMATCH[2]}"
153 else
154 echo "$0: \"$ARG1A\": invalid arguments" >&2
155 exit 1
157 PROG1="${BASH_REMATCH[1]}"
158 NAME1=$(mkprogname "${BASH_REMATCH[1]}")
159 NAME2=$(mkprogname "$ARG2")
160 ARG1A=$PROG1:$LOCALHOST:$PORT$OPTS1A ;;
161 PROXY-CONNECT:*|PROXY:*)
162 #if ! [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^:]*\):\([^,]*\)\(.*\) ]]; then # bash 3
163 if ! [[ $ARG1A =~ ^([^:]*):([^:]*):([^:]*):([^,]*)(.*) ]]; then
164 echo "$0: \"$ARG1A\": invalid arguments" >&2
165 exit 1
167 #echo "0:\"${BASH_REMATCH[0]}\" 1:\"${BASH_REMATCH[1]}\" 2:\"${BASH_REMATCH[2]}\" 3:\"${BASH_REMATCH[3]}\" 4:\"${BASH_REMATCH[4]}\""
168 PROG1="${BASH_REMATCH[1]}"
169 NAME1=$(mkprogname "${PROG1,,*}")
170 NAME2=$(mkprogname "$ARG2")
171 OPTS1A="${BASH_REMATCH[5]}"
172 ARG1A="$PROG1:$LOCALHOST:${BASH_REMATCH[3]}:${BASH_REMATCH[4]},proxyport=$PORT,$OPTS1A" ;;
173 SOCKS:*|SOCKS4:*|SOCKS4A*)
174 #if ! [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^:]*\):\([^:,]*\),* ]]; then # bash 3
175 if ! [[ $ARG1A =~ ^([^:]*):([^:]*):([^:]*):([^:,]*),* ]]; then
176 echo "$0: \"$ARG1A\": invalid arguments" >&2
177 exit 1
179 PROG1="${BASH_REMATCH[1]}"
180 NAME1=$(mkprogname "${PROG1,,*}")
181 NAME2=$(mkprogname "$ARG2")
182 OPTS1A="${BASH_REMATCH[5]}"
183 ARG1A="$PROG1:$LOCALHOST:${BASH_REMATCH[3]}:${BASH_REMATCH[4]},socksport=$PORT,$OPTS1A" ;;
184 SOCKS5:*|SOCKS5-CONNECT*)
185 #if ! [[ $ARG1A =~ ^\([^:]*\):\([^:]*\):\([^:]*\):\([^:,]*\):\([^:,]*\),* ]]; then # bash 3
186 if ! [[ $ARG1A =~ ^([^:]*):([^:]*):([^:]*):([^:,]*):([^:,]*),* ]]; then
187 echo "$0: \"$ARG1A\": invalid arguments" >&2
188 exit 1
190 PROG1="${BASH_REMATCH[1]}"
191 NAME1=$(mkprogname "${PROG1,,*}")
192 NAME2=$(mkprogname "$ARG2")
193 OPTS1A="${BASH_REMATCH[6]}"
194 ARG1A="$PROG1:$LOCALHOST:$PORT:${BASH_REMATCH[4]}:${BASH_REMATCH[5]},$OPTS1A" ;;
195 # Passive (server) addresses
196 OPENSSL-LISTEN|OPENSSL-LISTEN:*|SSL-L|SSL-L:.*)
197 PASSIVE=1
198 OPTS1A=
199 #if [[ $ARG1A =~ ^\([^:]*\):\([^,]*\)\(.*\) ]]; then # bash 3
200 if [[ $ARG1A =~ ^([^:]*):([^,]*)(.*) ]]; then
201 OPTS1A="${BASH_REMATCH[3]}"
202 #elif [[ $ARG1A =~ ^\([^,]*\)\(.*\) ]]; then # bash 3
203 elif [[ $ARG1A =~ ^([^,]*)(.*) ]]; then
204 OPTS1A="${BASH_REMATCH[2]}"
205 else
206 echo "$0: \"$ARG1A\": invalid arguments" >&2
207 exit 1
209 PROG1="${BASH_REMATCH[1]}"
210 NAME1=$(mkprogname "$ARG0")
211 NAME2=$(mkprogname "${BASH_REMATCH[1]}")
212 ARG1A=$PROG1:$PORT$OPTS1A ;;
213 *) echo "$0: Unsupported address \"$ARG1A\"" >&2
214 usage >&2
215 exit 1 ;;
216 esac
218 ADDR1A="$ARG0"
219 if [ -z "$PASSIVE" ]; then
220 ADDR1B="$ARG1A,bind=$LOCALHOST,$ARG1B"
221 ADDR2A="TCP4-L:$PORT,reuseaddr,$FORK,bind=$LOCALHOST,range=$LOCALHOST/32"
222 else
223 ADDR1B="TCP4:$LOCALHOST:$PORT,bind=$LOCALHOST"
224 ADDR2A="$ARG1A,reuseaddr,$FORK,bind=$LOCALHOST,range=$LOCALHOST/32,$ARG1B"
226 ADDR2B="$ARG2"
229 pid1= pid2=
230 trap '[ "$pid1" ] && kill $pid1 2>/dev/null; [ "$pid2" ] && kill $pid2 2>/dev/null' EXIT
232 set -bm
233 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
235 # Start instance 2 first, because instance 1 ("left") connects to 2
236 if [ "$VERBOSE" ]; then
237 $ECHO "$SOCAT $OPTS -lp socat-$NAME2 \\
238 \"$ADDR2A\" \\
239 \"$ADDR2B\" &"
241 $SOCAT $OPTS -lp socat-$NAME2 \
242 "$ADDR2A" \
243 "$ADDR2B" &
244 pid2=$!
245 sleep 0.1
247 #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
249 if [ "$VERBOSE" ]; then
250 $ECHO "$SOCAT $OPTS -lp socat-$NAME1 \\
251 \"$ADDR1A\" \\
252 \"$ADDR1B\""
254 $SOCAT $OPTS -lp socat-$NAME1 \
255 "$ADDR1A" \
256 "$ADDR1B"
257 #pid1=$!
258 rc1=$?
260 kill $pid2 2>/dev/null
261 wait 2>/dev/null
262 #wait -f
264 exit $rc1