mirror of https://github.com/subgraph/fw-daemon
borrow code from this git commit id 8897fe5b847b70fd54f19d1114d04e3bb67912d8 https://github.com/Yawning/or-ctl-filterpull/19/head
parent
6a7db8ba8c
commit
96375f4645
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* client.go - SOCSK5 client implementation.
|
||||||
|
*
|
||||||
|
* To the extent possible under law, Yawning Angel has waived all copyright and
|
||||||
|
* related or neighboring rights to or-ctl-filter, using the creative commons
|
||||||
|
* "cc0" public domain dedication. See LICENSE or
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redispatch dials the provided proxy and redispatches an existing request.
|
||||||
|
func Redispatch(proxyNet, proxyAddr string, req *Request) (conn net.Conn, bndAddr *Address, err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil && conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
conn, err = clientHandshake(proxyNet, proxyAddr, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
bndAddr, err = clientCmd(conn, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientHandshake(proxyNet, proxyAddr string, req *Request) (net.Conn, error) {
|
||||||
|
conn, err := net.Dial(proxyNet, proxyAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := conn.SetDeadline(time.Now().Add(requestTimeout)); err != nil {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
authMethod, err := clientNegotiateAuth(conn, req)
|
||||||
|
if err != nil {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
if err := clientAuthenticate(conn, req, authMethod); err != nil {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
if err := conn.SetDeadline(time.Time{}); err != nil {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientNegotiateAuth(conn net.Conn, req *Request) (byte, error) {
|
||||||
|
useRFC1929 := req.Auth.Uname != nil && req.Auth.Passwd != nil
|
||||||
|
// XXX: Validate uname/passwd lengths, though should always be valid.
|
||||||
|
|
||||||
|
var buf [3]byte
|
||||||
|
buf[0] = version
|
||||||
|
buf[1] = 1
|
||||||
|
if useRFC1929 {
|
||||||
|
buf[2] = authUsernamePassword
|
||||||
|
} else {
|
||||||
|
buf[2] = authNoneRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Write(buf[:]); err != nil {
|
||||||
|
return authNoAcceptableMethods, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp [2]byte
|
||||||
|
if _, err := io.ReadFull(conn, resp[:]); err != nil {
|
||||||
|
return authNoAcceptableMethods, err
|
||||||
|
}
|
||||||
|
if err := validateByte("version", resp[0], version); err != nil {
|
||||||
|
return authNoAcceptableMethods, err
|
||||||
|
}
|
||||||
|
if err := validateByte("method", resp[1], buf[2]); err != nil {
|
||||||
|
return authNoAcceptableMethods, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientAuthenticate(conn net.Conn, req *Request, authMethod byte) error {
|
||||||
|
switch authMethod {
|
||||||
|
case authNoneRequired:
|
||||||
|
case authUsernamePassword:
|
||||||
|
var buf []byte
|
||||||
|
buf = append(buf, authRFC1929Ver)
|
||||||
|
buf = append(buf, byte(len(req.Auth.Uname)))
|
||||||
|
buf = append(buf, req.Auth.Uname...)
|
||||||
|
buf = append(buf, byte(len(req.Auth.Passwd)))
|
||||||
|
buf = append(buf, req.Auth.Passwd...)
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp [2]byte
|
||||||
|
if _, err := io.ReadFull(conn, resp[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateByte("version", resp[0], authRFC1929Ver); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateByte("status", resp[1], authRFC1929Success); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown authentication method: 0x%02x", authMethod))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientCmd(conn net.Conn, req *Request) (*Address, error) {
|
||||||
|
var buf []byte
|
||||||
|
buf = append(buf, version)
|
||||||
|
buf = append(buf, byte(req.Cmd))
|
||||||
|
buf = append(buf, rsv)
|
||||||
|
buf = append(buf, req.Addr.raw...)
|
||||||
|
if _, err := conn.Write(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var respHdr [3]byte
|
||||||
|
if _, err := io.ReadFull(conn, respHdr[:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateByte("version", respHdr[0], version); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := validateByte("rep", respHdr[1], byte(ReplySucceeded)); err != nil {
|
||||||
|
return nil, clientError(respHdr[1])
|
||||||
|
}
|
||||||
|
if err := validateByte("rsv", respHdr[2], rsv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bndAddr Address
|
||||||
|
if err := bndAddr.read(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.SetDeadline(time.Time{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bndAddr, nil
|
||||||
|
}
|
@ -0,0 +1,272 @@
|
|||||||
|
/*
|
||||||
|
* common.go - SOCSK5 common definitons/routines.
|
||||||
|
*
|
||||||
|
* To the extent possible under law, Yawning Angel has waived all copyright and
|
||||||
|
* related or neighboring rights to or-ctl-filter, using the creative commons
|
||||||
|
* "cc0" public domain dedication. See LICENSE or
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package socks5 implements a SOCKS5 client/server. For more information see
|
||||||
|
// RFC 1928 and RFC 1929.
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// * GSSAPI authentication, is NOT supported.
|
||||||
|
// * The authentication provided by the client is always accepted.
|
||||||
|
// * A lot of the code is shamelessly stolen from obfs4proxy.
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = 0x05
|
||||||
|
rsv = 0x00
|
||||||
|
|
||||||
|
atypIPv4 = 0x01
|
||||||
|
atypDomainName = 0x03
|
||||||
|
atypIPv6 = 0x04
|
||||||
|
|
||||||
|
authNoneRequired = 0x00
|
||||||
|
authUsernamePassword = 0x02
|
||||||
|
authNoAcceptableMethods = 0xff
|
||||||
|
|
||||||
|
inboundTimeout = 5 * time.Second
|
||||||
|
requestTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidAtyp = errors.New("invalid address type")
|
||||||
|
|
||||||
|
// ReplyCode is a SOCKS 5 reply code.
|
||||||
|
type ReplyCode byte
|
||||||
|
|
||||||
|
// The various SOCKS 5 reply codes from RFC 1928.
|
||||||
|
const (
|
||||||
|
ReplySucceeded ReplyCode = iota
|
||||||
|
ReplyGeneralFailure
|
||||||
|
ReplyConnectionNotAllowed
|
||||||
|
ReplyNetworkUnreachable
|
||||||
|
ReplyHostUnreachable
|
||||||
|
ReplyConnectionRefused
|
||||||
|
ReplyTTLExpired
|
||||||
|
ReplyCommandNotSupported
|
||||||
|
ReplyAddressNotSupported
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command is a SOCKS 5 command.
|
||||||
|
type Command byte
|
||||||
|
|
||||||
|
// The various SOCKS 5 commands.
|
||||||
|
const (
|
||||||
|
CommandConnect Command = 0x01
|
||||||
|
CommandTorResolve Command = 0xf0
|
||||||
|
CommandTorResolvePTR Command = 0xf1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Address is a SOCKS 5 address + port.
|
||||||
|
type Address struct {
|
||||||
|
raw []byte
|
||||||
|
addrStr string
|
||||||
|
portStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString parses the provided "host:port" format address and populates the
|
||||||
|
// Address fields.
|
||||||
|
func (addr *Address) FromString(addrStr string) (err error) {
|
||||||
|
addr.addrStr, addr.portStr, err = net.SplitHostPort(addrStr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var raw []byte
|
||||||
|
if ip := net.ParseIP(addr.addrStr); ip != nil {
|
||||||
|
if v4Addr := ip.To4(); v4Addr != nil {
|
||||||
|
raw = append(raw, atypIPv4)
|
||||||
|
raw = append(raw, v4Addr...)
|
||||||
|
} else if v6Addr := ip.To16(); v6Addr != nil {
|
||||||
|
raw = append(raw, atypIPv6)
|
||||||
|
raw = append(raw, v6Addr...)
|
||||||
|
} else {
|
||||||
|
return errors.New("unsupported IP address type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Must be a FQDN.
|
||||||
|
if len(addr.addrStr) > 255 {
|
||||||
|
return fmt.Errorf("invalid FQDN, len > 255 bytes (%d bytes)", len(addr.addrStr))
|
||||||
|
}
|
||||||
|
raw = append(raw, atypDomainName)
|
||||||
|
raw = append(raw, addr.addrStr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var port uint64
|
||||||
|
if port, err = strconv.ParseUint(addr.portStr, 10, 16); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raw = append(raw, byte(port>>8))
|
||||||
|
raw = append(raw, byte(port&0xff))
|
||||||
|
|
||||||
|
addr.raw = raw
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the address, in "host:port"
|
||||||
|
// format.
|
||||||
|
func (addr *Address) String() string {
|
||||||
|
return addr.addrStr + ":" + addr.portStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostPort returns the string representation of the addess, split into the
|
||||||
|
// host and port components.
|
||||||
|
func (addr *Address) HostPort() (string, string) {
|
||||||
|
return addr.addrStr, addr.portStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *Address) read(conn net.Conn) (err error) {
|
||||||
|
// The address looks like:
|
||||||
|
// uint8_t atyp
|
||||||
|
// uint8_t addr[] (Length depends on atyp)
|
||||||
|
// uint16_t port
|
||||||
|
|
||||||
|
// Read the atype.
|
||||||
|
var atyp byte
|
||||||
|
if atyp, err = readByte(conn); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr.raw = append(addr.raw, atyp)
|
||||||
|
|
||||||
|
// Read the address.
|
||||||
|
var rawAddr []byte
|
||||||
|
switch atyp {
|
||||||
|
case atypIPv4:
|
||||||
|
rawAddr = make([]byte, net.IPv4len)
|
||||||
|
if _, err = io.ReadFull(conn, rawAddr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v4Addr := net.IPv4(rawAddr[0], rawAddr[1], rawAddr[2], rawAddr[3])
|
||||||
|
addr.addrStr = v4Addr.String()
|
||||||
|
case atypDomainName:
|
||||||
|
var alen byte
|
||||||
|
if alen, err = readByte(conn); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if alen == 0 {
|
||||||
|
return fmt.Errorf("domain name with 0 length")
|
||||||
|
}
|
||||||
|
rawAddr = make([]byte, alen)
|
||||||
|
addr.raw = append(addr.raw, alen)
|
||||||
|
if _, err = io.ReadFull(conn, rawAddr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr.addrStr = string(rawAddr)
|
||||||
|
case atypIPv6:
|
||||||
|
rawAddr = make([]byte, net.IPv6len)
|
||||||
|
if _, err = io.ReadFull(conn, rawAddr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v6Addr := make(net.IP, net.IPv6len)
|
||||||
|
copy(v6Addr[:], rawAddr)
|
||||||
|
addr.addrStr = fmt.Sprintf("[%s]", v6Addr.String())
|
||||||
|
default:
|
||||||
|
return errInvalidAtyp
|
||||||
|
}
|
||||||
|
addr.raw = append(addr.raw, rawAddr...)
|
||||||
|
|
||||||
|
// Read the port.
|
||||||
|
var rawPort [2]byte
|
||||||
|
if _, err = io.ReadFull(conn, rawPort[:]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
port := int(rawPort[0])<<8 | int(rawPort[1])
|
||||||
|
addr.portStr = fmt.Sprintf("%d", port)
|
||||||
|
addr.raw = append(addr.raw, rawPort[:]...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorToReplyCode converts an error to the "best" reply code.
|
||||||
|
func ErrorToReplyCode(err error) ReplyCode {
|
||||||
|
if cErr, ok := err.(clientError); ok {
|
||||||
|
return ReplyCode(cErr)
|
||||||
|
}
|
||||||
|
opErr, ok := err.(*net.OpError)
|
||||||
|
if !ok {
|
||||||
|
return ReplyGeneralFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
errno, ok := opErr.Err.(syscall.Errno)
|
||||||
|
if !ok {
|
||||||
|
return ReplyGeneralFailure
|
||||||
|
}
|
||||||
|
switch errno {
|
||||||
|
case syscall.EADDRNOTAVAIL:
|
||||||
|
return ReplyAddressNotSupported
|
||||||
|
case syscall.ETIMEDOUT:
|
||||||
|
return ReplyTTLExpired
|
||||||
|
case syscall.ENETUNREACH:
|
||||||
|
return ReplyNetworkUnreachable
|
||||||
|
case syscall.EHOSTUNREACH:
|
||||||
|
return ReplyHostUnreachable
|
||||||
|
case syscall.ECONNREFUSED, syscall.ECONNRESET:
|
||||||
|
return ReplyConnectionRefused
|
||||||
|
default:
|
||||||
|
return ReplyGeneralFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request describes a SOCKS 5 request.
|
||||||
|
type Request struct {
|
||||||
|
Auth AuthInfo
|
||||||
|
Cmd Command
|
||||||
|
Addr Address
|
||||||
|
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientError ReplyCode
|
||||||
|
|
||||||
|
func (e clientError) Error() string {
|
||||||
|
switch ReplyCode(e) {
|
||||||
|
case ReplySucceeded:
|
||||||
|
return "socks5: succeeded"
|
||||||
|
case ReplyGeneralFailure:
|
||||||
|
return "socks5: general failure"
|
||||||
|
case ReplyConnectionNotAllowed:
|
||||||
|
return "socks5: connection not allowed"
|
||||||
|
case ReplyNetworkUnreachable:
|
||||||
|
return "socks5: network unreachable"
|
||||||
|
case ReplyHostUnreachable:
|
||||||
|
return "socks5: host unreachable"
|
||||||
|
case ReplyConnectionRefused:
|
||||||
|
return "socks5: connection refused"
|
||||||
|
case ReplyTTLExpired:
|
||||||
|
return "socks5: ttl expired"
|
||||||
|
case ReplyCommandNotSupported:
|
||||||
|
return "socks5: command not supported"
|
||||||
|
case ReplyAddressNotSupported:
|
||||||
|
return "socks5: address not supported"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("socks5: reply code: 0x%02x", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readByte(conn net.Conn) (byte, error) {
|
||||||
|
var tmp [1]byte
|
||||||
|
if _, err := conn.Read(tmp[:]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return tmp[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateByte(descr string, val, expected byte) error {
|
||||||
|
if val != expected {
|
||||||
|
return fmt.Errorf("message field '%s' was 0x%02x (expected 0x%02x)", descr, val, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* server.go - SOCSK5 server implementation.
|
||||||
|
*
|
||||||
|
* To the extent possible under law, Yawning Angel has waived all copyright and
|
||||||
|
* related or neighboring rights to or-ctl-filter, using the creative commons
|
||||||
|
* "cc0" public domain dedication. See LICENSE or
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handshake attempts to handle a incoming client handshake over the provided
|
||||||
|
// connection and receive the SOCKS5 request. The routine handles sending
|
||||||
|
// appropriate errors if applicable, but will not close the connection.
|
||||||
|
func Handshake(conn net.Conn) (*Request, error) {
|
||||||
|
// Arm the handshake timeout.
|
||||||
|
var err error
|
||||||
|
if err = conn.SetDeadline(time.Now().Add(inboundTimeout)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Disarm the handshake timeout, only propagate the error if
|
||||||
|
// the handshake was successful.
|
||||||
|
nerr := conn.SetDeadline(time.Time{})
|
||||||
|
if err == nil {
|
||||||
|
err = nerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
req := new(Request)
|
||||||
|
req.conn = conn
|
||||||
|
|
||||||
|
// Negotiate the protocol version and authentication method.
|
||||||
|
var method byte
|
||||||
|
if method, err = req.negotiateAuth(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate if neccecary.
|
||||||
|
if err = req.authenticate(method); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the client command.
|
||||||
|
if err = req.readCommand(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply sends a SOCKS5 reply to the corresponding request. The BND.ADDR and
|
||||||
|
// BND.PORT fields are always set to an address/port corresponding to
|
||||||
|
// "0.0.0.0:0".
|
||||||
|
func (req *Request) Reply(code ReplyCode) error {
|
||||||
|
return req.ReplyAddr(code, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplyAddr sends a SOCKS5 reply to the corresponding request. The BND.ADDR
|
||||||
|
// and BND.PORT fields are specified by addr, or "0.0.0.0:0" if not provided.
|
||||||
|
func (req *Request) ReplyAddr(code ReplyCode, addr *Address) error {
|
||||||
|
// The server sends a reply message.
|
||||||
|
// uint8_t ver (0x05)
|
||||||
|
// uint8_t rep
|
||||||
|
// uint8_t rsv (0x00)
|
||||||
|
// uint8_t atyp
|
||||||
|
// uint8_t bnd_addr[]
|
||||||
|
// uint16_t bnd_port
|
||||||
|
|
||||||
|
resp := []byte{version, byte(code), rsv}
|
||||||
|
if addr == nil {
|
||||||
|
var nilAddr [net.IPv4len + 2]byte
|
||||||
|
resp = append(resp, atypIPv4)
|
||||||
|
resp = append(resp, nilAddr[:]...)
|
||||||
|
} else {
|
||||||
|
resp = append(resp, addr.raw...)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := req.conn.Write(resp[:])
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Request) negotiateAuth() (byte, error) {
|
||||||
|
// The client sends a version identifier/selection message.
|
||||||
|
// uint8_t ver (0x05)
|
||||||
|
// uint8_t nmethods (>= 1).
|
||||||
|
// uint8_t methods[nmethods]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if err = req.readByteVerify("version", version); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the number of methods, and the methods.
|
||||||
|
var nmethods byte
|
||||||
|
method := byte(authNoAcceptableMethods)
|
||||||
|
if nmethods, err = req.readByte(); err != nil {
|
||||||
|
return method, err
|
||||||
|
}
|
||||||
|
methods := make([]byte, nmethods)
|
||||||
|
if _, err := io.ReadFull(req.conn, methods); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick the best authentication method, prioritizing authenticating
|
||||||
|
// over not if both options are present.
|
||||||
|
if bytes.IndexByte(methods, authUsernamePassword) != -1 {
|
||||||
|
method = authUsernamePassword
|
||||||
|
} else if bytes.IndexByte(methods, authNoneRequired) != -1 {
|
||||||
|
method = authNoneRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server sends a method selection message.
|
||||||
|
// uint8_t ver (0x05)
|
||||||
|
// uint8_t method
|
||||||
|
msg := []byte{version, method}
|
||||||
|
if _, err = req.conn.Write(msg); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return method, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Request) authenticate(method byte) error {
|
||||||
|
switch method {
|
||||||
|
case authNoneRequired:
|
||||||
|
return nil
|
||||||
|
case authUsernamePassword:
|
||||||
|
return req.authRFC1929()
|
||||||
|
case authNoAcceptableMethods:
|
||||||
|
return fmt.Errorf("no acceptable authentication methods")
|
||||||
|
default:
|
||||||
|
// This should never happen as only supported auth methods should be
|
||||||
|
// negotiated.
|
||||||
|
return fmt.Errorf("negotiated unsupported method 0x%02x", method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Request) readCommand() error {
|
||||||
|
// The client sends the request details.
|
||||||
|
// uint8_t ver (0x05)
|
||||||
|
// uint8_t cmd
|
||||||
|
// uint8_t rsv (0x00)
|
||||||
|
// uint8_t atyp
|
||||||
|
// uint8_t dst_addr[]
|
||||||
|
// uint16_t dst_port
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var cmd byte
|
||||||
|
if err = req.readByteVerify("version", version); err != nil {
|
||||||
|
req.Reply(ReplyGeneralFailure)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cmd, err = req.readByte(); err != nil {
|
||||||
|
req.Reply(ReplyGeneralFailure)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch Command(cmd) {
|
||||||
|
case CommandConnect, CommandTorResolve, CommandTorResolvePTR:
|
||||||
|
req.Cmd = Command(cmd)
|
||||||
|
default:
|
||||||
|
req.Reply(ReplyCommandNotSupported)
|
||||||
|
return fmt.Errorf("unsupported SOCKS command: 0x%02x", cmd)
|
||||||
|
}
|
||||||
|
if err = req.readByteVerify("reserved", rsv); err != nil {
|
||||||
|
req.Reply(ReplyGeneralFailure)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the destination address/port.
|
||||||
|
err = req.Addr.read(req.conn)
|
||||||
|
if err == errInvalidAtyp {
|
||||||
|
req.Reply(ReplyAddressNotSupported)
|
||||||
|
} else if err != nil {
|
||||||
|
req.Reply(ReplyGeneralFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Request) readByte() (byte, error) {
|
||||||
|
return readByte(req.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Request) readByteVerify(descr string, expected byte) error {
|
||||||
|
val, err := req.readByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validateByte(descr, val, expected)
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* server_rfc1929.go - SOCSK 5 server authentication.
|
||||||
|
*
|
||||||
|
* To the extent possible under law, Yawning Angel has waived all copyright and
|
||||||
|
* related or neighboring rights to or-ctl-filter, using the creative commons
|
||||||
|
* "cc0" public domain dedication. See LICENSE or
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
authRFC1929Ver = 0x01
|
||||||
|
authRFC1929Success = 0x00
|
||||||
|
authRFC1929Fail = 0x01
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthInfo is the RFC 1929 Username/Password authentication data.
|
||||||
|
type AuthInfo struct {
|
||||||
|
Uname []byte
|
||||||
|
Passwd []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *Request) authRFC1929() (err error) {
|
||||||
|
sendErrResp := func() {
|
||||||
|
// Swallow write/flush errors, the auth failure is the relevant error.
|
||||||
|
resp := []byte{authRFC1929Ver, authRFC1929Fail}
|
||||||
|
req.conn.Write(resp[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// The client sends a Username/Password request.
|
||||||
|
// uint8_t ver (0x01)
|
||||||
|
// uint8_t ulen (>= 1)
|
||||||
|
// uint8_t uname[ulen]
|
||||||
|
// uint8_t plen (>= 1)
|
||||||
|
// uint8_t passwd[plen]
|
||||||
|
|
||||||
|
if err = req.readByteVerify("auth version", authRFC1929Ver); err != nil {
|
||||||
|
sendErrResp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the username.
|
||||||
|
var ulen byte
|
||||||
|
if ulen, err = req.readByte(); err != nil {
|
||||||
|
sendErrResp()
|
||||||
|
return
|
||||||
|
} else if ulen < 1 {
|
||||||
|
sendErrResp()
|
||||||
|
return fmt.Errorf("username with 0 length")
|
||||||
|
}
|
||||||
|
uname := make([]byte, ulen)
|
||||||
|
if _, err = io.ReadFull(req.conn, uname); err != nil {
|
||||||
|
sendErrResp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the password.
|
||||||
|
var plen byte
|
||||||
|
if plen, err = req.readByte(); err != nil {
|
||||||
|
sendErrResp()
|
||||||
|
return
|
||||||
|
} else if plen < 1 {
|
||||||
|
sendErrResp()
|
||||||
|
return fmt.Errorf("password with 0 length")
|
||||||
|
}
|
||||||
|
passwd := make([]byte, plen)
|
||||||
|
if _, err = io.ReadFull(req.conn, passwd); err != nil {
|
||||||
|
sendErrResp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Auth.Uname = uname
|
||||||
|
req.Auth.Passwd = passwd
|
||||||
|
|
||||||
|
resp := []byte{authRFC1929Ver, authRFC1929Success}
|
||||||
|
_, err = req.conn.Write(resp[:])
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in new issue