diff --git a/models/plugin/http-proxy/install.sh b/models/plugin/http-proxy/install.sh new file mode 100755 index 00000000..bae6c20e --- /dev/null +++ b/models/plugin/http-proxy/install.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +export GOPATH=$GOPATH:$PWD + +cd $PWD/src/main +go build -o http-proxy + +echo "successfully build,binary executable file in src/main" diff --git a/models/plugin/http-proxy/src/config/config.json b/models/plugin/http-proxy/src/config/config.json new file mode 100644 index 00000000..80f74ee0 --- /dev/null +++ b/models/plugin/http-proxy/src/config/config.json @@ -0,0 +1,5 @@ +{ + "port":":8080", + "auth":true, + "user":{"proxy":"proxy"} +} diff --git a/models/plugin/http-proxy/src/main/http-proxy b/models/plugin/http-proxy/src/main/http-proxy new file mode 100755 index 00000000..f253b566 Binary files /dev/null and b/models/plugin/http-proxy/src/main/http-proxy differ diff --git a/models/plugin/http-proxy/src/main/main.go b/models/plugin/http-proxy/src/main/main.go new file mode 100644 index 00000000..2d7a8eff --- /dev/null +++ b/models/plugin/http-proxy/src/main/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "proxy" + + "github.com/fatedier/frp/utils/log" +) + +func main() { + ps := proxy.NewProxyServer() + + log.Info("begin proxy") + log.Error("proxy exit %v", ps.ListenAndServe()) +} diff --git a/models/plugin/http-proxy/src/proxy/auth.go b/models/plugin/http-proxy/src/proxy/auth.go new file mode 100644 index 00000000..0fd7d50a --- /dev/null +++ b/models/plugin/http-proxy/src/proxy/auth.go @@ -0,0 +1,98 @@ +package proxy + +import ( + "encoding/base64" + "errors" + "net/http" + "strings" + + "github.com/fatedier/frp/utils/log" +) + +/* +http://www.checkupdown.com/status/E407_zh.html +HTTP 407 错误 - 要求代理身份验证 (Proxy authentication required) +您的 Web 服务器认为客户端(如您的浏览器或我们的 CheckUpDown 机器人)发送的 HTTP 数据流是正确的,但访问该网址资源需要事先经过一个代理服务器,而该代理服务器所需的身份验证尚未提供。 这通常意味着您必须首先登录(输入用户名和密码)代理服务器。 + +通过浏览器检测到的 407 错误往往可以通过选择略有不同的导航途径访问该网址来解决, 如先访问该代理服务器的其他网址 。 您的互联网服务供应商 (ISP) 应该能够解释该代理服务器在其安全设置中的作用,以及如何使用。 + +*/ + +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407 +/* +HTTP/1.1 407 Proxy Authentication Required +Date: Wed, 21 Oct 2015 07:28:00 GMT +Proxy-Authenticate: Basic realm="Access to internal site" +*/ +var HTTP_407 = []byte("HTTP/1.1 407 Proxy Authorization Required\r\nProxy-Authenticate: Basic realm=\"Access to internal site\"\r\n\r\n") + +// 鉴权方法 +func (proxy *ProxyServer) Auth(rw http.ResponseWriter, req *http.Request) bool { + var err error + if cnfg.Auth == true { // 代理服务器登入认证 + if proxy.Name, err = proxy.auth(rw, req); err != nil { + log.Debug("%s can not successfully access %v", proxy.Name, err) + return true + } + } else { + proxy.Name = "default-proxy" + } + + // log.Info("%s successfully log in!", proxy.Name) + return false +} + +func (proxy *ProxyServer) auth(rw http.ResponseWriter, req *http.Request) (string, error) { + + // get header + // Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l + auth := req.Header.Get("Proxy-Authorization") + auth = strings.Replace(auth, "Basic ", "", 1) + + if auth == "" { + writeResp(rw, HTTP_407) + return "", errors.New("Need Proxy Authorization!") + } + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization + data, err := base64.StdEncoding.DecodeString(auth) + if err != nil { + log.Debug("when decoding %v, got an error of %v", auth, err) + return "", errors.New("Fail to decoding Proxy-Authorization") + } + + var user, passwd string + + // username:password + UserPasswdPair := strings.Split(string(data), ":") + if len(UserPasswdPair) != 2 { + writeResp(rw, HTTP_407) + return "", errors.New("Fail to log in") + } else { + user = UserPasswdPair[0] + passwd = UserPasswdPair[1] + } + + if check(user, passwd) == false { + writeResp(rw, HTTP_407) + return "", errors.New("Fail to log in") + } + return user, nil +} + +func writeResp(rw http.ResponseWriter, data []byte) error { + _, err := rw.Write(data) + if err != nil { + log.Error("fail to write data to response") + return errors.New("InternalServerError") + } + return nil +} + +// 验证浏览器输入的proxy是否合法 +func check(User, passwd string) bool { + if User != "" && passwd != "" && cnfg.User[User] == passwd { + return true + } + return false +} diff --git a/models/plugin/http-proxy/src/proxy/config.go b/models/plugin/http-proxy/src/proxy/config.go new file mode 100644 index 00000000..4e8d0c16 --- /dev/null +++ b/models/plugin/http-proxy/src/proxy/config.go @@ -0,0 +1,32 @@ +package proxy + +import ( + "bufio" + "encoding/json" + "os" +) + +// Config 保存代理服务器的配置 +type Config struct { + Port string `json:"port"` + Auth bool `json:"auth"` + + User map[string]string `json:"user"` +} + +// 从指定json文件读取config配置 +func (c *Config) GetConfig(filename string) error { + + configFile, err := os.Open(filename) + if err != nil { + return err + } + defer configFile.Close() + + br := bufio.NewReader(configFile) + err = json.NewDecoder(br).Decode(c) + if err != nil { + return err + } + return nil +} diff --git a/models/plugin/http-proxy/src/proxy/init.go b/models/plugin/http-proxy/src/proxy/init.go new file mode 100644 index 00000000..f00aa174 --- /dev/null +++ b/models/plugin/http-proxy/src/proxy/init.go @@ -0,0 +1,24 @@ +package proxy + +import ( + "os" + + "github.com/fatedier/frp/utils/log" +) + +var cnfg Config + +func init() { + // 加载配置文件 + err := cnfg.GetConfig("../config/config.json") + if err != nil { + log.Error("can not load config file:%v\n", err) + os.Exit(-1) + } + + setLog() +} + +func setLog() { + log.SetLogLevel("debug") +} diff --git a/models/plugin/http-proxy/src/proxy/proxy.go b/models/plugin/http-proxy/src/proxy/proxy.go new file mode 100644 index 00000000..befd564c --- /dev/null +++ b/models/plugin/http-proxy/src/proxy/proxy.go @@ -0,0 +1,118 @@ +package proxy + +import ( + "fmt" + "io" + "net" + "net/http" + "time" + "util" + + "github.com/fatedier/frp/utils/log" +) + +var HTTP_200 = []byte("HTTP/1.1 200 Connection Established\r\n\r\n") + +type ProxyServer struct { + Tr *http.Transport + Name string +} + +func NewProxyServer() *http.Server { + + return &http.Server{ + Addr: cnfg.Port, + Handler: &ProxyServer{Tr: http.DefaultTransport.(*http.Transport), Name: "default-proxy"}, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } +} + +// 自动执行的方法,因为ProxyServer实现了Handler接口,需要ServerHTTP +func (proxy *ProxyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + defer func() { + if err := recover(); err != nil { + rw.WriteHeader(http.StatusInternalServerError) + log.Error("Panic: %v", err) + fmt.Fprintf(rw, fmt.Sprintln(err)) + } + }() + + // 鉴权 + if proxy.Auth(rw, req) { + return + } + + if req.Method == "CONNECT" { // 是connect连接 + proxy.HttpsHandler(rw, req) + } else { + proxy.HttpHandler(rw, req) + } +} + +// 处理普通的http请求 +func (proxy *ProxyServer) HttpHandler(rw http.ResponseWriter, req *http.Request) { + log.Info("%v is sending request %v %v ", proxy.Name, req.Method, req.URL.Host) + util.RemoveProxyHeaders(req) // 去除不必要的头 + + resp, err := proxy.Tr.RoundTrip(req) + if err != nil { + log.Error("%s transport RoundTrip error: %v", proxy.Name, err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + util.ClearHeaders(rw.Header()) // 得到一个空的Header + util.CopyHeaders(rw.Header(), resp.Header) + rw.WriteHeader(resp.StatusCode) + + nr, err := io.Copy(rw, resp.Body) + if err != nil && err != io.EOF { + log.Error("%v got an error when copy remote response to client.%v", proxy.Name, err) + return + } + log.Info("%v copied %v bytes from remote host %v.", proxy.Name, nr, req.URL.Host) +} + +// 处理https连接,主要用于CONNECT方法 +func (proxy *ProxyServer) HttpsHandler(rw http.ResponseWriter, req *http.Request) { + log.Info("[CONNECT] %v tried to connect to remote host %v", proxy.Name, req.URL.Host) + + hj, _ := rw.(http.Hijacker) + client, _, err := hj.Hijack() //获取客户端与代理服务器的tcp连接 + if err != nil { + log.Error("%v failed to get Tcp connection of", proxy.Name, req.RequestURI) + http.Error(rw, "Failed", http.StatusBadRequest) + return + } + + remote, err := net.Dial("tcp", req.URL.Host) //建立服务端和代理服务器的tcp连接 + if err != nil { + log.Error("%v failed to connect %v", proxy.Name, req.RequestURI) + http.Error(rw, "Failed", http.StatusBadRequest) + client.Close() + return + } + + client.Write(HTTP_200) + + go copyRemoteToClient(proxy.Name, remote, client) + go copyRemoteToClient(proxy.Name, client, remote) +} + +// data copy between two socket +func copyRemoteToClient(Name string, remote, client net.Conn) { + defer func() { + remote.Close() + client.Close() + }() + + nr, err := io.Copy(remote, client) + if err != nil && err != io.EOF { + log.Error("%v got an error when handles CONNECT %v", Name, err) + return + } + log.Info("[CONNECT] %v transported %v bytes betwwen %v and %v", Name, nr, remote.RemoteAddr(), client.RemoteAddr()) +} diff --git a/models/plugin/http-proxy/src/util/header_util.go b/models/plugin/http-proxy/src/util/header_util.go new file mode 100644 index 00000000..dd52c6a8 --- /dev/null +++ b/models/plugin/http-proxy/src/util/header_util.go @@ -0,0 +1,30 @@ +package util + +import "net/http" + +func CopyHeaders(dst, src http.Header) { + for key, values := range src { + for _, value := range values { + dst.Add(key, value) + } + } +} + +func ClearHeaders(headers http.Header) { + for key, _ := range headers { + headers.Del(key) + } +} + +func RemoveProxyHeaders(req *http.Request) { + req.RequestURI = "" + req.Header.Del("Proxy-Connection") + req.Header.Del("Connection") + req.Header.Del("Keep-Alive") + req.Header.Del("Proxy-Authenticate") + req.Header.Del("Proxy-Authorization") + req.Header.Del("TE") + req.Header.Del("Trailers") + req.Header.Del("Transfer-Encoding") + req.Header.Del("Upgrade") +}