diff --git a/client.go b/client.go index 315efe1..176fdf2 100644 --- a/client.go +++ b/client.go @@ -5,6 +5,7 @@ import ( "bytes" "crypto/tls" "encoding/binary" + "errors" "fmt" "github.com/ginuerzh/gosocks5" "github.com/gorilla/websocket" @@ -58,10 +59,18 @@ func listenAndServe(addr string, handler func(net.Conn)) error { func clientMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { switch method { + case gosocks5.MethodUserPass: + user, pass := parseUserPass(Password) + if err := clientSocksAuth(conn, user, pass); err != nil { + return nil, err + } case MethodTLS, MethodTLSAuth: conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) if method == MethodTLSAuth { - if err := cliTLSAuth(conn); err != nil { + if len(Password) == 0 { + return nil, ErrEmptyAuth + } + if err := clientSocksAuth(conn, "", Password); err != nil { return nil, err } } @@ -80,26 +89,6 @@ func clientMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { return conn, nil } -func cliTLSAuth(conn net.Conn) error { - if len(Password) == 0 { - return ErrEmptyPassword - } - - if err := gosocks5.NewUserPassRequest( - gosocks5.UserPassVer, "", Password).Write(conn); err != nil { - return err - } - res, err := gosocks5.ReadUserPassResponse(conn) - if err != nil { - return err - } - if res.Status != gosocks5.Succeeded { - return gosocks5.ErrAuthFailure - } - - return nil -} - func makeTunnel() (c net.Conn, err error) { if UseWebsocket || !UseHttp { c, err = connect(Saddr) @@ -161,21 +150,19 @@ func cliHandle(conn net.Conn) { } if b[0] == gosocks5.Ver5 { - length := 2 + int(b[1]) + mn := int(b[1]) // methods count + length := 2 + mn if n < length { if _, err := io.ReadFull(conn, b[n:length]); err != nil { return } } - if err := gosocks5.WriteMethod(gosocks5.MethodNoAuth, conn); err != nil { - return - } - - handleSocks5(conn) + methods := b[2 : 2+mn] + handleSocks5(conn, methods) return } - + log.Println(string(b[:n])) for { if bytes.HasSuffix(b[:n], []byte("\r\n\r\n")) { break @@ -197,7 +184,51 @@ func cliHandle(conn net.Conn) { handleHttp(req, conn) } -func handleSocks5(conn net.Conn) { +func selectMethod(conn net.Conn, methods ...uint8) error { + m := gosocks5.MethodNoAuth + + if listenUrl.User != nil { + for _, method := range methods { + if method == gosocks5.MethodUserPass { + m = method + break + } + } + if m != gosocks5.MethodUserPass { + m = gosocks5.MethodNoAcceptable + } + } + if err := gosocks5.WriteMethod(m, conn); err != nil { + return err + } + + log.Println(m) + + switch m { + case gosocks5.MethodUserPass: + var username, password string + + if listenUrl != nil && listenUrl.User != nil { + username = listenUrl.User.Username() + password, _ = listenUrl.User.Password() + } + + if err := serverSocksAuth(conn, username, password); err != nil { + return err + } + case gosocks5.MethodNoAcceptable: + return gosocks5.ErrBadMethod + } + + return nil +} + +func handleSocks5(conn net.Conn, methods []uint8) { + if err := selectMethod(conn, methods...); err != nil { + log.Println(err) + return + } + req, err := gosocks5.ReadRequest(conn) if err != nil { return @@ -301,10 +332,35 @@ func cliTunnelUDP(uconn *net.UDPConn, sconn net.Conn) { } } +func clientHttpAuth(req *http.Request, conn net.Conn, username, password string) error { + u, p, ok := req.BasicAuth() + if !ok || + (len(username) > 0 && u != username) || + (len(password) > 0 && p != password) { + conn.Write([]byte("HTTP/1.1 401 Not Authorized\r\n" + + "WWW-Authenticate: Basic realm=\"Authorization Required\"\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n")) + + return errors.New("Not Authorized") + } + + return nil +} + func handleHttp(req *http.Request, conn net.Conn) { var host string var port uint16 + if listenUrl != nil && listenUrl.User != nil { + username := listenUrl.User.Username() + password, _ := listenUrl.User.Password() + + if err := clientHttpAuth(req, conn, username, password); err != nil { + log.Println(err) + return + } + } + s := strings.Split(req.Host, ":") host = s[0] port = 80 diff --git a/main.go b/main.go index 41643c6..7dc27be 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "flag" - //"github.com/ginuerzh/gosocks5" "log" "net/url" "time" @@ -18,7 +17,8 @@ var ( CertFile, KeyFile string PrintVersion bool - proxyURL *url.URL + proxyURL *url.URL + listenUrl *url.URL ) func init() { @@ -40,6 +40,7 @@ func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) proxyURL, _ = parseURL(Proxy) + listenUrl, _ = parseURL(Laddr) } var ( @@ -54,18 +55,20 @@ func main() { return } + laddr := listenUrl.Host + if len(Saddr) == 0 { var server Server if UseWebsocket { - server = &WSServer{Addr: Laddr} + server = &WSServer{Addr: laddr} } else if UseHttp { - server = &HttpServer{Addr: Laddr} + server = &HttpServer{Addr: laddr} } else { - server = &Socks5Server{Addr: Laddr} + server = &Socks5Server{Addr: laddr} } log.Fatal(server.ListenAndServe()) return } - log.Fatal(listenAndServe(Laddr, cliHandle)) + log.Fatal(listenAndServe(laddr, cliHandle)) } diff --git a/socks5.go b/socks5.go index 342ccf4..95e0429 100644 --- a/socks5.go +++ b/socks5.go @@ -94,6 +94,7 @@ func (s *Socks5Server) ListenAndServe() error { } func serverSelectMethod(methods ...uint8) uint8 { + log.Println(methods) m := gosocks5.MethodNoAuth for _, method := range methods { @@ -102,6 +103,11 @@ func serverSelectMethod(methods ...uint8) uint8 { } } + // when user/pass is set for proxy auth, the NoAuth method is disabled + if len(Method) == 0 && m == gosocks5.MethodNoAuth && listenUrl.User != nil { + return gosocks5.MethodNoAcceptable + } + if len(Method) == 0 || Methods[m] == Method { return m } @@ -110,7 +116,19 @@ func serverSelectMethod(methods ...uint8) uint8 { } func serverMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { + log.Println(method) switch method { + case gosocks5.MethodUserPass: + var username, password string + + if listenUrl != nil && listenUrl.User != nil { + username = listenUrl.User.Username() + password, _ = listenUrl.User.Password() + } + + if err := serverSocksAuth(conn, username, password); err != nil { + return nil, err + } case MethodTLS, MethodTLSAuth: var cert tls.Certificate var err error @@ -126,7 +144,11 @@ func serverMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { } conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{cert}}) if method == MethodTLSAuth { - if err := svrTLSAuth(conn); err != nil { + // password is mandatory + if len(Password) == 0 { + return nil, ErrEmptyAuth + } + if err := serverSocksAuth(conn, "", Password); err != nil { return nil, err } } @@ -144,32 +166,6 @@ func serverMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { return conn, nil } -func svrTLSAuth(conn net.Conn) error { - if len(Password) == 0 { - return ErrEmptyPassword - } - - req, err := gosocks5.ReadUserPassRequest(conn) - if err != nil { - return err - } - - if req.Password != Password { - if err := gosocks5.NewUserPassResponse( - gosocks5.UserPassVer, gosocks5.Failure).Write(conn); err != nil { - return err - } - return gosocks5.ErrAuthFailure - } - - if err := gosocks5.NewUserPassResponse( - gosocks5.UserPassVer, gosocks5.Succeeded).Write(conn); err != nil { - return err - } - - return nil -} - func serveSocks5(conn net.Conn) { defer conn.Close() diff --git a/util.go b/util.go index 543b674..0cf5125 100644 --- a/util.go +++ b/util.go @@ -29,21 +29,22 @@ const ( MethodTLSAuth ) -var ErrEmptyPassword = errors.New("empty key") +var ErrEmptyAuth = errors.New("empty auth") var Methods = map[uint8]string{ - gosocks5.MethodNoAuth: "", // 0x00 - MethodTLS: "tls", // 0x80 - MethodAES128: "aes-128-cfb", // 0x81 - MethodAES192: "aes-192-cfb", // 0x82 - MethodAES256: "aes-256-cfb", // 0x83 - MethodDES: "des-cfb", // 0x84 - MethodBF: "bf-cfb", // 0x85 - MethodCAST5: "cast5-cfb", // 0x86 - MethodRC4MD5: "rc4-md5", // 8x87 - MethodRC4: "rc4", // 0x88 - MethodTable: "table", // 0x89 - MethodTLSAuth: "tls-auth", // 0x90 + //gosocks5.MethodNoAuth: "", // 0x00 + gosocks5.MethodUserPass: "userpass", // 0x02 + MethodTLS: "tls", // 0x80 + MethodAES128: "aes-128-cfb", // 0x81 + MethodAES192: "aes-192-cfb", // 0x82 + MethodAES256: "aes-256-cfb", // 0x83 + MethodDES: "des-cfb", // 0x84 + MethodBF: "bf-cfb", // 0x85 + MethodCAST5: "cast5-cfb", // 0x86 + MethodRC4MD5: "rc4-md5", // 8x87 + MethodRC4: "rc4", // 0x88 + MethodTable: "table", // 0x89 + MethodTLSAuth: "tls-auth", // 0x90 } func parseURL(rawurl string) (*url.URL, error) { @@ -57,6 +58,15 @@ func parseURL(rawurl string) (*url.URL, error) { return url.Parse(rawurl) } +func parseUserPass(key string) (username string, password string) { + sep := ":" + i := strings.Index(key, sep) + if i < 0 { + return key, "" + } + return key[0:i], key[i+len(sep):] +} + func ToSocksAddr(addr net.Addr) *gosocks5.Addr { host, port, _ := net.SplitHostPort(addr.String()) p, _ := strconv.Atoi(port) @@ -135,9 +145,12 @@ func connectSocks5Proxy(addr string) (conn net.Conn, err error) { } conf := &gosocks5.Config{ - Methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass}, + // Methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass}, MethodSelected: proxyMethodSelected, } + if proxyURL.User != nil { + conf.Methods = []uint8{gosocks5.MethodUserPass} + } c := gosocks5.ClientConn(conn, conf) if err := c.Handleshake(); err != nil { @@ -178,30 +191,61 @@ func connectSocks5Proxy(addr string) (conn net.Conn, err error) { func proxyMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { switch method { case gosocks5.MethodUserPass: - if proxyURL == nil || proxyURL.User == nil { - return nil, gosocks5.ErrAuthFailure + var user, pass string + + if proxyURL != nil && proxyURL.User != nil { + user = proxyURL.User.Username() + pass, _ = proxyURL.User.Password() } - pwd, _ := proxyURL.User.Password() - if err := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, - proxyURL.User.Username(), pwd).Write(conn); err != nil { + if err := clientSocksAuth(conn, user, pass); err != nil { return nil, err } - resp, err := gosocks5.ReadUserPassResponse(conn) - if err != nil { - return nil, err - } - if resp.Status != gosocks5.Succeeded { - return nil, gosocks5.ErrAuthFailure - } case gosocks5.MethodNoAcceptable: return nil, gosocks5.ErrBadMethod - - //case gosocks5.MethodNoAuth: } return conn, nil } +func clientSocksAuth(conn net.Conn, username, password string) error { + if err := gosocks5.NewUserPassRequest( + gosocks5.UserPassVer, username, password).Write(conn); err != nil { + return err + } + res, err := gosocks5.ReadUserPassResponse(conn) + if err != nil { + return err + } + if res.Status != gosocks5.Succeeded { + return gosocks5.ErrAuthFailure + } + + return nil +} + +func serverSocksAuth(conn net.Conn, username, password string) error { + req, err := gosocks5.ReadUserPassRequest(conn) + if err != nil { + return err + } + + if (len(username) > 0 && req.Username != username) || + (len(password) > 0 && req.Password != password) { + if err := gosocks5.NewUserPassResponse( + gosocks5.UserPassVer, gosocks5.Failure).Write(conn); err != nil { + return err + } + return gosocks5.ErrAuthFailure + } + + if err := gosocks5.NewUserPassResponse( + gosocks5.UserPassVer, gosocks5.Succeeded).Write(conn); err != nil { + return err + } + + return nil +} + func setBasicAuth(r *http.Request) { if proxyURL != nil && proxyURL.User != nil { r.Header.Set("Proxy-Authorization", diff --git a/version.go b/version.go index 3215dcf..1a89efd 100644 --- a/version.go +++ b/version.go @@ -5,7 +5,7 @@ import ( ) const ( - Version = "1.6" + Version = "1.7" ) func printVersion() {