forked from toolshed/abra
		
	
		
			
				
	
	
		
			318 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2018 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| // Package socks provides a SOCKS version 5 client implementation.
 | |
| //
 | |
| // SOCKS protocol version 5 is defined in RFC 1928.
 | |
| // Username/Password authentication for SOCKS version 5 is defined in
 | |
| // RFC 1929.
 | |
| package socks
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"io"
 | |
| 	"net"
 | |
| 	"strconv"
 | |
| )
 | |
| 
 | |
| // A Command represents a SOCKS command.
 | |
| type Command int
 | |
| 
 | |
| func (cmd Command) String() string {
 | |
| 	switch cmd {
 | |
| 	case CmdConnect:
 | |
| 		return "socks connect"
 | |
| 	case cmdBind:
 | |
| 		return "socks bind"
 | |
| 	default:
 | |
| 		return "socks " + strconv.Itoa(int(cmd))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // An AuthMethod represents a SOCKS authentication method.
 | |
| type AuthMethod int
 | |
| 
 | |
| // A Reply represents a SOCKS command reply code.
 | |
| type Reply int
 | |
| 
 | |
| func (code Reply) String() string {
 | |
| 	switch code {
 | |
| 	case StatusSucceeded:
 | |
| 		return "succeeded"
 | |
| 	case 0x01:
 | |
| 		return "general SOCKS server failure"
 | |
| 	case 0x02:
 | |
| 		return "connection not allowed by ruleset"
 | |
| 	case 0x03:
 | |
| 		return "network unreachable"
 | |
| 	case 0x04:
 | |
| 		return "host unreachable"
 | |
| 	case 0x05:
 | |
| 		return "connection refused"
 | |
| 	case 0x06:
 | |
| 		return "TTL expired"
 | |
| 	case 0x07:
 | |
| 		return "command not supported"
 | |
| 	case 0x08:
 | |
| 		return "address type not supported"
 | |
| 	default:
 | |
| 		return "unknown code: " + strconv.Itoa(int(code))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Wire protocol constants.
 | |
| const (
 | |
| 	Version5 = 0x05
 | |
| 
 | |
| 	AddrTypeIPv4 = 0x01
 | |
| 	AddrTypeFQDN = 0x03
 | |
| 	AddrTypeIPv6 = 0x04
 | |
| 
 | |
| 	CmdConnect Command = 0x01 // establishes an active-open forward proxy connection
 | |
| 	cmdBind    Command = 0x02 // establishes a passive-open forward proxy connection
 | |
| 
 | |
| 	AuthMethodNotRequired         AuthMethod = 0x00 // no authentication required
 | |
| 	AuthMethodUsernamePassword    AuthMethod = 0x02 // use username/password
 | |
| 	AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods
 | |
| 
 | |
| 	StatusSucceeded Reply = 0x00
 | |
| )
 | |
| 
 | |
| // An Addr represents a SOCKS-specific address.
 | |
| // Either Name or IP is used exclusively.
 | |
| type Addr struct {
 | |
| 	Name string // fully-qualified domain name
 | |
| 	IP   net.IP
 | |
| 	Port int
 | |
| }
 | |
| 
 | |
| func (a *Addr) Network() string { return "socks" }
 | |
| 
 | |
| func (a *Addr) String() string {
 | |
| 	if a == nil {
 | |
| 		return "<nil>"
 | |
| 	}
 | |
| 	port := strconv.Itoa(a.Port)
 | |
| 	if a.IP == nil {
 | |
| 		return net.JoinHostPort(a.Name, port)
 | |
| 	}
 | |
| 	return net.JoinHostPort(a.IP.String(), port)
 | |
| }
 | |
| 
 | |
| // A Conn represents a forward proxy connection.
 | |
| type Conn struct {
 | |
| 	net.Conn
 | |
| 
 | |
| 	boundAddr net.Addr
 | |
| }
 | |
| 
 | |
| // BoundAddr returns the address assigned by the proxy server for
 | |
| // connecting to the command target address from the proxy server.
 | |
| func (c *Conn) BoundAddr() net.Addr {
 | |
| 	if c == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return c.boundAddr
 | |
| }
 | |
| 
 | |
| // A Dialer holds SOCKS-specific options.
 | |
| type Dialer struct {
 | |
| 	cmd          Command // either CmdConnect or cmdBind
 | |
| 	proxyNetwork string  // network between a proxy server and a client
 | |
| 	proxyAddress string  // proxy server address
 | |
| 
 | |
| 	// ProxyDial specifies the optional dial function for
 | |
| 	// establishing the transport connection.
 | |
| 	ProxyDial func(context.Context, string, string) (net.Conn, error)
 | |
| 
 | |
| 	// AuthMethods specifies the list of request authentication
 | |
| 	// methods.
 | |
| 	// If empty, SOCKS client requests only AuthMethodNotRequired.
 | |
| 	AuthMethods []AuthMethod
 | |
| 
 | |
| 	// Authenticate specifies the optional authentication
 | |
| 	// function. It must be non-nil when AuthMethods is not empty.
 | |
| 	// It must return an error when the authentication is failed.
 | |
| 	Authenticate func(context.Context, io.ReadWriter, AuthMethod) error
 | |
| }
 | |
| 
 | |
| // DialContext connects to the provided address on the provided
 | |
| // network.
 | |
| //
 | |
| // The returned error value may be a net.OpError. When the Op field of
 | |
| // net.OpError contains "socks", the Source field contains a proxy
 | |
| // server address and the Addr field contains a command target
 | |
| // address.
 | |
| //
 | |
| // See func Dial of the net package of standard library for a
 | |
| // description of the network and address parameters.
 | |
| func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
 | |
| 	if err := d.validateTarget(network, address); err != nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
 | |
| 	}
 | |
| 	if ctx == nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
 | |
| 	}
 | |
| 	var err error
 | |
| 	var c net.Conn
 | |
| 	if d.ProxyDial != nil {
 | |
| 		c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress)
 | |
| 	} else {
 | |
| 		var dd net.Dialer
 | |
| 		c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
 | |
| 	}
 | |
| 	a, err := d.connect(ctx, c, address)
 | |
| 	if err != nil {
 | |
| 		c.Close()
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
 | |
| 	}
 | |
| 	return &Conn{Conn: c, boundAddr: a}, nil
 | |
| }
 | |
