mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-31 07:57:22 +02:00
380 lines
8.1 KiB
Go
380 lines
8.1 KiB
Go
package socks5
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
errStringTooLong = errors.New("string too long")
|
|
errUserAuthFailed = errors.New("user authentication failed")
|
|
errNoSupportedAuth = errors.New("no supported authentication mechanism")
|
|
errUnrecognizedAddrType = errors.New("unrecognized address type")
|
|
)
|
|
|
|
const (
|
|
maxUdpPacket = math.MaxUint16 - 28
|
|
// maxHeaderSize is the maximum size of SOCKS5 header (3 bytes) plus address (up to 255 bytes for domain name)
|
|
// 3 bytes (header: RSV, RSV, FRAG) + 1 byte (ATYP) + 1 byte (domain length) + 255 bytes (domain) + 2 bytes (port) = 262 bytes
|
|
maxHeaderSize = 3 + 1 + 1 + 255 + 2
|
|
)
|
|
|
|
const (
|
|
socks5Version = 0x05
|
|
)
|
|
|
|
const (
|
|
ConnectCommand Command = 0x01
|
|
BindCommand Command = 0x02
|
|
AssociateCommand Command = 0x03
|
|
)
|
|
|
|
// Command is a SOCKS Command.
|
|
type Command byte
|
|
|
|
func (cmd Command) String() string {
|
|
switch cmd {
|
|
case ConnectCommand:
|
|
return "socks connect"
|
|
case BindCommand:
|
|
return "socks bind"
|
|
case AssociateCommand:
|
|
return "socks associate"
|
|
default:
|
|
return "socks " + strconv.Itoa(int(cmd))
|
|
}
|
|
}
|
|
|
|
const (
|
|
successReply reply = 0x00
|
|
serverFailure reply = 0x01
|
|
ruleFailure reply = 0x02
|
|
networkUnreachable reply = 0x03
|
|
hostUnreachable reply = 0x04
|
|
connectionRefused reply = 0x05
|
|
ttlExpired reply = 0x06
|
|
commandNotSupported reply = 0x07
|
|
addrTypeNotSupported reply = 0x08
|
|
)
|
|
|
|
func errToReply(err error) reply {
|
|
if err == nil {
|
|
return successReply
|
|
}
|
|
msg := err.Error()
|
|
resp := hostUnreachable
|
|
if strings.Contains(msg, "refused") {
|
|
resp = connectionRefused
|
|
} else if strings.Contains(msg, "network is unreachable") {
|
|
resp = networkUnreachable
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// reply is a SOCKS Command reply code.
|
|
type reply byte
|
|
|
|
func (code reply) String() string {
|
|
switch code {
|
|
case successReply:
|
|
return "succeeded"
|
|
case serverFailure:
|
|
return "general SOCKS server failure"
|
|
case ruleFailure:
|
|
return "connection not allowed by ruleset"
|
|
case networkUnreachable:
|
|
return "network unreachable"
|
|
case hostUnreachable:
|
|
return "host unreachable"
|
|
case connectionRefused:
|
|
return "connection refused"
|
|
case ttlExpired:
|
|
return "TTL expired"
|
|
case commandNotSupported:
|
|
return "Command not supported"
|
|
case addrTypeNotSupported:
|
|
return "address type not supported"
|
|
default:
|
|
return "unknown code: " + strconv.Itoa(int(code))
|
|
}
|
|
}
|
|
|
|
const (
|
|
ipv4Address = 0x01
|
|
fqdnAddress = 0x03
|
|
ipv6Address = 0x04
|
|
)
|
|
|
|
// address is a SOCKS-specific address.
|
|
// Either Name or IP is used exclusively.
|
|
type address struct {
|
|
Name string // fully-qualified domain name
|
|
IP net.IP
|
|
Port int
|
|
}
|
|
|
|
func (a *address) Network() string { return "socks5" }
|
|
|
|
func (a *address) String() string {
|
|
if a == nil {
|
|
return "<nil>"
|
|
}
|
|
return a.Address()
|
|
}
|
|
|
|
// Address returns a string suitable to dial; prefer returning IP-based
|
|
// address, fallback to Name
|
|
func (a address) Address() string {
|
|
port := strconv.Itoa(a.Port)
|
|
if 0 != len(a.IP) {
|
|
return net.JoinHostPort(a.IP.String(), port)
|
|
}
|
|
return net.JoinHostPort(a.Name, port)
|
|
}
|
|
|
|
// authMethod is a SOCKS authentication method.
|
|
type authMethod byte
|
|
|
|
const (
|
|
noAuth authMethod = 0x00 // no authentication required
|
|
gssapiAuth authMethod = 0x01 // use GSSAPI
|
|
userAuth authMethod = 0x02 // use username/password
|
|
noAcceptable authMethod = 0xff // no acceptable authentication methods
|
|
)
|
|
|
|
const (
|
|
userAuthVersion = 0x01
|
|
authSuccess = 0x00
|
|
authFailure = 0x01
|
|
)
|
|
|
|
func readBytes(r io.Reader) ([]byte, error) {
|
|
var buf [1]byte
|
|
_, err := r.Read(buf[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bytes := make([]byte, buf[0])
|
|
_, err = io.ReadFull(r, bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bytes, nil
|
|
}
|
|
|
|
func writeBytes(w io.Writer, b []byte) error {
|
|
_, err := w.Write([]byte{byte(len(b))})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write(b)
|
|
return err
|
|
}
|
|
|
|
func readByte(r io.Reader) (byte, error) {
|
|
var buf [1]byte
|
|
_, err := r.Read(buf[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return buf[0], nil
|
|
}
|
|
|
|
func readAddr(r io.Reader) (*address, error) {
|
|
address := &address{}
|
|
|
|
var addrType [1]byte
|
|
if _, err := r.Read(addrType[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch addrType[0] {
|
|
case ipv4Address:
|
|
addr := make(net.IP, net.IPv4len)
|
|
if _, err := io.ReadFull(r, addr); err != nil {
|
|
return nil, err
|
|
}
|
|
address.IP = addr
|
|
case ipv6Address:
|
|
addr := make(net.IP, net.IPv6len)
|
|
if _, err := io.ReadFull(r, addr); err != nil {
|
|
return nil, err
|
|
}
|
|
address.IP = addr
|
|
case fqdnAddress:
|
|
if _, err := r.Read(addrType[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
addrLen := int(addrType[0])
|
|
fqdn := make([]byte, addrLen)
|
|
if _, err := io.ReadFull(r, fqdn); err != nil {
|
|
return nil, err
|
|
}
|
|
address.Name = string(fqdn)
|
|
default:
|
|
return nil, errUnrecognizedAddrType
|
|
}
|
|
var port [2]byte
|
|
if _, err := io.ReadFull(r, port[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
address.Port = int(binary.BigEndian.Uint16(port[:]))
|
|
return address, nil
|
|
}
|
|
|
|
func writeAddr(w io.Writer, addr *address) error {
|
|
if addr == nil {
|
|
_, err := w.Write([]byte{ipv4Address, 0, 0, 0, 0, 0, 0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
if addr.IP != nil {
|
|
if ip4 := addr.IP.To4(); ip4 != nil {
|
|
_, err := w.Write([]byte{ipv4Address})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write(ip4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if ip6 := addr.IP.To16(); ip6 != nil {
|
|
_, err := w.Write([]byte{ipv6Address})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write(ip6)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
_, err := w.Write([]byte{ipv4Address, 0, 0, 0, 0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if addr.Name != "" {
|
|
if len(addr.Name) > 255 {
|
|
return errStringTooLong
|
|
}
|
|
_, err := w.Write([]byte{fqdnAddress, byte(len(addr.Name))})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write([]byte(addr.Name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
_, err := w.Write([]byte{ipv4Address, 0, 0, 0, 0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var p [2]byte
|
|
binary.BigEndian.PutUint16(p[:], uint16(addr.Port))
|
|
_, err := w.Write(p[:])
|
|
return err
|
|
}
|
|
|
|
func writeAddrWithStr(w io.Writer, addr string) error {
|
|
host, port, err := splitHostPort(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ip := net.ParseIP(host); ip != nil {
|
|
return writeAddr(w, &address{IP: ip, Port: port})
|
|
}
|
|
return writeAddr(w, &address{Name: host, Port: port})
|
|
}
|
|
|
|
func splitHostPort(address string) (string, int, error) {
|
|
host, port, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
portnum, err := strconv.Atoi(port)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
if 0 > portnum || portnum > 0xffff {
|
|
return "", 0, errors.New("port number out of range " + port)
|
|
}
|
|
return host, portnum, nil
|
|
}
|
|
|
|
// isClosedConnError reports whether err is an error from use of a closed
|
|
// network connection.
|
|
func isClosedConnError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
str := err.Error()
|
|
if strings.Contains(str, "use of closed network connection") {
|
|
return true
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
if oe, ok := err.(*net.OpError); ok && oe.Op == "read" {
|
|
if se, ok := oe.Err.(*os.SyscallError); ok && se.Syscall == "wsarecv" {
|
|
const WSAECONNABORTED = 10053
|
|
const WSAECONNRESET = 10054
|
|
if n := errno(se.Err); n == WSAECONNRESET || n == WSAECONNABORTED {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func errno(v error) uintptr {
|
|
if rv := reflect.ValueOf(v); rv.Kind() == reflect.Uintptr {
|
|
return uintptr(rv.Uint())
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// tunnel create tunnels for two io.ReadWriteCloser
|
|
func tunnel(ctx context.Context, c1, c2 io.ReadWriteCloser, buf1, buf2 []byte) error {
|
|
errCh := make(chan error, 2)
|
|
go func() {
|
|
_, err := io.CopyBuffer(c1, c2, buf1)
|
|
errCh <- err
|
|
}()
|
|
go func() {
|
|
_, err := io.CopyBuffer(c2, c1, buf2)
|
|
errCh <- err
|
|
}()
|
|
defer func() {
|
|
_ = c1.Close()
|
|
_ = c2.Close()
|
|
}()
|
|
|
|
select {
|
|
case err := <-errCh:
|
|
return err
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
|
|
// BytesPool is an interface for getting and returning temporary
|
|
// bytes for use by io.CopyBuffer.
|
|
type BytesPool interface {
|
|
Get() []byte
|
|
Put([]byte)
|
|
}
|