package httpreverse import ( "b612.me/apps/b612/httpreverse/rp" "b612.me/starlog" "bytes" "context" "crypto/tls" "encoding/base64" "fmt" "io/ioutil" "net" "net/http" "net/url" "strconv" "strings" "time" ) var version = "2.1.0" func (h *ReverseConfig) Run() error { err := h.init() if err != nil { return err } for key, proxy := range h.proxy { h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) { starlog.Infof("<%s> Req Path:%s ListenAddr:%s UA:%s\n", h.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent")) if !h.BasicAuth(writer, request) { h.SetResponseHeader(writer) return } if !h.filter(writer, request) { h.SetResponseHeader(writer) writer.WriteHeader(403) if len(h.warnpagedata) != 0 { writer.Write(h.warnpagedata) return } writer.Write([]byte(` 403 Forbidden

403 Forbidden

You Are Not Allowed to Access This Page

Please Contact Site Administrator For Help


B612 HTTP REVERSE SERVER
`)) return } proxy.ServeHTTP(writer, request) }) } h.httpserver = http.Server{ Addr: fmt.Sprintf("%s:%d", h.Addr, h.Port), Handler: &h.httpmux, } starlog.Infoln(h.Name + " Listening on " + h.Addr + ":" + strconv.Itoa(h.Port)) if !h.UsingSSL { if err := h.httpserver.ListenAndServe(); err != http.ErrServerClosed { return err } return nil } if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed { return err } return nil } func (h *ReverseConfig) Close() error { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() return h.httpserver.Shutdown(ctx) } func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.Conn, error) { conn, err := net.DialTimeout(network, addr, time.Second*20) if err != nil { return nil, err } host, _, err := net.SplitHostPort(addr) if err != nil { return nil, err } if h.Host != "" { host = h.Host } cfg := &tls.Config{ServerName: host} tlsConn := tls.Client(conn, cfg) if err := tlsConn.Handshake(); err != nil { conn.Close() return nil, err } cs := tlsConn.ConnectionState() cert := cs.PeerCertificates[0] // Verify here if !h.SkipSSLVerify { err = cert.VerifyHostname(host) if err != nil { return nil, err } } return tlsConn, nil } func (h *ReverseConfig) init() error { h.proxy = make(map[string]*rp.ReverseProxy) for key, val := range h.ReverseURL { h.proxy[key] = &rp.ReverseProxy{ Transport: &http.Transport{DialTLSContext: h.dialTLS}, } h.proxy[key].ModifyResponse = h.ModifyResponse() h.proxy[key].Director = func(req *http.Request) { targetQuery := val.RawQuery req.URL.Scheme = val.Scheme if h.Host == "" { req.Host = val.Host } else { req.Host = h.Host } req.URL.Host = val.Host req.URL.Path, req.URL.RawPath = joinURLPath(val, req.URL, key) if targetQuery == "" || req.URL.RawQuery == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery } else { req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery } h.ModifyRequest(req, val) } } return nil } func (h *ReverseConfig) SetResponseHeader(resp http.ResponseWriter) { resp.Header().Set("X-Powered-By", "B612.ME") resp.Header().Set("Server", "B612/"+version) resp.Header().Set("X-Proxy", "B612 Reverse Proxy") } func (h *ReverseConfig) ModifyResponse() func(*http.Response) error { return func(resp *http.Response) error { for _, v := range h.OutHeader { resp.Header.Set(v[0], v[1]) } resp.Header.Del("X-Powered-By") resp.Header.Del("Server") resp.Header.Del("X-Proxy") resp.Header.Set("X-Powered-By", "B612.ME") resp.Header.Set("Server", "B612/"+version) resp.Header.Set("X-Proxy", "B612 Reverse Proxy") if len(h.ReplaceList) != 0 && resp.ContentLength <= 20*1024*1024 && strings.Contains(resp.Header.Get("Content-Type"), "text") { body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } strBody := string(body) replaceCount := -1 if h.ReplaceOnce { replaceCount = 1 } for _, v := range h.ReplaceList { strBody = strings.Replace(strBody, v[0], v[1], replaceCount) } body = []byte(strBody) resp.Body = ioutil.NopCloser(bytes.NewReader(body)) resp.ContentLength = int64(len(body)) resp.Header.Set("Content-Length", strconv.Itoa(len(body))) } return nil } } func (h *ReverseConfig) isInCIDR(ip string) bool { nip := net.ParseIP(strings.TrimSpace(ip)) if nip == nil { return false } for _, c := range h.CIDR { if c.Contains(nip) { return true } } return false } func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) { switch h.IPFilterMode { case 1: req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0]) case 2: xforward := strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ",") xforward = append(xforward, strings.Split(req.RemoteAddr, ":")[0]) req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", ")) case 3: var lastForwardIP string var xforward []string if h.FilterMustKey != "" && req.Header.Get(h.FilterMustKey) != "" { lastForwardIP = req.Header.Get(h.FilterMustKey) xforward = []string{lastForwardIP} } else { for _, ip := range append(strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ","), strings.Split(req.RemoteAddr, ":")[0]) { ip = strings.TrimSpace(ip) if !h.isInCIDR(ip) { xforward = append(xforward, ip) lastForwardIP = ip } } } if lastForwardIP == "" { lastForwardIP = strings.Split(req.RemoteAddr, ":")[0] } if h.FilterXForward { req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", ")) } if h.FilterRemoteAddr { req.Header.Set("X-Real-IP", lastForwardIP) } if h.FilterSetKey != "" { req.Header.Set(h.FilterSetKey, lastForwardIP) } } for _, v := range h.Cookie { req.AddCookie(&http.Cookie{ Name: v[1], Value: v[2], Path: v[0], }) } for _, v := range h.InHeader { req.Header.Set(v[0], v[1]) } } func (h *ReverseConfig) GiveBasicAuth(w http.ResponseWriter) { w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`) w.WriteHeader(401) w.Write([]byte(` 401 Authorization Required

401 Authorization Required


B612 HTTP SERVER
`)) } func (h *ReverseConfig) BasicAuth(w http.ResponseWriter, r *http.Request) bool { if h.basicAuthPwd != "" { if len(h.protectAuthPage) != 0 { for _, v := range h.protectAuthPage { if !(strings.Index(r.URL.Path, v) == 0 || strings.Contains(r.URL.RawQuery, v)) { return true } else { break } } } authHeader := strings.TrimSpace(r.Header.Get("Authorization")) if len(authHeader) == 0 { h.GiveBasicAuth(w) return false } else { userAuth := base64.StdEncoding.EncodeToString([]byte(h.basicAuthUser + ":" + h.basicAuthPwd)) authStr := strings.Split(authHeader, " ") if strings.TrimSpace(authStr[1]) != userAuth || strings.ToLower(strings.TrimSpace(authStr[0])) != "basic" { h.GiveBasicAuth(w) return false } } } return true } func (h *ReverseConfig) filter(w http.ResponseWriter, r *http.Request) bool { if len(h.blackip) == 0 && len(h.whiteip) == 0 { return true } if len(h.whiteip) != 0 { if _, ok := h.whiteip[strings.Split(r.RemoteAddr, ":")[0]]; ok { return true } for k, v := range h.whiteip { if match, _ := IPinRange2(k, v, strings.Split(r.RemoteAddr, ":")[0]); match { return true } } return false } if _, ok := h.blackip[strings.Split(r.RemoteAddr, ":")[0]]; ok { return false } for k, v := range h.blackip { if match, _ := IPinRange2(k, v, strings.Split(r.RemoteAddr, ":")[0]); match { return false } } return true } func joinURLPath(a, b *url.URL, hpath string) (path, rawpath string) { if hpath != "/" { b.Path = strings.TrimPrefix(b.Path, hpath) b.RawPath = strings.TrimPrefix(b.RawPath, hpath) } if a.RawPath == "" && b.RawPath == "" { return singleJoiningSlash(a.Path, b.Path), "" } // Same as singleJoiningSlash, but uses EscapedPath to determine // whether a slash should be added apath := a.EscapedPath() bpath := b.EscapedPath() aslash := strings.HasSuffix(apath, "/") bslash := strings.HasPrefix(bpath, "/") switch { case aslash && bslash: return a.Path + b.Path[1:], apath + bpath[1:] case !aslash && !bslash: return a.Path + "/" + b.Path, apath + "/" + bpath } return a.Path + b.Path, apath + bpath } func singleJoiningSlash(a, b string) string { aslash := strings.HasSuffix(a, "/") bslash := strings.HasPrefix(b, "/") switch { case aslash && bslash: return a + b[1:] case !aslash && !bslash: return a + "/" + b } return a + b }