| 
 | |
| // DialWithConn initiates a connection from SOCKS server to the target
 | |
| // network and address using the connection c that is already
 | |
| // connected to the SOCKS server.
 | |
| //
 | |
| // It returns the connection's local address assigned by the SOCKS
 | |
| // server.
 | |
| func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) {
 | |
| 	if err := d.validateTarget(network, address); err != nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
 | |
| 	}
 | |
| 	if ctx == nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}
 | |
| 	}
 | |
| 	a, err := d.connect(ctx, c, address)
 | |
| 	if err != nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
 | |
| 	}
 | |
| 	return a, nil
 | |
| }
 | |
| 
 | |
| // Dial connects to the provided address on the provided network.
 | |
| //
 | |
| // Unlike DialContext, it returns a raw transport connection instead
 | |
| // of a forward proxy connection.
 | |
| //
 | |
| // Deprecated: Use DialContext or DialWithConn instead.
 | |
| func (d *Dialer) Dial(network, address string) (net.Conn, error) {
 | |
| 	if err := d.validateTarget(network, address); err != nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
 | |
| 	}
 | |
| 	var err error
 | |
| 	var c net.Conn
 | |
| 	if d.ProxyDial != nil {
 | |
| 		c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress)
 | |
| 	} else {
 | |
| 		c, err = net.Dial(d.proxyNetwork, d.proxyAddress)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		proxy, dst, _ := d.pathAddrs(address)
 | |
| 		return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}
 | |
| 	}
 | |
| 	if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil {
 | |
| 		c.Close()
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return c, nil
 | |
| }
 | |
| 
 | |
| func (d *Dialer) validateTarget(network, address string) error {
 | |
| 	switch network {
 | |
| 	case "tcp", "tcp6", "tcp4":
 | |
| 	default:
 | |
| 		return errors.New("network not implemented")
 | |
| 	}
 | |
| 	switch d.cmd {
 | |
| 	case CmdConnect, cmdBind:
 | |
| 	default:
 | |
| 		return errors.New("command not implemented")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {
 | |
| 	for i, s := range []string{d.proxyAddress, address} {
 | |
| 		host, port, err := splitHostPort(s)
 | |
| 		if err != nil {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 		a := &Addr{Port: port}
 | |
| 		a.IP = net.ParseIP(host)
 | |
| 		if a.IP == nil {
 | |
| 			a.Name = host
 | |
| 		}
 | |
| 		if i == 0 {
 | |
| 			proxy = a
 | |
| 		} else {
 | |
| 			dst = a
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // NewDialer returns a new Dialer that dials through the provided
 | |
| // proxy server's network and address.
 | |
| func NewDialer(network, address string) *Dialer {
 | |
| 	return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect}
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	authUsernamePasswordVersion = 0x01
 | |
| 	authStatusSucceeded         = 0x00
 | |
| )
 | |
| 
 | |
| // UsernamePassword are the credentials for the username/password
 | |
| // authentication method.
 | |
| type UsernamePassword struct {
 | |
| 	Username string
 | |
| 	Password string
 | |
| }
 | |
| 
 | |
| // Authenticate authenticates a pair of username and password with the
 | |
| // proxy server.
 | |
| func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error {
 | |
| 	switch auth {
 | |
| 	case AuthMethodNotRequired:
 | |
| 		return nil
 | |
| 	case AuthMethodUsernamePassword:
 | |
| 		if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) > 255 {
 | |
| 			return errors.New("invalid username/password")
 | |
| 		}
 | |
| 		b := []byte{authUsernamePasswordVersion}
 | |
| 		b = append(b, byte(len(up.Username)))
 | |
| 		b = append(b, up.Username...)
 | |
| 		b = append(b, byte(len(up.Password)))
 | |
| 		b = append(b, up.Password...)
 | |
| 		// TODO(mikio): handle IO deadlines and cancelation if
 | |
| 		// necessary
 | |
| 		if _, err := rw.Write(b); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if _, err := io.ReadFull(rw, b[:2]); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if b[0] != authUsernamePasswordVersion {
 | |
| 			return errors.New("invalid username/password version")
 | |
| 		}
 | |
| 		if b[1] != authStatusSucceeded {
 | |
| 			return errors.New("username/password authentication failed")
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 	return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))
 | |
| }
 |