diff --git a/CHANGES b/CHANGES index 274b8e6..40d247a 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,11 @@ Testing: Fixed a few testing issues. + Added test script sock5server-echo.sh for SOCKS5-CONNECT and + SOCKS5-LISTEN, and appropriate tests. + SOCKS5 addresses are no longer experimental. + Tests: SOCKS5CONNECT_TCP4 SOCKS5LISTEN_TCP4 + ####################### V 1.8.0.2: Security: diff --git a/Makefile.in b/Makefile.in index 631d31d..6187393 100644 --- a/Makefile.in +++ b/Makefile.in @@ -80,7 +80,7 @@ SHFILES = socat-chain.sh socat-mux.sh socat-broker.sh \ daemon.sh mail.sh ftp.sh readline.sh \ socat_buildscript_for_android.sh TESTFILES = test.sh socks4echo.sh proxyecho.sh readline-test.sh \ - proxy.sh socks4a-echo.sh + proxy.sh socks4a-echo.sh socks5server-echo.sh all: progs doc diff --git a/doc/socat.yo b/doc/socat.yo index 2574a3f..747f915 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -1108,7 +1108,6 @@ dit(bf(tt(SOCKS5-CONNECT:<socks-server>:<target-host>:<target-port>))) to <target-host> [link(IPv4 address)(TYPE_IPV4_ADDRESS)] on <target-port> [link(TCP service)(TYPE_TCP_SERVICE)], using socks version 5 protocol over TCP. Currently no authentication mechanism is provided.nl() - This address type is experimental.nl() Option groups: link(FD)(GROUP_FD), link(SOCKET)(GROUP_SOCKET), link(IP4)(GROUP_IP4), link(IP6)(GROUP_IP6), link(TCP)(GROUP_TCP), link(CHILD)(GROUP_CHILD), link(RETRY)(GROUP_RETRY)nl() Useful options: link(socksport)(OPTION_SOCKSPORT), @@ -1127,7 +1126,7 @@ dit(bf(tt(SOCKS5-LISTEN:<socks-server>:<listen-host>:<listen-port>))) Connects to <socks-server> [link(IP address)(TYPE_IP_ADDRESS)] using socks version 5 protocol over TCP and makes it listen for incoming connections on <listen-port> [link(TCP service)(TYPE_TCP_SERVICE)], binding to <-listen-host> [link(IPv4 address)(TYPE_IPV4_ADDRESS)] - Currently not authentication mechanism is provided. This address type is experimental. + Currently not authentication mechanism is provided.nl() Option groups: link(FD)(GROUP_FD), link(SOCKET)(GROUP_SOCKET), link(IP4)(GROUP_IP4), link(IP6)(GROUP_IP6), link(TCP)(GROUP_TCP), link(CHILD)(GROUP_CHILD), link(RETRY)(GROUP_RETRY)nl() Useful options: link(sourceport)(OPTION_SOURCEPORT), diff --git a/socks4a-echo.sh b/socks4a-echo.sh index 7360366..91d255a 100755 --- a/socks4a-echo.sh +++ b/socks4a-echo.sh @@ -37,7 +37,7 @@ esac if [ $(echo "x\c") = "x" ]; then E="" elif [ $(echo -e "x\c") = "x" ]; then E="-e" else - echo "cannot suppress trailing newline on echo" >&2 + echo "$0: cannot suppress trailing newline on echo" >&2 exit 1 fi ECHO="echo $E" @@ -58,7 +58,7 @@ else fi if [ "$vn" != $($ECHO "\04") ]; then $ECHO "$SOCKSREPLY_FAILED" - echo "invalid socks version requested" >&2 + echo "$0 invalid socks version requested" >&2 exit fi @@ -69,7 +69,7 @@ else fi if [ "$cd" != $($ECHO "\01") ]; then $ECHO "$SOCKSREPLY_FAILED" - echo "invalid socks operation requested" >&2 + echo "$0: invalid socks operation requested" >&2 exit fi @@ -82,7 +82,7 @@ a=$(dd bs=1 count=6 2>/dev/null) if [ "$a" != "$($ECHO "}m\0\0\0\01")" ]; then sleep 1 $ECHO "$SOCKSREPLY_FAILED" - echo "wrong socks address or port requested" >&2 + echo "$0: wrong socks address or port requested" >&2 exit fi @@ -93,7 +93,7 @@ else fi if [ "$u" != "nobody" ]; then $ECHO "$SOCKSREPLY_FAILED" - echo "wrong socks user requested" >&2 + echo "$0: wrong socks user requested" >&2 exit fi @@ -104,7 +104,7 @@ else fi if [ "$h" != "localhost" ]; then $ECHO "$SOCKSREPLY_FAILED" - echo "wrong socks address requested" >&2 + echo "$0: wrong socks address requested" >&2 exit fi diff --git a/socks4echo.sh b/socks4echo.sh index 48ea536..a577b20 100755 --- a/socks4echo.sh +++ b/socks4echo.sh @@ -36,7 +36,7 @@ esac if [ $(echo "x\c") = "x" ]; then E="" elif [ $(echo -e "x\c") = "x" ]; then E="-e" else - echo "cannot suppress trailing newline on echo" >&2 + echo "$0: cannot suppress trailing newline on echo" >&2 exit 1 fi ECHO="echo $E" @@ -57,7 +57,7 @@ else fi if [ "$vn" != $($ECHO "\04") ]; then $ECHO "$SOCKSREPLY_FAILED" - echo "invalid socks version requested" >&2 + echo "$0: invalid socks version requested" >&2 exit fi @@ -68,7 +68,7 @@ else fi if [ "$cd" != $($ECHO "\01") ]; then $ECHO "$SOCKSREPLY_FAILED" - echo "invalid socks operation requested" >&2 + echo "$0: invalid socks operation requested" >&2 exit fi @@ -91,7 +91,7 @@ else fi if [ "$u" != "nobody" ]; then $ECHO "$SOCKSREPLY_FAILED" - echo "wrong socks user requested (expected \"nobody\")" >&2 + echo "$0: wrong socks user requested (expected \"nobody\")" >&2 exit fi diff --git a/socks5server-echo.sh b/socks5server-echo.sh new file mode 100755 index 0000000..04e562a --- /dev/null +++ b/socks5server-echo.sh @@ -0,0 +1,67 @@ +#! /usr/bin/env bash +# Source: socks5connect-echo.sh + +# Copyright Gerhard Rieger and contributors (see file CHANGES) +# Published under the GNU General Public License V.2, see file COPYING + +# Performs primitive simulation of a socks5 server with echo function via stdio. +# Accepts and answers SOCKS5 CONNECT request without authentication to +# 8.8.8.8:80, however is does not connect there but just echoes data. +# It is required for test.sh +# For TCP, use this script as: +# socat TCP-L:1080,reuseaddr EXEC:"socks5connect-echo.sh" + +#set -vx + +if [ "$SOCAT" ]; then + : +elif type socat >/dev/null 2>&1; then + SOCAT=socat +else + SOCAT=./socat +fi + +case `uname` in +HP-UX|OSF1) + CAT="$SOCAT -u STDIN STDOUT" + ;; +*) + CAT=cat + ;; +esac + +A="7f000001" +P="0050" + +# Read and parse SOCKS5 greeting +read _ v b c _ <<<"$($SOCAT -u -,readbytes=3 - |od -t x1)" +#echo "$v $b $c" >&2 +if [ "$v" != 05 ]; then echo "$0: Packet1: expected version x05, got \"$v\"" >&2; exit 1; fi +if [ "$b" != 01 ]; then echo "$0: Packet1: expected 01 auth methods, got \"$b\"" >&2; exit 1; fi +if [ "$c" != 00 ]; then echo "$0: Packet1: expected auth method 00, got \"$c\"" >&2; exit 1; fi +# Send answer +echo -en "\x05\x00" + +# Read and parse SOCKS5 connect request +read _ v b c d a1 a2 a3 a4 p1 p2 _ <<<"$($SOCAT -u -,readbytes=10 - |od -t x1)" +#echo "$v $b $c $d $a1 $a2 $a3 $a4 $p1 $p2" >&2 +a="$a1$a2$a3$a4" +p="$p1$p2" +if [ "$v" != 05 ]; then echo "$0: Packet2: expected version x05, got \"$v\"" >&2; exit 1; fi +if [ "$b" != 01 ] && [ "$b" != 02 ]; then echo "$0: Packet2: expected connect request 01 or bind request 02, got \"$b\"" >&2; exit 1; fi +if [ "$c" != 00 ]; then echo "$0: Packet2: expected reserved 00, got \"$c\"" >&2; exit 1; fi +if [ "$d" != 01 ]; then echo "$0: Packet2: expected address type 01, got \"$d\"" >&2; exit 1; fi +if [ "$a" != "$A" ]; then echo "$0: Packet2: expected address $A, got \"$a\"" >&2; exit 1; fi +if [ "$p" != "$P" ]; then echo "$0: Packet2: expected port $P, got \"$p\"" >&2; exit 1; fi +if [ "$z" != "" ]; then echo "$0: Packet2: trailing data \"$z\"" >&2; exit 1; fi +# Send answer +echo -en "\x05\x00\x00\x01\x10\x00\x1f\x64\x1f\x64" + +# Bind/listen/passive mode +if [ "$b" == 02 ]; then + sleep 1 # pretend to be waiting for connection + echo -en "\x05\x00\x00\x01\x10\xff\x1f\x64\x23\x28" +fi + +# perform echo function +$CAT diff --git a/test.sh b/test.sh index 1357f1d..9d08236 100755 --- a/test.sh +++ b/test.sh @@ -66,6 +66,7 @@ VERBOSE= DEBUG= DEFS= INTERNET= +EXPERIMENTAL= OPT_EXPECT_FAIL= EXPECT_FAIL= while [ "$1" ]; do case "X$1" in @@ -4295,46 +4296,62 @@ esac N=$((N+1)) +# Test the SOCKS address with IPv4 NAME=SOCKS4CONNECT_TCP4 case "$TESTS" in *%$N%*|*%functions%*|*%socks%*|*%socks4%*|*%tcp%*|*%tcp4%*|*%ip4%*|*%listen%*|*%$NAME%*) TEST="$NAME: socks4 connect over TCP/IPv4" if ! eval $NUMCOND; then :; -elif ! testfeats socks4 >/dev/null; then - $PRINTF "test $F_n $TEST... ${YELLOW}SOCKS4 not available${NORMAL}\n" $N - cant -elif ! testfeats listen tcp ip4 >/dev/null || ! runsip4 >/dev/null; then - $PRINTF "test $F_n $TEST... ${YELLOW}TCP/IPv4 not available${NORMAL}\n" $N +elif ! cond=$(checkconds \ + "" \ + "" \ + "socks4echo.sh" \ + "SOCKS4 IP4 TCP LISTEN STDIO" \ + "TCP4-LISTEN EXEC STDIN SOCKS4" \ + "so-reuseaddr" \ + "tcp4" ); then + $PRINTF "test $F_n $TEST... ${YELLOW}$cond${NORMAL}\n" $N cant else -tf="$td/test$N.stdout" -te="$td/test$N.stderr" -tdiff="$td/test$N.diff" -da="test$N $(date) $RANDOM"; da="$da$($ECHO '\r')" -# we have a normal tcp echo listening - so the socks header must appear in answer -newport tcp4 # provide free port number in $PORT -CMD2="$TRACE $SOCAT $opts TCP4-L:$PORT,$REUSEADDR EXEC:\"./socks4echo.sh\"" -CMD="$TRACE $SOCAT $opts - SOCKS4:$LOCALHOST:32.98.76.54:32109,pf=ip4,socksport=$PORT",socksuser="nobody" -printf "test $F_n $TEST... " $N -eval "$CMD2 2>\"${te}1\" &" -pid=$! # background process id -waittcp4port $PORT 1 -echo "$da" |$CMD >$tf 2>"${te}2" -if ! echo "$da" |diff - "$tf" >"$tdiff"; then - $PRINTF "$FAILED: $TRACE $SOCAT:\n" - echo "$CMD2 &" - echo "$CMD" - cat "${te}1" - cat "${te}2" - cat "$tdiff" - failed -else - $PRINTF "$OK\n" - if [ -n "$debug" ]; then cat "${te}1" "${te}2"; fi - ok -fi -kill $pid 2>/dev/null -wait + tf="$td/test$N.stdout" + te="$td/test$N.stderr" + tdiff="$td/test$N.diff" + da="test$N $(date) $RANDOM"; da="$da$($ECHO '\r')" + newport tcp4 # provide free port number in $PORT + CMD0="$TRACE $SOCAT $opts TCP4-LISTEN:$PORT,$REUSEADDR EXEC:\"./socks4echo.sh\"" + CMD1="$TRACE $SOCAT $opts STDIO SOCKS4:$LOCALHOST:32.98.76.54:32109,pf=ip4,socksport=$PORT",socksuser="nobody" + printf "test $F_n $TEST... " $N + eval "$CMD0 2>\"${te}0\" &" + pid0=$! # background process id + waittcp4port $PORT 1 + echo "$da" |$CMD1 >${tf}1 2>"${te}1" + rc1=$? + kill $pid0 2>/dev/null + wait + if [ "$rc1" -ne 0 ]; then + $PRINTF "$FAILED (rc1=$rc1)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + failed + elif ! echo "$da" |diff - "${tf}1" >"$tdiff"; then + $PRINTF "$FAILED (diff)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + echo "// diff:" >&2 + cat "$tdiff" >&2 + failed + else + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD1 &"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + ok + fi fi ;; # NUMCOND, feats esac N=$((N+1)) @@ -19292,6 +19309,86 @@ fi # NUMCOND esac N=$((N+1)) + +# Above test introduced with 1.8.0.2 +# Below tests introduced with 1.8.0.3 (or later) + + +# Test the SOCKS5-CONNECT and SOCKS5-LISTEN addresses with IPv4 +for SUFFIX in CONNECT LISTEN; do + +suffix=$(tolower $SUFFIX) +if [ "$SUFFIX" = LISTEN ]; then + test=listen + LISTEN=LISTEN + listen=listen +else + test=dont + LISTEN= + listen= +fi +NAME=SOCKS5${SUFFIX}_TCP4 +case "$TESTS" in +*%$N%*|*%functions%*|*%socks%*|*%socks5%*|*%tcp%*|*%tcp4%*|*%ip4%*|*%$test%*|*%$NAME%*) +TEST="$NAME: SOCKS5-$SUFFIX over TCP/IPv4" +if ! eval $NUMCOND; then :; +elif ! cond=$(checkconds \ + "" \ + "" \ + "od ./socks5server-echo.sh" \ + "SOCKS5 IP4 TCP $LISTEN STDIO" \ + "TCP4-LISTEN EXEC STDIN SOCKS5-$SUFFIX" \ + "so-reuseaddr readbytes" \ + "tcp4" ); then + $PRINTF "test $F_n $TEST... ${YELLOW}$cond${NORMAL}\n" $N + cant +else + tf="$td/test$N.stdout" + te="$td/test$N.stderr" + tdiff="$td/test$N.diff" + da="test$N $(date) $RANDOM"; da="$da$($ECHO '\r')" + newport tcp4 # provide free port number in $PORT + CMD0="$TRACE $SOCAT $opts TCP4-LISTEN:$PORT,$REUSEADDR EXEC:\"./socks5server-echo.sh\"" + CMD1="$TRACE $SOCAT $opts STDIO SOCKS5-$SUFFIX:$LOCALHOST:127.0.0.1:80,pf=ip4,socksport=$PORT" + printf "test $F_n $TEST... " $N + eval "$CMD0 2>\"${te}0\" &" + pid0=$! # background process id + waittcp4port $PORT 1 + echo "$da" |$CMD1 >${tf}1 2>"${te}1" + rc1=$? + kill $pid0 2>/dev/null + wait + if [ "$rc1" -ne 0 ]; then + $PRINTF "$FAILED (rc1=$rc1)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + failed + elif ! echo "$da" |diff - "${tf}1" >"$tdiff"; then + $PRINTF "$FAILED (diff)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + echo "// diff:" >&2 + cat "$tdiff" >&2 + failed + else + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD1 &"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + ok + fi +fi ;; # NUMCOND, feats +esac +N=$((N+1)) + +done # CONNECT LISTEN + + # end of common tests ################################################################################## diff --git a/xio-socks5.c b/xio-socks5.c index 8d8ffc5..d931961 100644 --- a/xio-socks5.c +++ b/xio-socks5.c @@ -53,7 +53,7 @@ static int xioopen_socks5(int argc, const char *argv[], struct opt *opts, int xi const struct addrdesc xioaddr_socks5_connect = { "SOCKS5-CONNECT", 1+XIO_RDWR, xioopen_socks5, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_IP_SOCKS|GROUP_CHILD|GROUP_RETRY, SOCKS5_COMMAND_CONNECT, 0, 0 HELP(":<socks-server>[:<socks-port>]:<target-host>:<target-port>") }; -const struct addrdesc xioaddr_socks5_listen = { "SOCKS5-LISTEN", 1+XIO_RDWR, xioopen_socks5, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_CHILD|GROUP_RETRY, SOCKS5_COMMAND_BIND, 0, 0 HELP(":<socks-server>[:<socks-port>]:<listen-host>:<listen-port>") }; +const struct addrdesc xioaddr_socks5_listen = { "SOCKS5-LISTEN", 1+XIO_RDWR, xioopen_socks5, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_IP_SOCKS|GROUP_CHILD|GROUP_RETRY, SOCKS5_COMMAND_BIND, 0, 0 HELP(":<socks-server>[:<socks-port>]:<listen-host>:<listen-port>") }; static const char * _xioopen_socks5_strerror(uint8_t r) { @@ -188,6 +188,8 @@ static int _xioopen_socks5_handshake(struct single *sfd, int level) return STAT_RETRYLATER; } + Debug2("received SOCKS5 server hello %02x %02x", + server_hello_ptr[0], server_hello_ptr[1]); Info2("received SOCKS5 server hello version=%d method=%d", server_hello.version, server_hello.method); @@ -332,6 +334,12 @@ static int _xioopen_socks5_read_reply( } return STAT_RETRYLATER; } + Debug5("received SOCKS5 reply %02x %02x %02x %02x %02x", + ((unsigned char *)reply+bytes_read)[0], + ((unsigned char *)reply+bytes_read)[1], + ((unsigned char *)reply+bytes_read)[2], + ((unsigned char *)reply+bytes_read)[3], + ((unsigned char *)reply+bytes_read)[4]); bytes_read += result; /* Once we've read 5 bytes, figure out total message length and @@ -518,10 +526,6 @@ static int xioopen_socks5( bool lowport = false; char infobuff[256]; - if (!xioparms.experimental) { - Error1("%s: use option --experimental to acknowledge unmature state", argv[0]); - return STAT_NORETRY; - } if (argc < 4 || argc > 5) { xio_syntax(argv[0], 4, argc-1, addrdesc->syntax); return STAT_NORETRY;