#57: add multi user auth support for HTTP and SOCKS5

This commit is contained in:
rui.zheng 2016-12-15 12:39:38 +08:00
parent e22229b5b1
commit c5fabde5f8
9 changed files with 129 additions and 51 deletions

View File

@ -86,9 +86,9 @@ func (c *ProxyChain) Init() {
if err != nil { if err != nil {
glog.V(LWARNING).Infoln("[kcp]", err) glog.V(LWARNING).Infoln("[kcp]", err)
} }
if c.nodes[0].User != nil { if c.nodes[0].Users != nil {
config.Crypt = c.nodes[0].User.Username() config.Crypt = c.nodes[0].Users[0].Username()
config.Key, _ = c.nodes[0].User.Password() config.Key, _ = c.nodes[0].Users[0].Password()
} }
c.kcpConfig = config c.kcpConfig = config
return return
@ -349,9 +349,9 @@ func (c *ProxyChain) http2Connect(addr string) (net.Conn, error) {
header := make(http.Header) header := make(http.Header)
header.Set("Gost-Target", addr) // Flag header to indicate the address that server connected to 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", 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) return c.getHttp2Conn(header)
} }

View File

@ -46,7 +46,7 @@ func init() {
} }
if printVersion { 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 return
} }
} }

4
cmd/gost/secrets.txt Normal file
View File

@ -0,0 +1,4 @@
# username password
test001 123456
test002 12345678

20
conn.go
View File

