From c5fabde5f85f2e7ca2d0bb6b35fd554b4788beda Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Thu, 15 Dec 2016 12:39:38 +0800 Subject: [PATCH] #57: add multi user auth support for HTTP and SOCKS5 --- chain.go | 10 ++++---- cmd/gost/main.go | 2 +- cmd/gost/secrets.txt | 4 ++++ conn.go | 20 +++++++++++----- gost.go | 2 +- http.go | 53 +++++++++++++++++++++++++++++-------------- node.go | 54 +++++++++++++++++++++++++++++++++++++++----- server.go | 14 ++++++------ socks.go | 21 ++++++++++------- 9 files changed, 129 insertions(+), 51 deletions(-) create mode 100644 cmd/gost/secrets.txt diff --git a/chain.go b/chain.go index f9820e2..d8ca5ab 100644 --- a/chain.go +++ b/chain.go @@ -86,9 +86,9 @@ func (c *ProxyChain) Init() { if err != nil { glog.V(LWARNING).Infoln("[kcp]", err) } - if c.nodes[0].User != nil { - config.Crypt = c.nodes[0].User.Username() - config.Key, _ = c.nodes[0].User.Password() + if c.nodes[0].Users != nil { + config.Crypt = c.nodes[0].Users[0].Username() + config.Key, _ = c.nodes[0].Users[0].Password() } c.kcpConfig = config return @@ -349,9 +349,9 @@ func (c *ProxyChain) http2Connect(addr string) (net.Conn, error) { header := make(http.Header) header.Set("Gost-Target", addr) // Flag header to indicate the address that server connected to - if http2Node.User != nil { + if http2Node.Users != nil { header.Set("Proxy-Authorization", - "Basic "+base64.StdEncoding.EncodeToString([]byte(http2Node.User.String()))) + "Basic "+base64.StdEncoding.EncodeToString([]byte(http2Node.Users[0].String()))) } return c.getHttp2Conn(header) } diff --git a/cmd/gost/main.go b/cmd/gost/main.go index 528d420..6456987 100644 --- a/cmd/gost/main.go +++ b/cmd/gost/main.go @@ -46,7 +46,7 @@ func init() { } if printVersion { - fmt.Fprintf(os.Stderr, "GOST %s (%s)\n", gost.Version, runtime.Version()) + fmt.Fprintf(os.Stderr, "gost %s (%s)\n", gost.Version, runtime.Version()) return } } diff --git a/cmd/gost/secrets.txt b/cmd/gost/secrets.txt new file mode 100644 index 0000000..5a4b77b --- /dev/null +++ b/cmd/gost/secrets.txt @@ -0,0 +1,4 @@ +# username password + +test001 123456 +test002 12345678 \ No newline at end of file diff --git a/conn.go b/conn.go index f31e7ae..f44fcc1 100644 --- a/conn.go +++ b/conn.go @@ -95,7 +95,10 @@ func (c *ProxyConn) handshake() error { gosocks5.MethodUserPass, //MethodTLS, }, - user: c.Node.User, + } + + if len(c.Node.Users) > 0 { + selector.user = c.Node.Users[0] } if !tlsUsed { // if transport is not security, enable security socks5 @@ -112,9 +115,9 @@ func (c *ProxyConn) handshake() error { } c.conn = conn case "ss": // shadowsocks - if c.Node.User != nil { - method := c.Node.User.Username() - password, _ := c.Node.User.Password() + if len(c.Node.Users) > 0 { + method := c.Node.Users[0].Username() + password, _ := c.Node.Users[0].Password() cipher, err := shadowsocks.NewCipher(method, password) if err != nil { return err @@ -191,9 +194,14 @@ func (c *ProxyConn) Connect(addr string) error { Header: make(http.Header), } req.Header.Set("Proxy-Connection", "keep-alive") - if c.Node.User != nil { + if len(c.Node.Users) > 0 { + user := c.Node.Users[0] + s := user.String() + if _, set := user.Password(); !set { + s += ":" + } req.Header.Set("Proxy-Authorization", - "Basic "+base64.StdEncoding.EncodeToString([]byte(c.Node.User.String()))) + "Basic "+base64.StdEncoding.EncodeToString([]byte(s))) } if err := req.Write(c); err != nil { return err diff --git a/gost.go b/gost.go index ef98ce6..9ce97cc 100644 --- a/gost.go +++ b/gost.go @@ -11,7 +11,7 @@ import ( ) const ( - Version = "2.3-dev" + Version = "2.3-rc1" ) // Log level for glog diff --git a/http.go b/http.go index 18418eb..23c804c 100644 --- a/http.go +++ b/http.go @@ -44,15 +44,21 @@ func (s *HttpServer) HandleRequest(req *http.Request) { return } - var username, password string - if s.Base.Node.User != nil { - username = s.Base.Node.User.Username() - password, _ = s.Base.Node.User.Password() + valid := false + u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) + glog.V(LINFO).Infoln(u, p) + for _, user := range s.Base.Node.Users { + username := user.Username() + password, _ := user.Password() + if (u == username && p == password) || + (u == username && password == "") || + (username == "" && p == password) { + valid = true + break + } } - u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) - req.Header.Del("Proxy-Authorization") - if (username != "" && u != username) || (password != "" && p != password) { + if len(s.Base.Node.Users) > 0 && !valid { glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", s.conn.RemoteAddr(), req.Host) resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" + "Proxy-Authenticate: Basic realm=\"gost\"\r\n" + @@ -61,6 +67,8 @@ func (s *HttpServer) HandleRequest(req *http.Request) { return } + req.Header.Del("Proxy-Authorization") + // forward http request lastNode := s.Base.Chain.lastNode if lastNode != nil && (lastNode.Protocol == "http" || lastNode.Protocol == "") { @@ -117,9 +125,14 @@ func (s *HttpServer) forwardRequest(req *http.Request) { } defer cc.Close() - if last.User != nil { + if len(last.Users) > 0 { + user := last.Users[0] + s := user.String() + if _, set := user.Password(); !set { + s += ":" + } req.Header.Set("Proxy-Authorization", - "Basic "+base64.StdEncoding.EncodeToString([]byte(last.User.String()))) + "Basic "+base64.StdEncoding.EncodeToString([]byte(s))) } cc.SetWriteDeadline(time.Now().Add(WriteTimeout)) @@ -184,20 +197,26 @@ func (s *Http2Server) HandleRequest(w http.ResponseWriter, req *http.Request) { return } - var username, password string - if s.Base.Node.User != nil { - username = s.Base.Node.User.Username() - password, _ = s.Base.Node.User.Password() - } - + valid := false u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) - req.Header.Del("Proxy-Authorization") - if (username != "" && u != username) || (password != "" && p != password) { + for _, user := range s.Base.Node.Users { + username := user.Username() + password, _ := user.Password() + if (u == username && p == password) || + (u == username && password == "") || + (username == "" && p == password) { + valid = true + break + } + } + if len(s.Base.Node.Users) > 0 && !valid { glog.V(LWARNING).Infof("[http2] %s <- %s : proxy authentication required", req.RemoteAddr, target) w.WriteHeader(http.StatusProxyAuthRequired) return } + req.Header.Del("Proxy-Authorization") + c, err := s.Base.Chain.Dial(target) if err != nil { glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) diff --git a/node.go b/node.go index 5c730ae..71690fb 100644 --- a/node.go +++ b/node.go @@ -1,20 +1,23 @@ package gost import ( + "bufio" "fmt" + "github.com/golang/glog" "net" "net/url" + "os" "strconv" "strings" ) // Proxy node represent a proxy type ProxyNode struct { - Addr string // [host]:port - Protocol string // protocol: http/socks5/ss - Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp - Remote string // remote address, used by tcp/udp port forwarding - User *url.Userinfo // authentication for proxy + Addr string // [host]:port + Protocol string // protocol: http/socks5/ss + Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp + Remote string // remote address, used by tcp/udp port forwarding + Users []*url.Userinfo // authentication for proxy values url.Values serverName string conn net.Conn @@ -34,11 +37,22 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { node = ProxyNode{ Addr: u.Host, - User: u.User, values: u.Query(), serverName: u.Host, } + if u.User != nil { + node.Users = append(node.Users, u.User) + } + + users, er := parseUsers(node.Get("secrets")) + if users != nil { + node.Users = append(node.Users, users...) + } + if er != nil { + glog.V(LWARNING).Infoln("secrets:", er) + } + if strings.Contains(u.Host, ":") { node.serverName, _, _ = net.SplitHostPort(u.Host) if node.serverName == "" { @@ -78,6 +92,34 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { return } +func parseUsers(authFile string) (users []*url.Userinfo, err error) { + if authFile == "" { + return + } + + file, err := os.Open(authFile) + if err != nil { + return + } + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + s := strings.SplitN(line, " ", 2) + if len(s) == 1 { + users = append(users, url.User(strings.TrimSpace(s[0]))) + } else if len(s) == 2 { + users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1]))) + } + } + + err = scanner.Err() + return +} + // Get get node parameter by key func (node *ProxyNode) Get(key string) string { return node.values.Get(key) diff --git a/server.go b/server.go index 438e542..488f491 100644 --- a/server.go +++ b/server.go @@ -28,10 +28,10 @@ func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *Prox } var cipher *ss.Cipher - if node.Protocol == "ss" && node.User != nil { + if node.Protocol == "ss" && node.Users != nil { var err error - method := node.User.Username() - password, _ := node.User.Password() + method := node.Users[0].Username() + password, _ := node.Users[0].Password() cipher, err = ss.NewCipher(method, password) if err != nil { glog.Fatal(err) @@ -49,7 +49,7 @@ func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *Prox MethodTLS, MethodTLSAuth, }, - user: node.User, + users: node.Users, tlsConfig: config, }, cipher: cipher, @@ -90,9 +90,9 @@ func (s *ProxyServer) Serve() error { glog.V(LWARNING).Infoln("[kcp]", err) } // override crypt and key if specified explicitly - if s.Node.User != nil { - config.Crypt = s.Node.User.Username() - config.Key, _ = s.Node.User.Password() + if s.Node.Users != nil { + config.Crypt = s.Node.Users[0].Username() + config.Key, _ = s.Node.Users[0].Password() } return NewKCPServer(s, config).ListenAndServe() default: diff --git a/socks.go b/socks.go index 4a5b21c..a570e0b 100644 --- a/socks.go +++ b/socks.go @@ -81,7 +81,7 @@ func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Con type serverSelector struct { methods []uint8 - user *url.Userinfo + users []*url.Userinfo tlsConfig *tls.Config } @@ -101,7 +101,7 @@ func (selector *serverSelector) Select(methods ...uint8) (method uint8) { } // when user/pass is set, auth is mandatory - if selector.user != nil { + if selector.users != nil { if method == gosocks5.MethodNoAuth { method = gosocks5.MethodUserPass } @@ -132,13 +132,18 @@ func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Con } glog.V(LDEBUG).Infoln("[socks5]", req.String()) - var username, password string - if selector.user != nil { - username = selector.user.Username() - password, _ = selector.user.Password() + valid := false + for _, user := range selector.users { + username := user.Username() + password, _ := user.Password() + if (req.Username == username && req.Password == password) || + (req.Username == username && password == "") || + (username == "" && req.Password == password) { + valid = true + break + } } - - if (username != "" && req.Username != username) || (password != "" && req.Password != password) { + if len(selector.users) > 0 && !valid { resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) if err := resp.Write(conn); err != nil { glog.V(LWARNING).Infoln("[socks5-auth]", err)