commit 93bcaf4c7d5b8e3dc29142ca50f96197639c38cf Author: Yawning Angel yawning@schwanenlied.me 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.... // -// 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@lists.torproject.org