#57: add multi user auth support for HTTP and SOCKS5
This commit is contained in:
parent
e22229b5b1
commit
c5fabde5f8
10
chain.go
10
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)
|
||||
}
|
||||
|
@ -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
4
cmd/gost/secrets.txt
Normal file
@ -0,0 +1,4 @@
|
||||
# username password
|
||||
|
||||
test001 123456
|
||||
test002 12345678
|
20
conn.go
20
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
|
||||
|
2
gost.go
2
gost.go
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "2.3-dev"
|
||||
Version = "2.3-rc1"
|
||||
)
|
||||
|
||||
// Log level for glog
|
||||
|
53
http.go
53
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)
|
||||
|
54
node.go
54
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)
|
||||
|
14
server.go
14
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:
|
||||
|
21
socks.go
21
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)
|
||||
|
Loading…
Reference in New Issue
Block a user