commit 93bcaf4c7d5b8e3dc29142ca50f96197639c38cf
Author: Yawning Angel <[email protected]>
Date:   Fri Apr 3 10:23:44 2015 +0000

    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 d2e7dc1..45cf67f 100644
--- a/pt.go
+++ b/pt.go
@@ -119,10 +119,11 @@
 // Extended ORPort Authentication:
 // 
https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt.
 //
-// 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)



_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to