From 95db78bc0b256259158be3c124ba02e7d67a49bf Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 20 Oct 2017 10:20:28 +0800 Subject: [PATCH] Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 2 +- README.md | 13 +++++- config.go | 2 + services/args.go | 2 + services/socks.go | 102 +++++++++++++++++++++++++++++++++++++--------- utils/structs.go | 7 +++- 6 files changed, 105 insertions(+), 23 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b46a797..900e64e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ proxy更新日志 v3.4 -1. +1.socks5代理新增了用户名密码验证支持. v3.3 1.修复了socks代理模式对证书文件的判断逻辑. diff --git a/README.md b/README.md index 12d5be2..a000608 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [v3.0手册](https://github.com/snail007/goproxy/tree/v3.0) - [v2.x手册](https://github.com/snail007/goproxy/tree/v2.2) +[TOC] + ### Fast Start 提示:所有操作需要root权限. **0.如果你的VPS是linux64位的系统,那么只需要执行下面一句,就可以完成自动安装和配置.** @@ -420,11 +422,18 @@ server连接到bridge的时候,如果同时有多个client连接到同一个brid 那么访问本地的28080端口就是通过VPS访问目标地址. -**5.7.查看帮助** +**5.7.认证** +对于socks5代理协议我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 +`./proxy socks -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` +多个用户,重复-a参数即可. +也可以放在文件中,格式是一行一个"用户名:密码",然后用-F指定. +`./proxy socks -t tcp -p ":33080" -F auth-file.txt` +如果没有-a或-F参数,就是关闭认证. + +**5.8.查看帮助** `./proxy help socks` ### TODO -- SOCKS5增加用户名密码认证? - http,socks代理多个上级负载均衡? - 内网穿透server<->bridge心跳机制? - 欢迎加群反馈... diff --git a/config.go b/config.go index 64fe49d..300d63a 100755 --- a/config.go +++ b/config.go @@ -124,6 +124,8 @@ func initConfig() (err error) { socksArgs.Interval = socks.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int() socksArgs.Blocked = socks.Flag("blocked", "blocked domain file , one domain each line").Default("blocked").Short('b').String() socksArgs.Direct = socks.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String() + socksArgs.AuthFile = socks.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() + socksArgs.Auth = socks.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) flags := log.Ldate diff --git a/services/args.go b/services/args.go index dfee7b6..3b91f26 100644 --- a/services/args.go +++ b/services/args.go @@ -118,6 +118,8 @@ type SocksArgs struct { Interval *int Blocked *string Direct *string + AuthFile *string + Auth *[]string } func (a *TCPArgs) Protocol() string { diff --git a/services/socks.go b/services/socks.go index b30f461..38322c2 100644 --- a/services/socks.go +++ b/services/socks.go @@ -78,6 +78,7 @@ func (s *Socks) CheckArgs() { } func (s *Socks) InitService() { + s.InitBasicAuth() s.checker = utils.NewChecker(*s.cfg.Timeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct) if *s.cfg.ParentType == "ssh" { err := s.ConnectSSH() @@ -94,9 +95,6 @@ func (s *Socks) InitService() { if err != nil { if s.sshClient != nil { s.sshClient.Close() - if s.sshClient.Conn != nil { - s.sshClient.Conn.Close() - } } log.Printf("ssh offline, retrying...") s.ConnectSSH() @@ -290,28 +288,72 @@ func (s *Socks) socksConnCallback(inConn net.Conn) { } utils.CloseConn(&inConn) }() + //协商开始 //method select request + inConn.SetReadDeadline(time.Now().Add(time.Second * 3)) methodReq, err := socks.NewMethodsRequest(inConn) - if err != nil || !methodReq.Select(socks.Method_NO_AUTH) { + inConn.SetReadDeadline(time.Time{}) + if err != nil { methodReq.Reply(socks.Method_NONE_ACCEPTABLE) utils.CloseConn(&inConn) - if err != nil { - log.Printf("new methods request fail,ERR: %s", err) + log.Printf("new methods request fail,ERR: %s", err) + return + } + + if !s.IsBasicAuth() { + if !methodReq.Select(socks.Method_NO_AUTH) { + methodReq.Reply(socks.Method_NONE_ACCEPTABLE) + utils.CloseConn(&inConn) + log.Printf("none method found : Method_NO_AUTH") + return + } + //method select reply + err = methodReq.Reply(socks.Method_NO_AUTH) + if err != nil { + log.Printf("reply answer data fail,ERR: %s", err) + utils.CloseConn(&inConn) + return + } + // log.Printf("% x", methodReq.Bytes()) + } else { + //auth + if !methodReq.Select(socks.Method_USER_PASS) { + methodReq.Reply(socks.Method_NONE_ACCEPTABLE) + utils.CloseConn(&inConn) + log.Printf("none method found : Method_USER_PASS") + return + } + //method reply need auth + err = methodReq.Reply(socks.Method_USER_PASS) + if err != nil { + log.Printf("reply answer data fail,ERR: %s", err) + utils.CloseConn(&inConn) + return + } + //read auth + buf := make([]byte, 500) + inConn.SetReadDeadline(time.Now().Add(time.Second * 3)) + n, err := inConn.Read(buf) + inConn.SetReadDeadline(time.Time{}) + if err != nil { + utils.CloseConn(&inConn) + return + } + r := buf[:n] + user := string(r[2 : r[1]+2]) + pass := string(r[2+r[1]+1:]) + //log.Printf("user:%s,pass:%s", user, pass) + //auth + if s.basicAuth.CheckUserPass(user, pass) { + inConn.Write([]byte{0x01, 0x00}) + } else { + inConn.Write([]byte{0x01, 0x01}) + utils.CloseConn(&inConn) + return } - return } - //method select reply - err = methodReq.Reply(socks.Method_NO_AUTH) - if err != nil { - log.Printf("reply answer data fail,ERR: %s", err) - utils.CloseConn(&inConn) - return - } - - // log.Printf("% x", methodReq.Bytes()) - //request detail request, err := socks.NewRequest(inConn) if err != nil { @@ -319,6 +361,7 @@ func (s *Socks) socksConnCallback(inConn net.Conn) { utils.CloseConn(&inConn) return } + //协商结束 switch request.CMD() { case socks.CMD_BIND: @@ -447,7 +490,7 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n return } wait := make(chan bool, 1) - func() { + go func() { defer func() { if err == nil { err = recover() @@ -458,8 +501,9 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n }() select { case <-wait: - case <-time.After(time.Second * 5): + case <-time.After(time.Millisecond * time.Duration(*s.cfg.Timeout) * 2): err = fmt.Errorf("ssh dial %s timeout", host) + s.sshClient.Close() } if err != nil { log.Printf("connect ssh fail, ERR: %s, retrying...", err) @@ -498,3 +542,23 @@ func (s *Socks) ConnectSSH() (err error) { <-s.lockChn return } +func (s *Socks) InitBasicAuth() (err error) { + s.basicAuth = utils.NewBasicAuth() + if *s.cfg.AuthFile != "" { + var n = 0 + n, err = s.basicAuth.AddFromFile(*s.cfg.AuthFile) + if err != nil { + err = fmt.Errorf("auth-file ERR:%s", err) + return + } + log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total()) + } + if len(*s.cfg.Auth) > 0 { + n := s.basicAuth.Add(*s.cfg.Auth) + log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total()) + } + return +} +func (s *Socks) IsBasicAuth() bool { + return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 +} diff --git a/utils/structs.go b/utils/structs.go index 7f67635..56abac8 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -213,7 +213,12 @@ func (ba *BasicAuth) Add(userpassArr []string) (n int) { } return } - +func (ba *BasicAuth) CheckUserPass(user, pass string) (ok bool) { + if p, _ok := ba.data.Get(user); _ok { + return p.(string) == pass + } + return +} func (ba *BasicAuth) Check(userpass string) (ok bool) { u := strings.Split(strings.Trim(userpass, " "), ":") if len(u) == 2 {