add proxy auth feature

This commit is contained in:
rui.zheng 2015-06-26 18:10:26 +08:00
parent 8e568b6451
commit 6b8d16042e
5 changed files with 190 additions and 91 deletions

114
client.go
View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"github.com/ginuerzh/gosocks5" "github.com/ginuerzh/gosocks5"
"github.com/gorilla/websocket" "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) { func clientMethodSelected(method uint8, conn net.Conn) (net.Conn, error) {
switch method { switch method {
case gosocks5.MethodUserPass:
user, pass := parseUserPass(Password)
if err := clientSocksAuth(conn, user, pass); err != nil {
return nil, err
}
case MethodTLS, MethodTLSAuth: case MethodTLS, MethodTLSAuth:
conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
if method == MethodTLSAuth { 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 return nil, err
} }
} }
@ -80,26 +89,6 @@ func clientMethodSelected(method uint8, conn net.Conn) (net.Conn, error) {
return conn, nil 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) { func makeTunnel() (c net.Conn, err error) {
if UseWebsocket || !UseHttp { if UseWebsocket || !UseHttp {
c, err = connect(Saddr) c, err = connect(Saddr)
@ -161,21 +150,19 @@ func cliHandle(conn net.Conn) {
} }
if b[0] == gosocks5.Ver5 { if b[0] == gosocks5.Ver5 {
length := 2 + int(b[1]) mn := int(b[1]) // methods count
length := 2 + mn
if n < length { if n < length {
if _, err := io.ReadFull(conn, b[n:length]); err != nil { if _, err := io.ReadFull(conn, b[n:length]); err != nil {
return return
} }
} }
if err := gosocks5.WriteMethod(gosocks5.MethodNoAuth, conn); err != nil { methods := b[2 : 2+mn]
return handleSocks5(conn, methods)
}
handleSocks5(conn)
return return
} }
log.Println(string(b[:n]))
for { for {
if bytes.HasSuffix(b[:n], []byte("\r\n\r\n")) { if bytes.HasSuffix(b[:n], []byte("\r\n\r\n")) {
break break
@ -197,7 +184,51 @@ func cliHandle(conn net.Conn) {
handleHttp(req, 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) req, err := gosocks5.ReadRequest(conn)
if err != nil { if err != nil {
return 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) { func handleHttp(req *http.Request, conn net.Conn) {
var host string var host string
var port uint16 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, ":") s := strings.Split(req.Host, ":")
host = s[0] host = s[0]
port = 80 port = 80

15
main.go
View File

@ -3,7 +3,6 @@ package main
import ( import (
"flag" "flag"
//"github.com/ginuerzh/gosocks5"
"log" "log"
"net/url" "net/url"
"time" "time"
@ -18,7 +17,8 @@ var (
CertFile, KeyFile string CertFile, KeyFile string
PrintVersion bool PrintVersion bool
proxyURL *url.URL proxyURL *url.URL
listenUrl *url.URL
) )
func init() { func init() {
@ -40,6 +40,7 @@ func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
proxyURL, _ = parseURL(Proxy) proxyURL, _ = parseURL(Proxy)
listenUrl, _ = parseURL(Laddr)
} }
var ( var (
@ -54,18 +55,20 @@ func main() {
return return
} }
laddr := listenUrl.Host
if len(Saddr) == 0 { if len(Saddr) == 0 {
var server Server var server Server
if UseWebsocket { if UseWebsocket {
server = &WSServer{Addr: Laddr} server = &WSServer{Addr: laddr}
} else if UseHttp { } else if UseHttp {
server = &HttpServer{Addr: Laddr} server = &HttpServer{Addr: laddr}
} else { } else {
server = &Socks5Server{Addr: Laddr} server = &Socks5Server{Addr: laddr}
} }
log.Fatal(server.ListenAndServe()) log.Fatal(server.ListenAndServe())
return return
} }
log.Fatal(listenAndServe(Laddr, cliHandle)) log.Fatal(listenAndServe(laddr, cliHandle))
} }

View File

@ -94,6 +94,7 @@ func (s *Socks5Server) ListenAndServe() error {
} }
func serverSelectMethod(methods ...uint8) uint8 { func serverSelectMethod(methods ...uint8) uint8 {
log.Println(methods)
m := gosocks5.MethodNoAuth m := gosocks5.MethodNoAuth
for _, method := range methods { 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 { if len(Method) == 0 || Methods[m] == Method {
return m return m
} }
@ -110,7 +116,19 @@ func serverSelectMethod(methods ...uint8) uint8 {
} }
func serverMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { func serverMethodSelected(method uint8, conn net.Conn) (net.Conn, error) {
log.Println(method)
switch 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: case MethodTLS, MethodTLSAuth:
var cert tls.Certificate var cert tls.Certificate
var err error 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}}) conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{cert}})
if method == MethodTLSAuth { 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 return nil, err
} }
} }
@ -144,32 +166,6 @@ func serverMethodSelected(method uint8, conn net.Conn) (net.Conn, error) {
return conn, nil 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) { func serveSocks5(conn net.Conn) {
defer conn.Close() defer conn.Close()

100
util.go
View File

@ -29,21 +29,22 @@ const (
MethodTLSAuth MethodTLSAuth
) )
var ErrEmptyPassword = errors.New("empty key") var ErrEmptyAuth = errors.New("empty auth")
var Methods = map[uint8]string{ var Methods = map[uint8]string{
gosocks5.MethodNoAuth: "", // 0x00 //gosocks5.MethodNoAuth: "", // 0x00
MethodTLS: "tls", // 0x80 gosocks5.MethodUserPass: "userpass", // 0x02
MethodAES128: "aes-128-cfb", // 0x81 MethodTLS: "tls", // 0x80
MethodAES192: "aes-192-cfb", // 0x82 MethodAES128: "aes-128-cfb", // 0x81
MethodAES256: "aes-256-cfb", // 0x83 MethodAES192: "aes-192-cfb", // 0x82
MethodDES: "des-cfb", // 0x84 MethodAES256: "aes-256-cfb", // 0x83
MethodBF: "bf-cfb", // 0x85 MethodDES: "des-cfb", // 0x84
MethodCAST5: "cast5-cfb", // 0x86 MethodBF: "bf-cfb", // 0x85
MethodRC4MD5: "rc4-md5", // 8x87 MethodCAST5: "cast5-cfb", // 0x86
MethodRC4: "rc4", // 0x88 MethodRC4MD5: "rc4-md5", // 8x87
MethodTable: "table", // 0x89 MethodRC4: "rc4", // 0x88
MethodTLSAuth: "tls-auth", // 0x90 MethodTable: "table", // 0x89
MethodTLSAuth: "tls-auth", // 0x90
} }
func parseURL(rawurl string) (*url.URL, error) { func parseURL(rawurl string) (*url.URL, error) {
@ -57,6 +58,15 @@ func parseURL(rawurl string) (*url.URL, error) {
return url.Parse(rawurl) 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 { func ToSocksAddr(addr net.Addr) *gosocks5.Addr {
host, port, _ := net.SplitHostPort(addr.String()) host, port, _ := net.SplitHostPort(addr.String())
p, _ := strconv.Atoi(port) p, _ := strconv.Atoi(port)
@ -135,9 +145,12 @@ func connectSocks5Proxy(addr string) (conn net.Conn, err error) {
} }
conf := &gosocks5.Config{ conf := &gosocks5.Config{
Methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass}, // Methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass},
MethodSelected: proxyMethodSelected, MethodSelected: proxyMethodSelected,
} }
if proxyURL.User != nil {
conf.Methods = []uint8{gosocks5.MethodUserPass}
}
c := gosocks5.ClientConn(conn, conf) c := gosocks5.ClientConn(conn, conf)
if err := c.Handleshake(); err != nil { 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) { func proxyMethodSelected(method uint8, conn net.Conn) (net.Conn, error) {
switch method { switch method {
case gosocks5.MethodUserPass: case gosocks5.MethodUserPass:
if proxyURL == nil || proxyURL.User == nil { var user, pass string
return nil, gosocks5.ErrAuthFailure
if proxyURL != nil && proxyURL.User != nil {
user = proxyURL.User.Username()
pass, _ = proxyURL.User.Password()
} }
pwd, _ := proxyURL.User.Password() if err := clientSocksAuth(conn, user, pass); err != nil {
if err := gosocks5.NewUserPassRequest(gosocks5.UserPassVer,
proxyURL.User.Username(), pwd).Write(conn); err != nil {
return nil, err 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: case gosocks5.MethodNoAcceptable:
return nil, gosocks5.ErrBadMethod return nil, gosocks5.ErrBadMethod
//case gosocks5.MethodNoAuth:
} }
return conn, nil 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) { func setBasicAuth(r *http.Request) {
if proxyURL != nil && proxyURL.User != nil { if proxyURL != nil && proxyURL.User != nil {
r.Header.Set("Proxy-Authorization", r.Header.Set("Proxy-Authorization",

View File

@ -5,7 +5,7 @@ import (
) )
const ( const (
Version = "1.6" Version = "1.7"
) )
func printVersion() { func printVersion() {