1
0
Fork 0

Adding authentication handlers

logger
Armon Dadgar 2014-01-23 11:27:48 -08:00
parent aa25f19bf1
commit 6c690b91b0
2 changed files with 207 additions and 0 deletions

126
auth.go Normal file
View File

@ -0,0 +1,126 @@
package socks5
import (
"fmt"
"io"
)
const (
noAuth = uint8(0)
noAcceptable = uint8(255)
userPassAuth = uint8(2)
userAuthVersion = uint8(1)
authSuccess = uint8(0)
authFailure = uint8(1)
)
var (
UserAuthFailed = fmt.Errorf("User authentication failed")
NoSupportedAuth = fmt.Errorf("No supported authentication mechanism")
)
// authenticate is used to handle connection authentication
func (s *Server) authenticate(conn io.Writer, bufConn io.Reader) error {
// Get the methods
methods, err := readMethods(bufConn)
if err != nil {
return fmt.Errorf("Failed to get auth methods: %v", err)
}
// Determine what is supported
supportUserPass := s.config.Credentials != nil
// Select a usable method
for _, method := range methods {
if method == noAuth && !supportUserPass {
return noAuthMode(conn)
}
if method == userPassAuth && supportUserPass {
return s.userPassAuth(conn, bufConn)
}
}
// No usable method found
return noAcceptableAuth(conn)
}
// userPassAuth is used to handle username/password based
// authentication
func (s *Server) userPassAuth(conn io.Writer, bufConn io.Reader) error {
// Tell the client to use user/pass auth
if _, err := conn.Write([]byte{socks5Version, userPassAuth}); err != nil {
return err
}
// Get the version and username length
header := []byte{0, 0}
if _, err := io.ReadAtLeast(bufConn, header, 2); err != nil {
return err
}
// Ensure we are compatible
if header[0] != userAuthVersion {
return fmt.Errorf("Unsupported auth version: %v", header[0])
}
// Get the user name
userLen := int(header[1])
user := make([]byte, userLen)
if _, err := io.ReadAtLeast(bufConn, user, userLen); err != nil {
return err
}
// Get the password length
if _, err := bufConn.Read(header[:1]); err != nil {
return err
}
// Get the password
passLen := int(header[0])
pass := make([]byte, passLen)
if _, err := io.ReadAtLeast(bufConn, pass, passLen); err != nil {
return err
}
// Verify the password
if s.config.Credentials.Valid(string(user), string(pass)) {
if _, err := conn.Write([]byte{userAuthVersion, authSuccess}); err != nil {
return err
}
} else {
if _, err := conn.Write([]byte{userAuthVersion, authFailure}); err != nil {
return err
}
return UserAuthFailed
}
// Done
return nil
}
// noAuth is used to handle the "No Authentication" mode
func noAuthMode(conn io.Writer) error {
_, err := conn.Write([]byte{socks5Version, noAuth})
return err
}
// noAcceptableAuth is used to handle when we have no eligible
// authentication mechanism
func noAcceptableAuth(conn io.Writer) error {
conn.Write([]byte{socks5Version, noAcceptable})
return NoSupportedAuth
}
// readMethods is used to read the number of methods
// and proceeding auth methods
func readMethods(r io.Reader) ([]byte, error) {
header := []byte{0}
if _, err := r.Read(header); err != nil {
return nil, err
}
numMethods := int(header[0])
methods := make([]byte, numMethods)
_, err := io.ReadAtLeast(r, methods, numMethods)
return methods, err
}

81
auth_test.go Normal file
View File

@ -0,0 +1,81 @@
package socks5
import (
"bytes"
"testing"
)
func TestNoAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, noAuth})
var resp bytes.Buffer
s := &Server{config: &Config{}}
if err := s.authenticate(&resp, req); err != nil {
t.Fatalf("err: %v", err)
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, noAuth}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Valid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, noAuth, userPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
s := &Server{config: &Config{Credentials: cred}}
if err := s.authenticate(&resp, req); err != nil {
t.Fatalf("err: %v", err)
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, userPassAuth, 1, authSuccess}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Invalid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, noAuth, userPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
s := &Server{config: &Config{Credentials: cred}}
if err := s.authenticate(&resp, req); err != UserAuthFailed {
t.Fatalf("err: %v", err)
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, userPassAuth, 1, authFailure}) {
t.Fatalf("bad: %v", out)
}
}
func TestNoSupportedAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, noAuth})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
s := &Server{config: &Config{Credentials: cred}}
if err := s.authenticate(&resp, req); err != NoSupportedAuth {
t.Fatalf("err: %v", err)
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, noAcceptable}) {
t.Fatalf("bad: %v", out)
}
}