@ -95,7 +95,10 @@ func (c *ProxyConn) handshake() error {
gosocks5.MethodUserPass, gosocks5.MethodUserPass,
//MethodTLS, //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 if !tlsUsed { // if transport is not security, enable security socks5
@ -112,9 +115,9 @@ func (c *ProxyConn) handshake() error {
} }
c.conn = conn c.conn = conn
case "ss": // shadowsocks case "ss": // shadowsocks
if c.Node.User != nil { if len(c.Node.Users) > 0 {
method := c.Node.User.Username() method := c.Node.Users[0].Username()
password, _ := c.Node.User.Password() password, _ := c.Node.Users[0].Password()
cipher, err := shadowsocks.NewCipher(method, password) cipher, err := shadowsocks.NewCipher(method, password)
if err != nil { if err != nil {
return err return err
@ -191,9 +194,14 @@ func (c *ProxyConn) Connect(addr string) error {
Header: make(http.Header), Header: make(http.Header),
} }
req.Header.Set("Proxy-Connection", "keep-alive") 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", 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 { if err := req.Write(c); err != nil {
return err return err

View File

@ -11,7 +11,7 @@ import (
) )
const ( const (
Version = "2.3-dev" Version = "2.3-rc1"
) )
// Log level for glog // Log level for glog

53
http.go
View File

@ -44,15 +44,21 @@ func (s *HttpServer) HandleRequest(req *http.Request) {
return return
} }
var username, password string valid := false
if s.Base.Node.User != nil { u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
username = s.Base.Node.User.Username() glog.V(LINFO).Infoln(u, p)
password, _ = s.Base.Node.User.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
}
} }
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) if len(s.Base.Node.Users) > 0 && !valid {
req.Header.Del("Proxy-Authorization")
if (username != "" && u != username) || (password != "" && p != password) {
glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", s.conn.RemoteAddr(), req.Host) 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" + resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" +
"Proxy-Authenticate: Basic realm=\"gost\"\r\n" + "Proxy-Authenticate: Basic realm=\"gost\"\r\n" +
@ -61,6 +67,8 @@ func (s *HttpServer) HandleRequest(req *http.Request) {
return return
} }
req.Header.Del("Proxy-Authorization")
// forward http request // forward http request
lastNode := s.Base.Chain.lastNode lastNode := s.Base.Chain.lastNode
if lastNode != nil && (lastNode.Protocol == "http" || lastNode.Protocol == "") { if lastNode != nil && (lastNode.Protocol == "http" || lastNode.Protocol == "") {
@ -117,9 +125,14 @@ func (s *HttpServer) forwardRequest(req *http.Request) {
} }
defer cc.Close() 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", 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)) cc.SetWriteDeadline(time.Now().Add(WriteTimeout))
@ -184,20 +197,26 @@ func (s *Http2Server) HandleRequest(w http.ResponseWriter, req *http.Request) {
return return
} }
var username, password string valid := false
if s.Base.Node.User != nil {
username = s.Base.Node.User.Username()
password, _ = s.Base.Node.User.Password()
}
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
req.Header.Del("Proxy-Authorization") for _, user := range s.Base.Node.Users {
if (username != "" && u != username) || (password != "" && p != password) { 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) glog.V(LWARNING).Infof("[http2] %s <- %s : proxy authentication required", req.RemoteAddr, target)
w.WriteHeader(http.StatusProxyAuthRequired) w.WriteHeader(http.StatusProxyAuthRequired)
return return
} }
req.Header.Del("Proxy-Authorization")
c, err := s.Base.Chain.Dial(target) c, err := s.Base.Chain.Dial(target)
if err != nil { if err != nil {
glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err)

54
node.go
View File

@ -1,20 +1,23 @@
package gost package gost
import ( import (
"bufio"
"fmt" "fmt"
"github.com/golang/glog"
"net" "net"
"net/url" "net/url"
"os"
"strconv" "strconv"
"strings" "strings"
) )
// Proxy node represent a proxy // Proxy node represent a proxy
type ProxyNode struct { type ProxyNode struct {
Addr string // [host]:port Addr string // [host]:port
Protocol string // protocol: http/socks5/ss Protocol string // protocol: http/socks5/ss
Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp
Remote string // remote address, used by tcp/udp port forwarding Remote string // remote address, used by tcp/udp port forwarding
User *url.Userinfo // authentication for proxy Users []*url.Userinfo // authentication for proxy
values url.Values values url.Values
serverName string serverName string
conn net.Conn conn net.Conn
@ -34,11 +37,22 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
node = ProxyNode{ node = ProxyNode{
Addr: u.Host, Addr: u.Host,
User: u.User,
values: u.Query(), values: u.Query(),
serverName: u.Host, 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, ":") { if strings.Contains(u.Host, ":") {
node.serverName, _, _ = net.SplitHostPort(u.Host) node.serverName, _, _ = net.SplitHostPort(u.Host)
if node.serverName == "" { if node.serverName == "" {
@ -78,6 +92,34 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
return 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 // Get get node parameter by key
func (node *ProxyNode) Get(key string) string { func (node *ProxyNode) Get(key string) string {
return node.values.Get(key) return node.values.Get(key)

View File

@ -28,10 +28,10 @@ func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *Prox
} }
var cipher *ss.Cipher var cipher *ss.Cipher
if node.Protocol == "ss" && node.User != nil { if node.Protocol == "ss" && node.Users != nil {
var err error var err error
method := node.User.Username() method := node.Users[0].Username()
password, _ := node.User.Password() password, _ := node.Users[0].Password()
cipher, err = ss.NewCipher(method, password) cipher, err = ss.NewCipher(method, password)
if err != nil { if err != nil {
glog.Fatal(err) glog.Fatal(err)
@ -49,7 +49,7 @@ func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *Prox
MethodTLS, MethodTLS,
MethodTLSAuth, MethodTLSAuth,
}, },
user: node.User, users: node.Users,
tlsConfig: config, tlsConfig: config,
}, },
cipher: cipher, cipher: cipher,
@ -90,9 +90,9 @@ func (s *ProxyServer) Serve() error {
glog.V(LWARNING).Infoln("[kcp]", err) glog.V(LWARNING).Infoln("[kcp]", err)
} }
// override crypt and key if specified explicitly // override crypt and key if specified explicitly
if s.Node.User != nil { if s.Node.Users != nil {
config.Crypt = s.Node.User.Username() config.Crypt = s.Node.Users[0].Username()
config.Key, _ = s.Node.User.Password() config.Key, _ = s.Node.Users[0].Password()
} }
return NewKCPServer(s, config).ListenAndServe() return NewKCPServer(s, config).ListenAndServe()
default: default:

View File

@ -81,7 +81,7 @@ func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Con
type serverSelector struct { type serverSelector struct {
methods []uint8 methods []uint8
user *url.Userinfo users []*url.Userinfo
tlsConfig *tls.Config tlsConfig *tls.Config
} }
@ -101,7 +101,7 @@ func (selector *serverSelector) Select(methods ...uint8) (method uint8) {
} }
// when user/pass is set, auth is mandatory // when user/pass is set, auth is mandatory
if selector.user != nil { if selector.users != nil {
if method == gosocks5.MethodNoAuth { if method == gosocks5.MethodNoAuth {
method = gosocks5.MethodUserPass 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()) glog.V(LDEBUG).Infoln("[socks5]", req.String())
var username, password string valid := false
if selector.user != nil { for _, user := range selector.users {
username = selector.user.Username() username := user.Username()
password, _ = selector.user.Password() password, _ := user.Password()
if (req.Username == username && req.Password == password) ||
(req.Username == username && password == "") ||
(username == "" && req.Password == password) {
valid = true
break
}
} }
if len(selector.users) > 0 && !valid {
if (username != "" && req.Username != username) || (password != "" && req.Password != password) {
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure)
if err := resp.Write(conn); err != nil { if err := resp.Write(conn); err != nil {
glog.V(LWARNING).Infoln("[socks5-auth]", err) glog.V(LWARNING).Infoln("[socks5-auth]", err)