From e91d5f4f5980e74b27ce1cca1c7837fa8ec3f5f2 Mon Sep 17 00:00:00 2001 From: Stanislav Gumeniuk Date: Mon, 28 May 2018 21:35:07 +0300 Subject: [PATCH] [add] auth --- .gometalinter.json | 41 +++++++++++++++++ BENCHMARKS.md | 4 +- Makefile | 5 +- README.md | 30 +++++++++--- config.toml | 4 +- proxy/app.go | 25 +++++++++- proxy/auth.go | 32 ++++++++----- proxy/auth_test.go | 33 +++++++------- proxy/command.go | 2 +- proxy/connection.go | 108 +++++++++++++++++++++++++++++++------------- proxy/request.go | 2 +- proxy/response.go | 8 ++-- proxy/server.go | 20 +++++++- 13 files changed, 235 insertions(+), 79 deletions(-) create mode 100644 .gometalinter.json diff --git a/.gometalinter.json b/.gometalinter.json new file mode 100644 index 0000000..d6100a3 --- /dev/null +++ b/.gometalinter.json @@ -0,0 +1,41 @@ +{ + "Vendor": true, + "Deadline": "2m", + "Sort": [ + "linter", + "severity", + "path", + "line" + ], + "Exclude": [ + "vendor" + ], + "EnableGC": true, + "Linters": { + "nakedret": { + "Command": "nakedret", + "Pattern": "^(?P.*?\\.go):(?P\\d+)\\s*(?P.*)$" + } + }, + "WarnUnmatchedDirective": true, + "DisableAll": true, + "Enable": [ + "deadcode", + "gocyclo", + "gofmt", + "goimports", + "golint", + "gosimple", + "ineffassign", + "interfacer", + "lll", + "misspell", + "nakedret", + "unconvert", + "unparam", + "unused", + "vet" + ], + "Cyclo": 16, + "LineLength": 100 +} \ No newline at end of file diff --git a/BENCHMARKS.md b/BENCHMARKS.md index f850720..b228fe3 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -4,7 +4,7 @@ go test -bench=. -benchmem `go list ./... | grep -v vendor` goos: darwin goarch: amd64 pkg: github.com/vigo5190/go-socks5/proxy -BenchmarkProxy_Start-8 10000 161408 ns/op 5131 B/op 66 allocs/op -BenchmarkAuth_Auth-8 3000000 441 ns/op 104 B/op 4 allocs/op +BenchmarkProxy_Start-8 10000 145070 ns/op 5130 B/op 66 allocs/op +BenchmarkAuth_Auth-8 3000000 499 ns/op 96 B/op 3 allocs/op PASS ``` \ No newline at end of file diff --git a/Makefile b/Makefile index dd9f55e..dca7d37 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,9 @@ vet: lint: golint $(GOFILES) +metalint: + gometalinter ./... + docker: docker build -t $(IMAGE) . @@ -36,4 +39,4 @@ docker-push: travis: lint vet test echo "done all" -pre: fmt lint vet test bench \ No newline at end of file +pre: fmt lint vet metalint test bench \ No newline at end of file diff --git a/README.md b/README.md index 1745f59..23744c8 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,35 @@ How to use docker run -d -p 5190:8008 vigo5190/gosocks5 ``` -How to use (not docker) ------------------------- - -Build: +How to build (local) +---- ```bash make ``` -Run: +Configuration +-------------- + +By default your app run on `0.0.0.0:8008` without auth + +How to get password : ```bash - ./go-socks5 -port=8009 -addr=0.0.0.0 + htpasswd -nbs yourUsername yourPassword +``` + +You will get something like : `yourUsername:{SHA}pNvEyEKjh5XJ9cGgtK0l0WiuwmM=` +You need part after `:{SHA}`. for this example its `pNvEyEKjh5XJ9cGgtK0l0WiuwmM= +` + +Example: + +```toml +listen ="0.0.0.0:8008" + +auth = true +[[users]] + login = "vigo5190" + pass = "Ys23Ag/5IOWqZCw9QGaVDdHwH00=" ``` \ No newline at end of file diff --git a/config.toml b/config.toml index e5b1fe0..ad6868b 100644 --- a/config.toml +++ b/config.toml @@ -1 +1,3 @@ -listen ="0.0.0.0:8008" \ No newline at end of file +listen ="0.0.0.0:8008" + +auth = false \ No newline at end of file diff --git a/proxy/app.go b/proxy/app.go index c3652dd..d2e4978 100644 --- a/proxy/app.go +++ b/proxy/app.go @@ -6,13 +6,34 @@ import ( //Proxy is main struct for all magic type Proxy struct { - Listen string `toml:"listen"` + Listen string `toml:"listen"` + AuthEnable bool `toml:"auth"` + Users []user `toml:"users"` +} + +type user struct { + Login string `toml:"login"` + Pass string `toml:"pass"` } //Start method for start proxy func (p *Proxy) Start() { - server := &Server{} + + server := &Server{ + auth: p.authorizer(), + } log.Info().Msgf("start %s", p.Listen) server.ListenAndServe("tcp", p.Listen) } + +func (p *Proxy) authorizer() Authorizer { + upmap := map[string]string{} + for _, u := range p.Users { + upmap[u.Login] = u.Pass + } + return &Auth{ + Users: upmap, + AuthEnable: p.AuthEnable, + } +} diff --git a/proxy/auth.go b/proxy/auth.go index c6e2405..ecc2778 100644 --- a/proxy/auth.go +++ b/proxy/auth.go @@ -3,21 +3,30 @@ package proxy import ( "crypto/sha1" "encoding/base64" - "sync" "hash" + "sync" ) -type Auther interface { - Auth(login, pass string) bool +//Authorizer interface for auth +type Authorizer interface { + AuthLoginPassword(login string, pass []byte) bool + ShouldAuth() bool } +//Auth container for auth info type Auth struct { AuthEnable bool Users map[string]string - Shapool sync.Pool + shapool sync.Pool } -func (p *Auth) Auth(login, pass string) bool { +//ShouldAuth return true if aith enable +func (p *Auth) ShouldAuth() bool { + return p.AuthEnable +} + +//AuthLoginPassword return true if auth enable and login/password corrected +func (p *Auth) AuthLoginPassword(login string, pass []byte) bool { if !p.AuthEnable { return true } @@ -33,17 +42,18 @@ func (p *Auth) Auth(login, pass string) bool { return hashedPass == spass } -func (p *Auth) hashSha(password string) string { - s := p.Shapool.Get() +func (p *Auth) hashSha(password []byte) string { + s := p.shapool.Get() if s == nil { s = sha1.New() } ss := s.(hash.Hash) + defer ss.Reset() + defer p.shapool.Put(ss) + + ss.Write(password) + passwordSum := ss.Sum(nil) - ss.Write([]byte(password)) - passwordSum := []byte(ss.Sum(nil)) - ss.Reset() - p.Shapool.Put(s) return base64.StdEncoding.EncodeToString(passwordSum) } diff --git a/proxy/auth_test.go b/proxy/auth_test.go index c9a8b6b..e8ed82a 100644 --- a/proxy/auth_test.go +++ b/proxy/auth_test.go @@ -6,19 +6,19 @@ func TestProxy_Auth(t *testing.T) { p := Auth{ AuthEnable: true, Users: map[string]string{ - "foo":"Ys23Ag/5IOWqZCw9QGaVDdHwH00=", + "foo": "Ys23Ag/5IOWqZCw9QGaVDdHwH00=", }, } - if !p.Auth("foo", "bar") { + if !p.AuthLoginPassword("foo", []byte("bar")) { t.Error("expected auth true, got false") } - if p.Auth("foo", "bar1") { + if p.AuthLoginPassword("foo", []byte("bar1")) { t.Error("expected auth false, got true") } - if p.Auth("fooq", "bar") { + if p.AuthLoginPassword("fooq", []byte("bar")) { t.Error("expected auth false, got true") } } @@ -27,32 +27,32 @@ func TestProxy_Auth2(t *testing.T) { p := Auth{ AuthEnable: true, Users: map[string]string{ - "foo":"Ys23Ag/5IOWqZCw9QGaVDdHwH00=", - "bar":"C+7Hteo/D9vJXQ3UfzxbwnXaijM=", + "foo": "Ys23Ag/5IOWqZCw9QGaVDdHwH00=", + "bar": "C+7Hteo/D9vJXQ3UfzxbwnXaijM=", }, } - if !p.Auth("foo", "bar") { + if !p.AuthLoginPassword("foo", []byte("bar")) { t.Error("expected auth true, got false") } - if p.Auth("foo", "bar1") { + if p.AuthLoginPassword("foo", []byte("bar1")) { t.Error("expected auth false, got true") } - if p.Auth("fooq", "bar") { + if p.AuthLoginPassword("fooq", []byte("bar")) { t.Error("expected auth false, got true") } - if !p.Auth("bar", "foo") { + if !p.AuthLoginPassword("bar", []byte("foo")) { t.Error("expected auth true, got false") } - if p.Auth("bar", "foo1") { + if p.AuthLoginPassword("bar", []byte("foo1")) { t.Error("expected auth false, got true") } - if p.Auth("bar1", "foo") { + if p.AuthLoginPassword("bar1", []byte("foo")) { t.Error("expected auth false, got true") } } @@ -61,15 +61,14 @@ func BenchmarkAuth_Auth(b *testing.B) { p := Auth{ AuthEnable: true, Users: map[string]string{ - "foo":"Ys23Ag/5IOWqZCw9QGaVDdHwH00=", + "foo": "Ys23Ag/5IOWqZCw9QGaVDdHwH00=", }, } - + pass := []byte("bar") for n := 0; n < b.N; n++ { - if !p.Auth("foo", "bar") { + if !p.AuthLoginPassword("foo", pass) { b.Error("expected auth true, got false") } } - -} \ No newline at end of file +} diff --git a/proxy/command.go b/proxy/command.go index 575906f..ee6a167 100644 --- a/proxy/command.go +++ b/proxy/command.go @@ -73,7 +73,7 @@ func (c *Command) connect() (rsp byte, err error) { c.proxy() - return + return rspSuccess, nil } func (c *Command) proxy() { diff --git a/proxy/connection.go b/proxy/connection.go index 922ae1d..8a3f3f2 100644 --- a/proxy/connection.go +++ b/proxy/connection.go @@ -2,47 +2,54 @@ package proxy import ( "bytes" - "context" "encoding/binary" "errors" - "github.com/rs/zerolog/log" + "fmt" "io" "net" - "sync" + "unsafe" + + "github.com/rs/zerolog/log" ) const ( socks5Ver = 0x05 - authNoAuth = 0x00 - authGSSAPI = 0x01 - authUserPassword = 0x02 + authNoAuth = 0x00 + //authGSSAPI = 0x01 + authUP = 0x02 authNoMethods = 0xFF + + authUPSuccess = 0x00 + authUPFailure = 0x01 +) + +var ( + errUserPasswordVersion = fmt.Errorf("user password wrong version") + errUserPassword = fmt.Errorf("user password wrong") + errNoAuthMethod = fmt.Errorf("no auth method") ) //Connection container for data type Connection struct { - conn net.Conn - ctx *context.Context - + conn net.Conn + auth Authorizer ver byte methods []byte } -//NewConnection return new connection -func NewConnection(conn net.Conn) *Connection { - ctx := context.Background() - return &Connection{ - conn: conn, - ctx: &ctx, - } +//Reset use for reset data +func (c *Connection) Reset() { + c.conn = nil + c.auth = nil + c.ver = 0x0 + c.methods = c.methods[:0] } //Serve serve connection: handshake + cmd -func (c *Connection) Serve(wg *sync.WaitGroup) { +func (c *Connection) Serve() { defer c.conn.Close() - defer wg.Done() if err := binary.Read(c.conn, binary.BigEndian, &c.ver); err != nil { log.Error().Msgf("Error on read data from connection : %v", err) @@ -67,43 +74,80 @@ func (c *Connection) Serve(wg *sync.WaitGroup) { } func (c *Connection) handshake() (err error) { - var n int64 var acceptableAuthMethodSize byte if err = binary.Read(c.conn, binary.BigEndian, &acceptableAuthMethodSize); err != nil { return } - n++ c.methods = make([]byte, acceptableAuthMethodSize) if _, err = io.ReadFull(c.conn, c.methods); err != nil { return } - n += int64(acceptableAuthMethodSize) - if bytes.IndexByte(c.methods, authNoAuth) != -1 { + if !c.shouldAuth() && bytes.IndexByte(c.methods, authNoAuth) != -1 { return c.handleNoAuth() } - //TODO: add another methods + if c.shouldAuth() && bytes.IndexByte(c.methods, authUP) != -1 { + if err = c.handleAuthUserPassword(); err != nil { + c.conn.Write([]byte{socks5Ver, authUPFailure}) + } + c.conn.Write([]byte{socks5Ver, authUPSuccess}) + return + } c.conn.Write([]byte{socks5Ver, authNoMethods}) - return + return errNoAuthMethod } -func (c *Connection) handleNoAuth() (err error) { - remAddrstring := c.conn.RemoteAddr().String() +func (c *Connection) shouldAuth() (should bool) { + return c.auth != nil && c.auth.ShouldAuth() +} - _, _, err = net.SplitHostPort(remAddrstring) - if err != nil { +func (c *Connection) handleAuthUserPassword() (err error) { + if _, err = c.conn.Write([]byte{socks5Ver, authUP}); err != nil { return } - _, err = c.conn.Write([]byte{socks5Ver, authNoAuth}) + var ver byte + if err = binary.Read(c.conn, binary.BigEndian, &ver); err != nil { + return + } - if err != nil { + if ver != 0x01 { + return errUserPasswordVersion + } + + var usernameLen byte + if err = binary.Read(c.conn, binary.BigEndian, &usernameLen); err != nil { + return + } + + username := make([]byte, usernameLen) + if _, err = io.ReadFull(c.conn, username); err != nil { + return + } + + var passwordLen byte + if err = binary.Read(c.conn, binary.BigEndian, &passwordLen); err != nil { + return + } + + password := make([]byte, passwordLen) + if _, err = io.ReadFull(c.conn, password); err != nil { return } - return + + if !c.auth.AuthLoginPassword(*(*string)(unsafe.Pointer(&username)), password) { + return errUserPassword + } + return nil +} + +func (c *Connection) handleNoAuth() (err error) { + _, err = c.conn.Write([]byte{socks5Ver, authNoAuth}) + + return err } func (c *Connection) cmd() (err error) { @@ -128,7 +172,7 @@ func (c *Connection) cmd() (err error) { return c.writeErrorResponce(r, rsp) } - return + return nil } func (c *Connection) writeErrorResponce(r *Rqst, errCode byte) (err error) { diff --git a/proxy/request.go b/proxy/request.go index 1a56228..a92e935 100644 --- a/proxy/request.go +++ b/proxy/request.go @@ -96,5 +96,5 @@ func (r *Rqst) fromReader(src io.Reader) (err error) { return } - return + return nil } diff --git a/proxy/response.go b/proxy/response.go index 125f709..19806c9 100644 --- a/proxy/response.go +++ b/proxy/response.go @@ -19,7 +19,7 @@ type Rsps struct { } var ( - errRspServerMsg = errors.New("general error") + //errRspServerMsg = errors.New("general error") errRspCommandNotSupportMsg = errors.New("command not support") errRspAddressToShort = errors.New("address to short") errRspAddressToLong = errors.New("address to long") @@ -61,7 +61,8 @@ func (r *Rsps) Bytes() (buf []byte, err error) { buf = append(buf, 0, 0) binary.BigEndian.PutUint16(buf[len(buf)-2:], r.port) - return + + return buf, nil } func (r *Rsps) parseAddr(addr string) (err error) { @@ -90,5 +91,6 @@ func (r *Rsps) parseAddr(addr string) (err error) { return errRspEmptyPort } r.port = uint16(prt) - return + + return nil } diff --git a/proxy/server.go b/proxy/server.go index d68d371..50bd2d0 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -16,10 +16,12 @@ type ServerProxifier interface { //Server container for data type Server struct { + auth Authorizer ctx context.Context done context.CancelFunc closed bool wg sync.WaitGroup + connSP sync.Pool } //ListenAndServe create listener and serve @@ -58,9 +60,23 @@ func (s *Server) Serve(listener net.Listener) { s.wg.Wait() return case conn := <-newConns: - c := NewConnection(conn) + newConnection := s.connSP.Get() + if newConnection == nil { + newConnection = Connection{ + auth: s.auth, + } + } + cc := newConnection.(Connection) + + cc.conn = conn + s.wg.Add(1) - go c.Serve(&s.wg) + go func(swg *sync.WaitGroup) { + defer swg.Done() + defer s.connSP.Put(cc) + cc.Serve() + cc.Reset() + }(&s.wg) } } }