diff options
Diffstat (limited to 'vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go')
-rw-r--r-- | vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go new file mode 100644 index 0000000..eab74c6 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go @@ -0,0 +1,486 @@ +package pt + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "net" + "time" +) + +const ( + socksVersion = 0x05 + + socksAuthNoneRequired = 0x00 + socksAuthPrivateMethodPT2 = 0x80 + socksAuthNoAcceptableMethods = 0xff + + socksCmdConnect = 0x01 + socksRsv = 0x00 + + socksAtypeV4 = 0x01 + socksAtypeDomainName = 0x03 + socksAtypeV6 = 0x04 + + 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. +const socksRequestTimeout = 5 * time.Second + +// SocksRequest describes a SOCKS request. +type SocksRequest struct { + // The endpoint requested by the client as a "host:port" string. + Target string + // The parsed contents of private authentication method as a key–value mapping. + Args Args +} + +// SocksConn encapsulates a net.Conn and information associated with a SOCKS request. +type SocksConn struct { + net.Conn + Req SocksRequest +} + +// Send a message to the proxy client that access to the given address is +// 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 sendSocks5ResponseGranted(conn) +} + +// 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 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. +// +// func handleConn(conn *pt.SocksConn) error { +// defer conn.Close() +// remote, err := net.Dial("tcp", conn.Req.Target) +// if err != nil { +// conn.Reject() +// return err +// } +// defer remote.Close() +// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) +// if err != nil { +// return err +// } +// // do something with conn and remote +// return nil +// } +// ... +// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") +// if err != nil { +// panic(err.Error()) +// } +// for { +// conn, err := ln.AcceptSocks() +// if err != nil { +// log.Printf("accept error: %s", err) +// if e, ok := err.(net.Error); ok && e.Temporary() { +// continue +// } +// break +// } +// go handleConn(conn) +// } +type SocksListener struct { + net.Listener +} + +// Open a net.Listener according to network and laddr, and return it as a +// SocksListener. +func ListenSocks(network, laddr string) (*SocksListener, error) { + ln, err := net.Listen(network, laddr) + if err != nil { + return nil, err + } + return NewSocksListener(ln), nil +} + +// Create a new SocksListener wrapping the given net.Listener. +func NewSocksListener(ln net.Listener) *SocksListener { + return &SocksListener{ln} +} + +// Accept is the same as AcceptSocks, except that it returns a generic net.Conn. +// It is present for the sake of satisfying the net.Listener interface. +func (ln *SocksListener) Accept() (net.Conn, error) { + return ln.AcceptSocks() +} + +// Call Accept on the wrapped net.Listener, do SOCKS negotiation, and return a +// SocksConn. After accepting, you must call either conn.Grant or conn.Reject +// (presumably after trying to connect to conn.Req.Target). +// +// Errors returned by AcceptSocks may be temporary (for example, EOF while +// reading the request, or a badly formatted userid string), or permanent (e.g., +// the underlying socket is closed). You can determine whether an error is +// temporary and take appropriate action with a type conversion to net.Error. +// For example: +// +// for { +// conn, err := ln.AcceptSocks() +// if err != nil { +// if e, ok := err.(net.Error); ok && e.Temporary() { +// log.Printf("temporary accept error; trying again: %s", err) +// continue +// } +// log.Printf("permanent accept error; giving up: %s", err) +// break +// } +// go handleConn(conn) +// } +func (ln *SocksListener) AcceptSocks() (*SocksConn, error) { +retry: + c, err := ln.Listener.Accept() + if err != nil { + return nil, err + } + conn := new(SocksConn) + conn.Conn = c + err = conn.SetDeadline(time.Now().Add(socksRequestTimeout)) + if err != nil { + conn.Close() + goto retry + } + conn.Req, err = socks5Handshake(conn, false) + if err != nil { + conn.Close() + goto retry + } + err = conn.SetDeadline(time.Time{}) + if err != nil { + conn.Close() + goto retry + } + return conn, nil +} + +// Returns "socks5", suitable to be included in a call to Cmethod. +func (ln *SocksListener) Version() string { + return "socks5" +} + +// 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, needOptions bool) (req SocksRequest, err error) { + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + + // Negotiate the authentication method. + var method byte + if method, err = socksNegotiateAuth(rw, needOptions); err != nil { + return + } + + // Authenticate the client. + if err = socksAuthenticate(rw, method, &req); err != nil { + return + } + + // 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, needOptions bool) (method byte, err error) { + // Validate the version. + if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { + return + } + + // Read the number of methods. + var nmethods byte + if nmethods, err = socksReadByte(rw); err != nil { + return + } + + // Read the methods. + var methods []byte + if methods, err = socksReadBytes(rw, int(nmethods)); err != nil { + return + } + + // Pick the most "suitable" method. + method = socksAuthNoAcceptableMethods + for _, m := range methods { + if needOptions { + switch m { + case socksAuthNoneRequired: + // Pick PT 2.0 private authentication method over None if the client happens to + // send both and SOCKS header options are needed. + if method == socksAuthNoAcceptableMethods { + method = m + } + + case socksAuthPrivateMethodPT2: + method = m + } + } else { + switch m { + case socksAuthPrivateMethodPT2: + // Pick None over PT 2.0 private authentication method if the client happens to + // send both and SOCKS header options are not needed. + if method == socksAuthNoAcceptableMethods { + method = m + } + + case socksAuthNoneRequired: + 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. + + case socksAuthPrivateMethodPT2: + if err = socksAuthPT2(rw, req); err != nil { + return + } + + 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 Pluggable Transports 2.0, draft 1 +// private auth method. +func socksAuthPT2(rw *bufio.ReadWriter, req *SocksRequest) (err error) { + // Read the authentication data. + var len uint32 + if len, err = socksReadUint32(rw); err != nil { + return + } + if len == 0 { + err = fmt.Errorf("PT 2.0 authentication data with 0 length") + return + } + var data []byte + if data, err = socksReadBytes(rw, int(len)); err != nil { + return + } + + var result string = string(data) + + // Parse the authentication data according to the PT 2.0 specification + if req.Args, err = ParsePT2ClientParameters(result); err != nil { + fmt.Println("Error parsing PT2 client parameters", err) + return + } + + 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 err = socksFlushBuffers(rw); err != nil { + return + } + + req.Target = fmt.Sprintf("%s:%d", host, port) + return +} + +// 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] = 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 +} + +// Send a SOCKS5 response code 0x00. +func sendSocks5ResponseGranted(w io.Writer) error { + return sendSocks5Response(w, socksRepSucceeded) +} + +// Send a SOCKS5 response with the provided failure reason. +func sendSocks5ResponseRejected(w io.Writer, reason byte) error { + return sendSocks5Response(w, reason) +} + +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 socksReadUint32(rw *bufio.ReadWriter) (uint32, error) { + buffer, err := socksReadBytes(rw, 4) + if err != nil { + return 0, err + } + + return binary.BigEndian.Uint32(buffer), nil +} + +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) |