#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 {
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)
}

View File

@ -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
}
}

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,
//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

View File

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

53
http.go
View File

@ -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)

54
node.go
View File

@ -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)

View File

@ -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:

View File

@ -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)