commit 9d26e0009fc6a8e89a48db2a976097459f14f45e Author: Yawning Angel yawning@schwanenlied.me Date: Mon Mar 23 01:02:39 2015 +0000
Use a SOCKS5 listener instead of a SOCKS4a for goptlib.
This is done in the form of a patch that has yet to be merged into goptlib. Once the branch is merged, this change should be reverted, and the goptlib tag updated. --- .../linux/gitian-pluggable-transports.yml | 6 + .../mac/gitian-pluggable-transports.yml | 6 + .../windows/gitian-pluggable-transports.yml | 6 + gitian/patches/bug12535.patch | 1032 ++++++++++++++++++++ 4 files changed, 1050 insertions(+)
diff --git a/gitian/descriptors/linux/gitian-pluggable-transports.yml b/gitian/descriptors/linux/gitian-pluggable-transports.yml index 9596b7f..2f8e70e 100644 --- a/gitian/descriptors/linux/gitian-pluggable-transports.yml +++ b/gitian/descriptors/linux/gitian-pluggable-transports.yml @@ -55,6 +55,7 @@ files: - "openssl-linux32-utils.zip" - "openssl-linux64-utils.zip" - "go.net.tar.bz2" +- "bug12535.patch" script: | INSTDIR="$HOME/install" PTDIR="$INSTDIR/Tor/PluggableTransports" @@ -206,6 +207,11 @@ script: |
# Building goptlib cd goptlib + git update-index --refresh -q + export GIT_COMMITTER_NAME="nobody" + export GIT_COMMITTER_EMAIL="nobody@localhost" + export GIT_COMMITTER_DATE="$REFERENCE_DATETIME" + git am ~/build/bug12535.patch find -type f | xargs touch --date="$REFERENCE_DATETIME" mkdir -p "$GOPATH/src/git.torproject.org/pluggable-transports" ln -sf "$PWD" "$GOPATH/src/git.torproject.org/pluggable-transports/goptlib.git" diff --git a/gitian/descriptors/mac/gitian-pluggable-transports.yml b/gitian/descriptors/mac/gitian-pluggable-transports.yml index a1bdff9..0ed896e 100644 --- a/gitian/descriptors/mac/gitian-pluggable-transports.yml +++ b/gitian/descriptors/mac/gitian-pluggable-transports.yml @@ -51,6 +51,7 @@ files: - "multiarch-darwin11-cctools127.2-gcc42-5666.3-llvmgcc42-2336.1-Linux-120724.tar.xz" - "dzip.sh" - "go.net.tar.bz2" +- "bug12535.patch" - "gmp-mac64-utils.zip" - "openssl-mac64-utils.zip" script: | @@ -232,6 +233,11 @@ script: |
# Building goptlib cd goptlib + git update-index --refresh -q + export GIT_COMMITTER_NAME="nobody" + export GIT_COMMITTER_EMAIL="nobody@localhost" + export GIT_COMMITTER_DATE="$REFERENCE_DATETIME" + git am ~/build/bug12535.patch find -type f | xargs touch --date="$REFERENCE_DATETIME" mkdir -p "$GOPATH/src/git.torproject.org/pluggable-transports" ln -sf "$PWD" "$GOPATH/src/git.torproject.org/pluggable-transports/goptlib.git" diff --git a/gitian/descriptors/windows/gitian-pluggable-transports.yml b/gitian/descriptors/windows/gitian-pluggable-transports.yml index 4ca477e..0ea6e6d 100644 --- a/gitian/descriptors/windows/gitian-pluggable-transports.yml +++ b/gitian/descriptors/windows/gitian-pluggable-transports.yml @@ -58,6 +58,7 @@ files: - "gmp-win32-utils.zip" - "gcclibs-win32-utils.zip" - "go.net.tar.bz2" +- "bug12535.patch" script: | # Set the timestamp on every .pyc file in a zip file, and re-dzip the zip file. function py2exe_zip_timestomp { @@ -308,6 +309,11 @@ script: |
# Building goptlib cd goptlib + git update-index --refresh -q + export GIT_COMMITTER_NAME="nobody" + export GIT_COMMITTER_EMAIL="nobody@localhost" + export GIT_COMMITTER_DATE="$REFERENCE_DATETIME" + git am ~/build/bug12535.patch find -type f | xargs touch --date="$REFERENCE_DATETIME" mkdir -p "$GOPATH/src/git.torproject.org/pluggable-transports" ln -sf "$PWD" "$GOPATH/src/git.torproject.org/pluggable-transports/goptlib.git" diff --git a/gitian/patches/bug12535.patch b/gitian/patches/bug12535.patch new file mode 100644 index 0000000..dae6818 --- /dev/null +++ b/gitian/patches/bug12535.patch @@ -0,0 +1,1032 @@ +From 743ae58b6c33dda837ffb1267867f564597dd59d Mon Sep 17 00:00:00 2001 +From: Yawning Angel yawning@schwanenlied.me +Date: Mon, 8 Sep 2014 18:24:08 +0000 +Subject: [PATCH] Replace the SOCKS4a listener with a SOCKS5 listener. + +The change is designed to be transparent to calling code and implements +enough of RFC1928/RFC1929 such that pluggable transports can be written. + +Notable incompatibilities/limitations: + * GSSAPI authentication is not supported. + * BND.ADDR/BND.PORT in responses is always "0.0.0.0:0" instead of the + bound address/port of the outgoing socket. + * RFC1929 Username/Password authentication is setup exclusively for + pluggable transport arguments. + * The BIND and UDP ASSOCIATE commands are not supported. +--- + pt.go | 5 +- + socks.go | 386 +++++++++++++++++++++++++++++++++++++------- + socks_test.go | 510 +++++++++++++++++++++++++++++++++++++++++----------------- + 3 files changed, 685 insertions(+), 216 deletions(-) + +diff --git a/pt.go b/pt.go +index 62dfc38..47f8273 100644 +--- a/pt.go ++++ b/pt.go +@@ -119,10 +119,11 @@ + // Extended ORPort Authentication: + // https://gitweb.torproject.org/torspec.git/blob/HEAD:/proposals/217-ext-orpor.... + // +-// The package implements a SOCKS4a server sufficient for a Tor client transport ++// The package implements a SOCKS5 server sufficient for a Tor client transport + // plugin. + // +-// http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol ++// https://www.ietf.org/rfc/rfc1928.txt ++// https://www.ietf.org/rfc/rfc1929.txt + package pt + + import ( +diff --git a/socks.go b/socks.go +index 6ad6542..e4488e8 100644 +--- a/socks.go ++++ b/socks.go +@@ -9,11 +9,40 @@ import ( + ) + + const ( +- socksVersion = 0x04 +- socksCmdConnect = 0x01 +- socksResponseVersion = 0x00 +- socksRequestGranted = 0x5a +- socksRequestRejected = 0x5b ++ socksVersion = 0x05 ++ ++ socksAuthNoneRequired = 0x00 ++ socksAuthUsernamePassword = 0x02 ++ socksAuthNoAcceptableMethods = 0xff ++ ++ socksCmdConnect = 0x01 ++ socksRsv = 0x00 ++ ++ socksAtypeV4 = 0x01 ++ socksAtypeDomainName = 0x03 ++ socksAtypeV6 = 0x04 ++ ++ socksAuthRFC1929Ver = 0x01 ++ socksAuthRFC1929Success = 0x00 ++ socksAuthRFC1929Fail = 0x01 ++ ++ socksRepSucceeded = 0x00 ++ // "general SOCKS server failure" ++ SocksRepGeneralFailure = 0x01 ++ // "connection not allowed by ruleset" ++ SocksRepConnectionNotAllowed = 0x02 ++ // "Network unreachable" ++ SocksRepNetworkUnreachable = 0x03 ++ // "Host unreachable" ++ SocksRepHostUnreachable = 0x04 ++ // "Connection refused" ++ SocksRepConnectionRefused = 0x05 ++ // "TTL expired" ++ SocksRepTTLExpired = 0x06 ++ // "Command not supported" ++ SocksRepCommandNotSupported = 0x07 ++ // "Address type not supported" ++ SocksRepAddressNotSupported = 0x08 + ) + + // Put a sanity timeout on how long we wait for a SOCKS request. +@@ -25,6 +54,8 @@ type SocksRequest struct { + Target string + // The userid string sent by the client. + Username string ++ // The password string sent by the client. ++ Password string + // The parsed contents of Username as a key–value mapping. + Args Args + } +@@ -36,15 +67,23 @@ type SocksConn struct { + } + + // Send a message to the proxy client that access to the given address is +-// granted. If the IP field inside addr is not an IPv4 address, the IP portion +-// of the response will be four zero bytes. ++// granted. Addr is ignored, and "0.0.0.0:0" is always sent back for ++// BND.ADDR/BND.PORT in the SOCKS response. + func (conn *SocksConn) Grant(addr *net.TCPAddr) error { +- return sendSocks4aResponseGranted(conn, addr) ++ return sendSocks5ResponseGranted(conn) + } + +-// Send a message to the proxy client that access was rejected or failed. ++// Send a message to the proxy client that access was rejected or failed. This ++// sends back a "General Failure" error code. RejectReason should be used if ++// more specific error reporting is desired. + func (conn *SocksConn) Reject() error { +- return sendSocks4aResponseRejected(conn) ++ return conn.RejectReason(SocksRepGeneralFailure) ++} ++ ++// Send a message to the proxy client that access was rejected, with the ++// specific error code indicating the reason behind the rejection. ++func (conn *SocksConn) RejectReason(reason byte) error { ++ return sendSocks5ResponseRejected(conn, reason) + } + + // SocksListener wraps a net.Listener in order to read a SOCKS request on Accept. +@@ -138,7 +177,7 @@ func (ln *SocksListener) AcceptSocks() (*SocksConn, error) { + if err != nil { + return nil, err + } +- conn.Req, err = readSocks4aConnect(conn) ++ conn.Req, err = socks5Handshake(conn) + if err != nil { + conn.Close() + return nil, err +@@ -150,58 +189,251 @@ func (ln *SocksListener) AcceptSocks() (*SocksConn, error) { + return conn, nil + } + +-// Returns "socks4", suitable to be included in a call to Cmethod. ++// Returns "socks5", suitable to be included in a call to Cmethod. + func (ln *SocksListener) Version() string { +- return "socks4" ++ return "socks5" + } + +-// Read a SOCKS4a connect request. Returns a SocksRequest. +-func readSocks4aConnect(s io.Reader) (req SocksRequest, err error) { +- r := bufio.NewReader(s) ++// socks5handshake conducts the SOCKS5 handshake up to the point where the ++// client command is read and the proxy must open the outgoing connection. ++// Returns a SocksRequest. ++func socks5Handshake(s io.ReadWriter) (req SocksRequest, err error) { ++ rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + +- var h [8]byte +- _, err = io.ReadFull(r, h[:]) +- if err != nil { ++ // Negotiate the authentication method. ++ var method byte ++ if method, err = socksNegotiateAuth(rw); err != nil { + return + } +- if h[0] != socksVersion { +- err = fmt.Errorf("SOCKS header had version 0x%02x, not 0x%02x", h[0], socksVersion) ++ ++ // Authenticate the client. ++ if err = socksAuthenticate(rw, method, &req); err != nil { + return + } +- if h[1] != socksCmdConnect { +- err = fmt.Errorf("SOCKS header had command 0x%02x, not 0x%02x", h[1], socksCmdConnect) ++ ++ // Read the command. ++ err = socksReadCommand(rw, &req) ++ return ++} ++ ++// socksNegotiateAuth negotiates the authentication method and returns the ++// selected method as a byte. On negotiation failures an error is returned. ++func socksNegotiateAuth(rw *bufio.ReadWriter) (method byte, err error) { ++ // Validate the version. ++ if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { + return + } + +- var usernameBytes []byte +- usernameBytes, err = r.ReadBytes('\x00') +- if err != nil { ++ // Read the number of methods. ++ var nmethods byte ++ if nmethods, err = socksReadByte(rw); err != nil { + return + } +- req.Username = string(usernameBytes[:len(usernameBytes)-1]) + +- req.Args, err = parseClientParameters(req.Username) +- if err != nil { ++ // Read the methods. ++ var methods []byte ++ if methods, err = socksReadBytes(rw, int(nmethods)); err != nil { + return + } + +- var port int +- var host string ++ // Pick the most "suitable" method. ++ method = socksAuthNoAcceptableMethods ++ for _, m := range methods { ++ switch m { ++ case socksAuthNoneRequired: ++ // Pick Username/Password over None if the client happens to ++ // send both. ++ if method == socksAuthNoAcceptableMethods { ++ method = m ++ } ++ ++ case socksAuthUsernamePassword: ++ method = m ++ } ++ } ++ ++ // Send the negotiated method. ++ var msg [2]byte ++ msg[0] = socksVersion ++ msg[1] = method ++ if _, err = rw.Writer.Write(msg[:]); err != nil { ++ return ++ } ++ ++ if err = socksFlushBuffers(rw); err != nil { ++ return ++ } ++ return ++} ++ ++// socksAuthenticate authenticates the client via the chosen authentication ++// mechanism. ++func socksAuthenticate(rw *bufio.ReadWriter, method byte, req *SocksRequest) (err error) { ++ switch method { ++ case socksAuthNoneRequired: ++ // Straight into reading the connect. + +- port = int(h[2])<<8 | int(h[3])<<0 +- if h[4] == 0 && h[5] == 0 && h[6] == 0 && h[7] != 0 { +- var hostBytes []byte +- hostBytes, err = r.ReadBytes('\x00') +- if err != nil { ++ case socksAuthUsernamePassword: ++ if err = socksAuthRFC1929(rw, req); err != nil { + return + } +- host = string(hostBytes[:len(hostBytes)-1]) ++ ++ case socksAuthNoAcceptableMethods: ++ err = fmt.Errorf("SOCKS method select had no compatible methods") ++ return ++ ++ default: ++ err = fmt.Errorf("SOCKS method select picked a unsupported method 0x%02x", method) ++ return ++ } ++ ++ if err = socksFlushBuffers(rw); err != nil { ++ return ++ } ++ return ++} ++ ++// socksAuthRFC1929 authenticates the client via RFC 1929 username/password ++// auth. As a design decision any valid username/password is accepted as this ++// field is primarily used as an out-of-band argument passing mechanism for ++// pluggable transports. ++func socksAuthRFC1929(rw *bufio.ReadWriter, req *SocksRequest) (err error) { ++ sendErrResp := func() { ++ // Swallow the write/flush error here, we are going to close the ++ // connection and the original failure is more useful. ++ resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Fail} ++ rw.Write(resp[:]) ++ socksFlushBuffers(rw) ++ } ++ ++ // Validate the fixed parts of the command message. ++ if err = socksReadByteVerify(rw, "auth version", socksAuthRFC1929Ver); err != nil { ++ sendErrResp() ++ return ++ } ++ ++ // Read the username. ++ var ulen byte ++ if ulen, err = socksReadByte(rw); err != nil { ++ return ++ } ++ if ulen < 1 { ++ sendErrResp() ++ err = fmt.Errorf("RFC1929 username with 0 length") ++ return ++ } ++ var uname []byte ++ if uname, err = socksReadBytes(rw, int(ulen)); err != nil { ++ return ++ } ++ req.Username = string(uname) ++ ++ // Read the password. ++ var plen byte ++ if plen, err = socksReadByte(rw); err != nil { ++ return ++ } ++ if plen < 1 { ++ sendErrResp() ++ err = fmt.Errorf("RFC1929 password with 0 length") ++ return ++ } ++ var passwd []byte ++ if passwd, err = socksReadBytes(rw, int(plen)); err != nil { ++ return ++ } ++ if !(plen == 1 && passwd[0] == 0x00) { ++ // tor will set the password to 'NUL' if there are no arguments. ++ req.Password = string(passwd) ++ } ++ ++ // Mash the username/password together and parse it as a pluggable ++ // transport argument string. ++ if req.Args, err = parseClientParameters(req.Username + req.Password); err != nil { ++ sendErrResp() + } else { +- host = net.IPv4(h[4], h[5], h[6], h[7]).String() ++ resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Success} ++ _, err = rw.Write(resp[:]) ++ } ++ return ++} ++ ++// socksReadCommand reads a SOCKS5 client command and parses out the relevant ++// fields into a SocksRequest. Only CMD_CONNECT is supported. ++func socksReadCommand(rw *bufio.ReadWriter, req *SocksRequest) (err error) { ++ sendErrResp := func(reason byte) { ++ // Swallow errors that occur when writing/flushing the response, ++ // connection will be closed anyway. ++ sendSocks5ResponseRejected(rw, reason) ++ socksFlushBuffers(rw) ++ } ++ ++ // Validate the fixed parts of the command message. ++ if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { ++ sendErrResp(SocksRepGeneralFailure) ++ return ++ } ++ if err = socksReadByteVerify(rw, "command", socksCmdConnect); err != nil { ++ sendErrResp(SocksRepCommandNotSupported) ++ return ++ } ++ if err = socksReadByteVerify(rw, "reserved", socksRsv); err != nil { ++ sendErrResp(SocksRepGeneralFailure) ++ return ++ } ++ ++ // Read the destination address/port. ++ // XXX: This should probably eventually send socks 5 error messages instead ++ // of rudely closing connections on invalid addresses. ++ var atype byte ++ if atype, err = socksReadByte(rw); err != nil { ++ return ++ } ++ var host string ++ switch atype { ++ case socksAtypeV4: ++ var addr []byte ++ if addr, err = socksReadBytes(rw, net.IPv4len); err != nil { ++ return ++ } ++ host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String() ++ ++ case socksAtypeDomainName: ++ var alen byte ++ if alen, err = socksReadByte(rw); err != nil { ++ return ++ } ++ if alen == 0 { ++ err = fmt.Errorf("SOCKS request had domain name with 0 length") ++ return ++ } ++ var addr []byte ++ if addr, err = socksReadBytes(rw, int(alen)); err != nil { ++ return ++ } ++ host = string(addr) ++ ++ case socksAtypeV6: ++ var rawAddr []byte ++ if rawAddr, err = socksReadBytes(rw, net.IPv6len); err != nil { ++ return ++ } ++ addr := make(net.IP, net.IPv6len) ++ copy(addr[:], rawAddr[:]) ++ host = fmt.Sprintf("[%s]", addr.String()) ++ ++ default: ++ sendErrResp(SocksRepAddressNotSupported) ++ err = fmt.Errorf("SOCKS request had unsupported address type 0x%02x", atype) ++ return + } ++ var rawPort []byte ++ if rawPort, err = socksReadBytes(rw, 2); err != nil { ++ return ++ } ++ port := int(rawPort[0])<<8 | int(rawPort[1])<<0 + +- if r.Buffered() != 0 { +- err = fmt.Errorf("%d bytes left after SOCKS header", r.Buffered()) ++ if err = socksFlushBuffers(rw); err != nil { + return + } + +@@ -209,34 +441,64 @@ func readSocks4aConnect(s io.Reader) (req SocksRequest, err error) { + return + } + +-// Send a SOCKS4a response with the given code and address. If the IP field +-// inside addr is not an IPv4 address, the IP portion of the response will be +-// four zero bytes. +-func sendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error { +- var resp [8]byte +- resp[0] = socksResponseVersion ++// Send a SOCKS5 response with the given code. BND.ADDR/BND.PORT is always the ++// IPv4 address/port "0.0.0.0:0". ++func sendSocks5Response(w io.Writer, code byte) error { ++ resp := make([]byte, 4+4+2) ++ resp[0] = socksVersion + resp[1] = code +- resp[2] = byte((addr.Port >> 8) & 0xff) +- resp[3] = byte((addr.Port >> 0) & 0xff) +- ipv4 := addr.IP.To4() +- if ipv4 != nil { +- resp[4] = ipv4[0] +- resp[5] = ipv4[1] +- resp[6] = ipv4[2] +- resp[7] = ipv4[3] +- } ++ resp[2] = socksRsv ++ resp[3] = socksAtypeV4 ++ ++ // BND.ADDR/BND.PORT should be the address and port that the outgoing ++ // connection is bound to on the proxy, but Tor does not use this ++ // information, so all zeroes are sent. ++ + _, err := w.Write(resp[:]) + return err + } + +-var emptyAddr = net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 0} ++// Send a SOCKS5 response code 0x00. ++func sendSocks5ResponseGranted(w io.Writer) error { ++ return sendSocks5Response(w, socksRepSucceeded) ++} + +-// Send a SOCKS4a response code 0x5a. +-func sendSocks4aResponseGranted(w io.Writer, addr *net.TCPAddr) error { +- return sendSocks4aResponse(w, socksRequestGranted, addr) ++// Send a SOCKS5 response with the provided failure reason. ++func sendSocks5ResponseRejected(w io.Writer, reason byte) error { ++ return sendSocks5Response(w, reason) + } + +-// Send a SOCKS4a response code 0x5b (with an all-zero address). +-func sendSocks4aResponseRejected(w io.Writer) error { +- return sendSocks4aResponse(w, socksRequestRejected, &emptyAddr) ++func socksFlushBuffers(rw *bufio.ReadWriter) error { ++ if err := rw.Writer.Flush(); err != nil { ++ return err ++ } ++ if rw.Reader.Buffered() > 0 { ++ return fmt.Errorf("%d bytes left after SOCKS message", rw.Reader.Buffered()) ++ } ++ return nil ++} ++ ++func socksReadByte(rw *bufio.ReadWriter) (byte, error) { ++ return rw.Reader.ReadByte() ++} ++ ++func socksReadBytes(rw *bufio.ReadWriter, n int) ([]byte, error) { ++ ret := make([]byte, n) ++ if _, err := io.ReadFull(rw.Reader, ret); err != nil { ++ return nil, err ++ } ++ return ret, nil + } ++ ++func socksReadByteVerify(rw *bufio.ReadWriter, descr string, expected byte) error { ++ val, err := socksReadByte(rw) ++ if err != nil { ++ return err ++ } ++ if val != expected { ++ return fmt.Errorf("SOCKS message field %s was 0x%02x, not 0x%02x", descr, val, expected) ++ } ++ return nil ++} ++ ++var _ net.Listener = (*SocksListener)(nil) +diff --git a/socks_test.go b/socks_test.go +index 18d141a..aa27d4c 100644 +--- a/socks_test.go ++++ b/socks_test.go +@@ -1,162 +1,368 @@ + package pt + + import ( ++ "bufio" + "bytes" ++ "encoding/hex" ++ "io" + "net" + "testing" + ) + +-func TestReadSocks4aConnect(t *testing.T) { +- badTests := [...][]byte{ +- []byte(""), +- // missing userid +- []byte("\x04\x01\x12\x34\x01\x02\x03\x04"), +- // missing \x00 after userid +- []byte("\x04\x01\x12\x34\x01\x02\x03\x04key=value"), +- // missing hostname +- []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00"), +- // missing \x00 after hostname +- []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00hostname"), +- // bad name–value mapping +- []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00hostname\x00"), +- // bad version number +- []byte("\x03\x01\x12\x34\x01\x02\x03\x04\x00"), +- // BIND request +- []byte("\x04\x02\x12\x34\x01\x02\x03\x04\x00"), +- // SOCKS5 +- []byte("\x05\x01\x00"), +- } +- ipTests := [...]struct { +- input []byte +- addr net.TCPAddr +- userid string +- }{ +- { +- []byte("\x04\x01\x12\x34\x01\x02\x03\x04key=value\x00"), +- net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, +- "key=value", +- }, +- { +- []byte("\x04\x01\x12\x34\x01\x02\x03\x04\x00"), +- net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, +- "", +- }, +- } +- hostnameTests := [...]struct { +- input []byte +- target string +- userid string +- }{ +- { +- []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00hostname\x00"), +- "hostname:4660", +- "key=value", +- }, +- { +- []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00hostname\x00"), +- "hostname:4660", +- "", +- }, +- { +- []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00\x00"), +- ":4660", +- "key=value", +- }, +- { +- []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00\x00"), +- ":4660", +- "", +- }, +- } +- +- for _, input := range badTests { +- var buf bytes.Buffer +- buf.Write(input) +- _, err := readSocks4aConnect(&buf) +- if err == nil { +- t.Errorf("%q unexpectedly succeeded", input) +- } +- } +- +- for _, test := range ipTests { +- var buf bytes.Buffer +- buf.Write(test.input) +- req, err := readSocks4aConnect(&buf) +- if err != nil { +- t.Errorf("%q unexpectedly returned an error: %s", test.input, err) +- } +- addr, err := net.ResolveTCPAddr("tcp", req.Target) +- if err != nil { +- t.Errorf("%q → target %q: cannot resolve: %s", test.input, +- req.Target, err) +- } +- if !tcpAddrsEqual(addr, &test.addr) { +- t.Errorf("%q → address %s (expected %s)", test.input, +- req.Target, test.addr.String()) +- } +- if req.Username != test.userid { +- t.Errorf("%q → username %q (expected %q)", test.input, +- req.Username, test.userid) +- } +- if req.Args == nil { +- t.Errorf("%q → unexpected nil Args from username %q", test.input, req.Username) +- } +- } +- +- for _, test := range hostnameTests { +- var buf bytes.Buffer +- buf.Write(test.input) +- req, err := readSocks4aConnect(&buf) +- if err != nil { +- t.Errorf("%q unexpectedly returned an error: %s", test.input, err) +- } +- if req.Target != test.target { +- t.Errorf("%q → target %q (expected %q)", test.input, +- req.Target, test.target) +- } +- if req.Username != test.userid { +- t.Errorf("%q → username %q (expected %q)", test.input, +- req.Username, test.userid) +- } +- if req.Args == nil { +- t.Errorf("%q → unexpected nil Args from username %q", test.input, req.Username) +- } +- } +-} +- +-func TestSendSocks4aResponse(t *testing.T) { +- tests := [...]struct { +- code byte +- addr net.TCPAddr +- expected []byte +- }{ +- { +- socksRequestGranted, +- net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, +- []byte("\x00\x5a\x12\x34\x01\x02\x03\x04"), +- }, +- { +- socksRequestRejected, +- net.TCPAddr{IP: net.ParseIP("1:2::3:4"), Port: 0x1234}, +- []byte("\x00\x5b\x12\x34\x00\x00\x00\x00"), +- }, +- } +- +- for _, test := range tests { +- var buf bytes.Buffer +- err := sendSocks4aResponse(&buf, test.code, &test.addr) +- if err != nil { +- t.Errorf("0x%02x %s unexpectedly returned an error: %s", test.code, &test.addr, err) +- } +- p := make([]byte, 1024) +- n, err := buf.Read(p) +- if err != nil { +- t.Fatal(err) +- } +- output := p[:n] +- if !bytes.Equal(output, test.expected) { +- t.Errorf("0x%02x %s → %v (expected %v)", +- test.code, &test.addr, output, test.expected) +- } ++// testReadWriter is a bytes.Buffer backed io.ReadWriter used for testing. The ++// Read and Write routines are to be used by the component being tested. Data ++// can be written to and read back via the writeHex and readHex routines. ++type testReadWriter struct { ++ readBuf bytes.Buffer ++ writeBuf bytes.Buffer ++} ++ ++func (c *testReadWriter) Read(buf []byte) (n int, err error) { ++ return c.readBuf.Read(buf) ++} ++ ++func (c *testReadWriter) Write(buf []byte) (n int, err error) { ++ return c.writeBuf.Write(buf) ++} ++ ++func (c *testReadWriter) writeHex(str string) (n int, err error) { ++ var buf []byte ++ if buf, err = hex.DecodeString(str); err != nil { ++ return ++ } ++ return c.readBuf.Write(buf) ++} ++ ++func (c *testReadWriter) readHex() string { ++ return hex.EncodeToString(c.writeBuf.Bytes()) ++} ++ ++func (c *testReadWriter) toBufio() *bufio.ReadWriter { ++ return bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) ++} ++ ++func (c *testReadWriter) reset() { ++ c.readBuf.Reset() ++ c.writeBuf.Reset() ++} ++ ++// TestAuthInvalidVersion tests auth negotiation with an invalid version. ++func TestAuthInvalidVersion(t *testing.T) { ++ c := new(testReadWriter) ++ ++ // VER = 03, NMETHODS = 01, METHODS = [00] ++ c.writeHex("030100") ++ if _, err := socksNegotiateAuth(c.toBufio()); err == nil { ++ t.Error("socksNegotiateAuth(InvalidVersion) succeded") ++ } ++} ++ ++// TestAuthInvalidNMethods tests auth negotiaton with no methods. ++func TestAuthInvalidNMethods(t *testing.T) { ++ c := new(testReadWriter) ++ var err error ++ var method byte ++ ++ // VER = 05, NMETHODS = 00 ++ c.writeHex("0500") ++ if method, err = socksNegotiateAuth(c.toBufio()); err != nil { ++ t.Error("socksNegotiateAuth(No Methods) failed:", err) ++ } ++ if method != socksAuthNoAcceptableMethods { ++ t.Error("socksNegotiateAuth(No Methods) picked unexpected method:", method) ++ } ++ if msg := c.readHex(); msg != "05ff" { ++ t.Error("socksNegotiateAuth(No Methods) invalid response:", msg) ++ } ++} ++ ++// TestAuthNoneRequired tests auth negotiaton with NO AUTHENTICATION REQUIRED. ++func TestAuthNoneRequired(t *testing.T) { ++ c := new(testReadWriter) ++ var err error ++ var method byte ++ ++ // VER = 05, NMETHODS = 01, METHODS = [00] ++ c.writeHex("050100") ++ if method, err = socksNegotiateAuth(c.toBufio()); err != nil { ++ t.Error("socksNegotiateAuth(None) failed:", err) ++ } ++ if method != socksAuthNoneRequired { ++ t.Error("socksNegotiateAuth(None) unexpected method:", method) ++ } ++ if msg := c.readHex(); msg != "0500" { ++ t.Error("socksNegotiateAuth(None) invalid response:", msg) ++ } ++} ++ ++// TestAuthUsernamePassword tests auth negotiation with USERNAME/PASSWORD. ++func TestAuthUsernamePassword(t *testing.T) { ++ c := new(testReadWriter) ++ var err error ++ var method byte ++ ++ // VER = 05, NMETHODS = 01, METHODS = [02] ++ c.writeHex("050102") ++ if method, err = socksNegotiateAuth(c.toBufio()); err != nil { ++ t.Error("socksNegotiateAuth(UsernamePassword) failed:", err) ++ } ++ if method != socksAuthUsernamePassword { ++ t.Error("socksNegotiateAuth(UsernamePassword) unexpected method:", method) ++ } ++ if msg := c.readHex(); msg != "0502" { ++ t.Error("socksNegotiateAuth(UsernamePassword) invalid response:", msg) + } + } ++ ++// TestAuthBoth tests auth negotiation containing both NO AUTHENTICATION ++// REQUIRED and USERNAME/PASSWORD. ++func TestAuthBoth(t *testing.T) { ++ c := new(testReadWriter) ++ var err error ++ var method byte ++ ++ // VER = 05, NMETHODS = 02, METHODS = [00, 02] ++ c.writeHex("05020002") ++ if method, err = socksNegotiateAuth(c.toBufio()); err != nil { ++ t.Error("socksNegotiateAuth(Both) failed:", err) ++ } ++ if method != socksAuthUsernamePassword { ++ t.Error("socksNegotiateAuth(Both) unexpected method:", method) ++ } ++ if msg := c.readHex(); msg != "0502" { ++ t.Error("socksNegotiateAuth(Both) invalid response:", msg) ++ } ++} ++ ++// TestAuthUnsupported tests auth negotiation with a unsupported method. ++func TestAuthUnsupported(t *testing.T) { ++ c := new(testReadWriter) ++ var err error ++ var method byte ++ ++ // VER = 05, NMETHODS = 01, METHODS = [01] (GSSAPI) ++ c.writeHex("050101") ++ if method, err = socksNegotiateAuth(c.toBufio()); err != nil { ++ t.Error("socksNegotiateAuth(Unknown) failed:", err) ++ } ++ if method != socksAuthNoAcceptableMethods { ++ t.Error("socksNegotiateAuth(Unknown) picked unexpected method:", method) ++ } ++ if msg := c.readHex(); msg != "05ff" { ++ t.Error("socksNegotiateAuth(Unknown) invalid response:", msg) ++ } ++} ++ ++// TestAuthUnsupported2 tests auth negotiation with supported and unsupported ++// methods. ++func TestAuthUnsupported2(t *testing.T) { ++ c := new(testReadWriter) ++ var err error ++ var method byte ++ ++ // VER = 05, NMETHODS = 03, METHODS = [00,01,02] ++ c.writeHex("0503000102") ++ if method, err = socksNegotiateAuth(c.toBufio()); err != nil { ++ t.Error("socksNegotiateAuth(Unknown2) failed:", err) ++ } ++ if method != socksAuthUsernamePassword { ++ t.Error("socksNegotiateAuth(Unknown2) picked unexpected method:", method) ++ } ++ if msg := c.readHex(); msg != "0502" { ++ t.Error("socksNegotiateAuth(Unknown2) invalid response:", msg) ++ } ++} ++ ++// TestRFC1929InvalidVersion tests RFC1929 auth with an invalid version. ++func TestRFC1929InvalidVersion(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 03, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" ++ c.writeHex("03054142434445056162636465") ++ if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { ++ t.Error("socksAuthenticate(InvalidVersion) succeded") ++ } ++ if msg := c.readHex(); msg != "0101" { ++ t.Error("socksAuthenticate(InvalidVersion) invalid response:", msg) ++ } ++} ++ ++// TestRFC1929InvalidUlen tests RFC1929 auth with an invalid ULEN. ++func TestRFC1929InvalidUlen(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 01, ULEN = 0, UNAME = "", PLEN = 5, PASSWD = "abcde" ++ c.writeHex("0100056162636465") ++ if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { ++ t.Error("socksAuthenticate(InvalidUlen) succeded") ++ } ++ if msg := c.readHex(); msg != "0101" { ++ t.Error("socksAuthenticate(InvalidUlen) invalid response:", msg) ++ } ++} ++ ++// TestRFC1929InvalidPlen tests RFC1929 auth with an invalid PLEN. ++func TestRFC1929InvalidPlen(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 0, PASSWD = "" ++ c.writeHex("0105414243444500") ++ if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { ++ t.Error("socksAuthenticate(InvalidPlen) succeded") ++ } ++ if msg := c.readHex(); msg != "0101" { ++ t.Error("socksAuthenticate(InvalidPlen) invalid response:", msg) ++ } ++} ++ ++// TestRFC1929InvalidArgs tests RFC1929 auth with invalid pt args. ++func TestRFC1929InvalidPTArgs(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" ++ c.writeHex("01054142434445056162636465") ++ if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { ++ t.Error("socksAuthenticate(InvalidArgs) succeded") ++ } ++ if msg := c.readHex(); msg != "0101" { ++ t.Error("socksAuthenticate(InvalidArgs) invalid response:", msg) ++ } ++} ++ ++// TestRFC1929Success tests RFC1929 auth with valid pt args. ++func TestRFC1929Success(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 01, ULEN = 9, UNAME = "key=value", PLEN = 1, PASSWD = "\0" ++ c.writeHex("01096b65793d76616c75650100") ++ if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err != nil { ++ t.Error("socksAuthenticate(Success) failed:", err) ++ } ++ if msg := c.readHex(); msg != "0100" { ++ t.Error("socksAuthenticate(Success) invalid response:", msg) ++ } ++ v, ok := req.Args.Get("key") ++ if v != "value" || !ok { ++ t.Error("RFC1929 k,v parse failure:", v) ++ } ++} ++ ++// TestRequestInvalidHdr tests SOCKS5 requests with invalid VER/CMD/RSV/ATYPE ++func TestRequestInvalidHdr(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 03, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 ++ c.writeHex("030100017f000001235a") ++ if err := socksReadCommand(c.toBufio(), &req); err == nil { ++ t.Error("socksReadCommand(InvalidVer) succeded") ++ } ++ if msg := c.readHex(); msg != "05010001000000000000" { ++ t.Error("socksReadCommand(InvalidVer) invalid response:", msg) ++ } ++ c.reset() ++ ++ // VER = 05, CMD = 05, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 ++ c.writeHex("050500017f000001235a") ++ if err := socksReadCommand(c.toBufio(), &req); err == nil { ++ t.Error("socksReadCommand(InvalidCmd) succeded") ++ } ++ if msg := c.readHex(); msg != "05070001000000000000" { ++ t.Error("socksReadCommand(InvalidCmd) invalid response:", msg) ++ } ++ c.reset() ++ ++ // VER = 05, CMD = 01, RSV = 30, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 ++ c.writeHex("050130017f000001235a") ++ if err := socksReadCommand(c.toBufio(), &req); err == nil { ++ t.Error("socksReadCommand(InvalidRsv) succeded") ++ } ++ if msg := c.readHex(); msg != "05010001000000000000" { ++ t.Error("socksReadCommand(InvalidRsv) invalid response:", msg) ++ } ++ c.reset() ++ ++ // VER = 05, CMD = 01, RSV = 01, ATYPE = 05, DST.ADDR = 127.0.0.1, DST.PORT = 9050 ++ c.writeHex("050100057f000001235a") ++ if err := socksReadCommand(c.toBufio(), &req); err == nil { ++ t.Error("socksReadCommand(InvalidAtype) succeded") ++ } ++ if msg := c.readHex(); msg != "05080001000000000000" { ++ t.Error("socksAuthenticate(InvalidAtype) invalid response:", msg) ++ } ++ c.reset() ++} ++ ++// TestRequestIPv4 tests IPv4 SOCKS5 requests. ++func TestRequestIPv4(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 05, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 ++ c.writeHex("050100017f000001235a") ++ if err := socksReadCommand(c.toBufio(), &req); err != nil { ++ t.Error("socksReadCommand(IPv4) failed:", err) ++ } ++ addr, err := net.ResolveTCPAddr("tcp", req.Target) ++ if err != nil { ++ t.Error("net.ResolveTCPAddr failed:", err) ++ } ++ if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9050}) { ++ t.Error("Unexpected target:", addr) ++ } ++} ++ ++// TestRequestIPv6 tests IPv4 SOCKS5 requests. ++func TestRequestIPv6(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = 0102:0304:0506:0708:090a:0b0c:0d0e:0f10, DST.PORT = 9050 ++ c.writeHex("050100040102030405060708090a0b0c0d0e0f10235a") ++ if err := socksReadCommand(c.toBufio(), &req); err != nil { ++ t.Error("socksReadCommand(IPv6) failed:", err) ++ } ++ addr, err := net.ResolveTCPAddr("tcp", req.Target) ++ if err != nil { ++ t.Error("net.ResolveTCPAddr failed:", err) ++ } ++ if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("0102:0304:0506:0708:090a:0b0c:0d0e:0f10"), Port: 9050}) { ++ t.Error("Unexpected target:", addr) ++ } ++} ++ ++// TestRequestFQDN tests FQDN (DOMAINNAME) SOCKS5 requests. ++func TestRequestFQDN(t *testing.T) { ++ c := new(testReadWriter) ++ var req SocksRequest ++ ++ // VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = example.com, DST.PORT = 9050 ++ c.writeHex("050100030b6578616d706c652e636f6d235a") ++ if err := socksReadCommand(c.toBufio(), &req); err != nil { ++ t.Error("socksReadCommand(FQDN) failed:", err) ++ } ++ if req.Target != "example.com:9050" { ++ t.Error("Unexpected target:", req.Target) ++ } ++} ++ ++// TestResponseNil tests nil address SOCKS5 responses. ++func TestResponseNil(t *testing.T) { ++ c := new(testReadWriter) ++ ++ b := c.toBufio() ++ if err := sendSocks5ResponseGranted(b); err != nil { ++ t.Error("sendSocks5ResponseGranted() failed:", err) ++ } ++ b.Flush() ++ if msg := c.readHex(); msg != "05000001000000000000" { ++ t.Error("sendSocks5ResponseGranted(nil) invalid response:", msg) ++ } ++} ++ ++var _ io.ReadWriter = (*testReadWriter)(nil) +-- +2.3.3 +