70 lines
2.0 KiB
Go
70 lines
2.0 KiB
Go
|
|
package socks5
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
ErrSubnegotiationVersionNotSupported = errors.New("subnegotiation version not supported")
|
||
|
|
ErrUsernameNotValid = errors.New("username not valid")
|
||
|
|
ErrPasswordNotValid = errors.New("password not valid")
|
||
|
|
)
|
||
|
|
|
||
|
|
// See https://datatracker.ietf.org/doc/html/rfc1929#section-2
|
||
|
|
func usernamePasswordSubnegotiate(conn io.ReadWriter, username, password string) (err error) {
|
||
|
|
status := byte(1)
|
||
|
|
const defaultVersion = byte(1)
|
||
|
|
|
||
|
|
const headerLength = 2
|
||
|
|
var header [headerLength]byte
|
||
|
|
_, err = io.ReadFull(conn, header[:])
|
||
|
|
if err != nil {
|
||
|
|
_, _ = conn.Write([]byte{defaultVersion, status})
|
||
|
|
return fmt.Errorf("reading header: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if header[0] != authUsernamePasswordSubNegotiation1 {
|
||
|
|
_, _ = conn.Write([]byte{defaultVersion, status})
|
||
|
|
return fmt.Errorf("%w: %d", ErrSubnegotiationVersionNotSupported, header[0])
|
||
|
|
}
|
||
|
|
version := header[0]
|
||
|
|
|
||
|
|
usernameBytes := make([]byte, header[1])
|
||
|
|
_, err = io.ReadFull(conn, usernameBytes)
|
||
|
|
if err != nil {
|
||
|
|
_, _ = conn.Write([]byte{version, status})
|
||
|
|
return fmt.Errorf("reading username bytes: %w", err)
|
||
|
|
} else if username != string(usernameBytes) {
|
||
|
|
_, _ = conn.Write([]byte{version, status})
|
||
|
|
return fmt.Errorf("%w: %s", ErrUsernameNotValid, string(usernameBytes))
|
||
|
|
}
|
||
|
|
|
||
|
|
const passwordHeaderLength = 1
|
||
|
|
passwordHeader := make([]byte, passwordHeaderLength)
|
||
|
|
_, err = io.ReadFull(conn, passwordHeader[:])
|
||
|
|
if err != nil {
|
||
|
|
_, _ = conn.Write([]byte{version, status})
|
||
|
|
return fmt.Errorf("reading password length: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
passwordBytes := make([]byte, passwordHeader[0])
|
||
|
|
_, err = io.ReadFull(conn, passwordBytes)
|
||
|
|
if err != nil {
|
||
|
|
_, _ = conn.Write([]byte{version, status})
|
||
|
|
return fmt.Errorf("reading password bytes: %w", err)
|
||
|
|
} else if password != string(passwordBytes) {
|
||
|
|
_, _ = conn.Write([]byte{version, status})
|
||
|
|
return fmt.Errorf("%w: %s", ErrPasswordNotValid, string(passwordBytes))
|
||
|
|
}
|
||
|
|
|
||
|
|
status = 0
|
||
|
|
_, err = conn.Write([]byte{version, status})
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("writing success status: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|