3.0.1 init commit

- Removed Go HTTP client UA
- Added optional bypass of websocket origin check #107
- Added basic forward proxy for debug
- Fixed UI error in network utils tab
This commit is contained in:
Toby Chui 2024-03-10 14:49:18 +08:00
parent 9b2168466c
commit 200c924acd
29 changed files with 849 additions and 37 deletions

View File

@ -4,7 +4,7 @@ import (
"encoding/json"
"net/http"
strip "github.com/grokify/html-strip-tags-go"
"github.com/microcosm-cc/bluemonday"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/utils"
)
@ -137,7 +137,8 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
}
comment, _ := utils.PostPara(r, "comment")
comment = strip.StripTags(comment)
p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment)
geodbStore.AddCountryCodeToWhitelist(countryCode, comment)
@ -164,7 +165,8 @@ func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
}
comment, _ := utils.PostPara(r, "comment")
comment = strip.StripTags(comment)
p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment)
geodbStore.AddIPToWhiteList(ipAddr, comment)
}

View File

@ -163,6 +163,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet)
authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet)
authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend)
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
//Account Reset
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)

View File

@ -10,11 +10,10 @@ require (
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.5.0
github.com/grandcat/zeroconf v1.0.0
github.com/grokify/html-strip-tags-go v0.1.0
github.com/likexian/whois v1.15.1
github.com/microcosm-cc/bluemonday v1.0.25
golang.org/x/net v0.14.0
golang.org/x/sys v0.11.0
golang.org/x/text v0.12.0
golang.org/x/net v0.20.0
golang.org/x/sys v0.16.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.12.0 // indirect
)

View File

@ -740,8 +740,6 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@ -1146,8 +1144,9 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -1268,8 +1267,9 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1421,8 +1421,9 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1437,6 +1438,7 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1455,8 +1457,9 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@ -17,6 +17,7 @@ import (
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
@ -49,9 +50,9 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
var (
name = "Zoraxy"
version = "3.0.0"
version = "3.0.1"
nodeUUID = "generic"
development = false //Set this to false to use embedded web fs
development = true //Set this to false to use embedded web fs
bootTime = time.Now().Unix()
/*
@ -79,6 +80,7 @@ var (
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
//Helper modules
EmailSender *email.Sender //Email sender that handle email sending

View File

@ -272,6 +272,14 @@ func removeHeaders(header http.Header, noCache bool) {
header.Del("Cache-Control")
header.Set("Cache-Control", "no-store")
}
//Hide Go-HTTP-Client UA if the client didnt sent us one
if _, ok := header["User-Agent"]; !ok {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent.
header.Set("User-Agent", "")
}
}
func addXForwardedForHeader(req *http.Request) {
@ -365,6 +373,12 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
}
}
//if res.StatusCode == 501 || res.StatusCode == 500 {
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
// fmt.Println(outreq.Header, req.Host)
//}
//Custom header rewriter functions
if res.Header.Get("Location") != "" {
locationRewrite := res.Header.Get("Location")

View File

@ -114,7 +114,10 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
}
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.SkipWebSocketOriginCheck,
})
wspHandler.ServeHTTP(w, r)
return
}
@ -178,7 +181,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
}
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
})
wspHandler.ServeHTTP(w, r)
return
}

View File

@ -95,9 +95,10 @@ type ProxyEndpoint struct {
Domain string //Domain or IP to proxy to
//TLS/SSL Related
RequireTLS bool //Target domain require TLS
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
RequireTLS bool //Target domain require TLS
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
SkipCertValidations bool //Set to true to accept self signed certs
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
//Virtual Directories
VirtualDirectories []*VirtualDirectoryEndpoint
@ -115,6 +116,7 @@ type ProxyEndpoint struct {
DefaultSiteValue string //Fallback routing target, optional
Disabled bool //If the rule is disabled
//Internal Logic Elements
parent *Router
proxy *dpcore.ReverseProxy `json:"-"`

View File

@ -0,0 +1,25 @@
MIT License
Copyright (c) 2022 Smarty
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
NOTE: Various optional and subordinate components carry their own licensing
requirements and restrictions. Use of those components is subject to the terms
and conditions outlined the respective license of each component.

View File

@ -0,0 +1,109 @@
package cproxy
import (
"net/http"
"time"
)
func New(options ...option) http.Handler {
var this configuration
Options.apply(options...)(&this)
return newHandler(this.Filter, this.ClientConnector, this.ServerConnector, this.Monitor)
}
var Options singleton
type singleton struct{}
type option func(*configuration)
type configuration struct {
DialTimeout time.Duration
Filter Filter
DialAddress string
Dialer Dialer
LogConnections bool
ProxyProtocol bool
Initializer initializer
ClientConnector clientConnector
ServerConnector serverConnector
Monitor monitor
Logger logger
}
func (singleton) DialTimeout(value time.Duration) option {
return func(this *configuration) { this.DialTimeout = value }
}
func (singleton) Filter(value Filter) option {
return func(this *configuration) { this.Filter = value }
}
func (singleton) ClientConnector(value clientConnector) option {
return func(this *configuration) { this.ClientConnector = value }
}
func (singleton) DialAddress(value string) option {
return func(this *configuration) { this.DialAddress = value }
}
func (singleton) Dialer(value Dialer) option {
return func(this *configuration) { this.Dialer = value }
}
func (singleton) LogConnections(value bool) option {
return func(this *configuration) { this.LogConnections = value }
}
func (singleton) ProxyProtocol(value bool) option {
return func(this *configuration) { this.ProxyProtocol = value }
}
func (singleton) Initializer(value initializer) option {
return func(this *configuration) { this.Initializer = value }
}
func (singleton) ServerConnector(value serverConnector) option {
return func(this *configuration) { this.ServerConnector = value }
}
func (singleton) Monitor(value monitor) option {
return func(this *configuration) { this.Monitor = value }
}
func (singleton) Logger(value logger) option {
return func(this *configuration) { this.Logger = value }
}
func (singleton) apply(options ...option) option {
return func(this *configuration) {
for _, item := range Options.defaults(options...) {
item(this)
}
if this.Dialer == nil {
this.Dialer = newDialer(this)
}
this.Dialer = newRoutingDialer(this)
if this.ProxyProtocol {
this.Initializer = newProxyProtocolInitializer()
}
if this.Initializer == nil {
this.Initializer = nop{}
}
this.Initializer = newLoggingInitializer(this)
if this.ServerConnector == nil {
this.ServerConnector = newServerConnector(this.Dialer, this.Initializer)
}
}
}
func (singleton) defaults(options ...option) []option {
return append([]option{
Options.DialTimeout(time.Second * 10),
Options.Filter(newFilter()),
Options.ClientConnector(newClientConnector()),
Options.Initializer(nop{}),
Options.Monitor(nop{}),
Options.Logger(nop{}),
}, options...)
}
type nop struct{}
func (nop) Measure(int) {}
func (nop) Printf(string, ...interface{}) {}
func (nop) Initialize(Socket, Socket) bool { return true }

View File

@ -0,0 +1,19 @@
package cproxy
import "net/http"
type defaultClientConnector struct{}
func newClientConnector() *defaultClientConnector {
return &defaultClientConnector{}
}
func (this *defaultClientConnector) Connect(response http.ResponseWriter) Socket {
if hijacker, ok := response.(http.Hijacker); !ok {
return nil
} else if socket, _, _ := hijacker.Hijack(); socket == nil {
return nil // this 'else if' exists to avoid the pointer nil != interface nil issue
} else {
return socket
}
}

View File

@ -0,0 +1,25 @@
package cproxy
import (
"net"
"time"
)
type defaultDialer struct {
timeout time.Duration
logger logger
}
func newDialer(config *configuration) *defaultDialer {
return &defaultDialer{timeout: config.DialTimeout, logger: config.Logger}
}
func (this *defaultDialer) Dial(address string) Socket {
if socket, err := net.DialTimeout("tcp", address, this.timeout); err == nil {
return socket
} else {
this.logger.Printf("[INFO] Unable to establish connection to [%s]: %s", address, err)
}
return nil
}

View File

@ -0,0 +1,9 @@
package cproxy
import "net/http"
type defaultFilter struct{}
func newFilter() *defaultFilter { return &defaultFilter{} }
func (this *defaultFilter) IsAuthorized(http.ResponseWriter, *http.Request) bool { return true }

View File

@ -0,0 +1,56 @@
package cproxy
import "net/http"
type defaultHandler struct {
filter Filter
clientConnector clientConnector
serverConnector serverConnector
meter monitor
}
func newHandler(filter Filter, clientConnector clientConnector, serverConnector serverConnector, meter monitor) *defaultHandler {
return &defaultHandler{
filter: filter,
clientConnector: clientConnector,
serverConnector: serverConnector,
meter: meter,
}
}
func (this *defaultHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
this.meter.Measure(MeasurementHTTPRequest)
if request.Method != "CONNECT" {
this.meter.Measure(MeasurementBadMethod)
writeResponseStatus(response, http.StatusMethodNotAllowed)
} else if !this.filter.IsAuthorized(response, request) {
this.meter.Measure(MeasurementUnauthorizedRequest)
//writeResponseStatus(response, http.StatusUnauthorized)
} else if client := this.clientConnector.Connect(response); client == nil {
this.meter.Measure(MeasurementClientConnectionFailed)
writeResponseStatus(response, http.StatusNotImplemented)
} else if connection := this.serverConnector.Connect(client, request.URL.Host); connection == nil {
this.meter.Measure(MeasurementServerConnectionFailed)
_, _ = client.Write(statusBadGateway)
_ = client.Close()
} else {
this.meter.Measure(MeasurementProxyReady)
_, _ = client.Write(statusReady)
connection.Proxy()
this.meter.Measure(MeasurementProxyComplete)
}
}
func writeResponseStatus(response http.ResponseWriter, statusCode int) {
http.Error(response, http.StatusText(statusCode), statusCode)
}
var (
statusBadGateway = []byte("HTTP/1.1 502 Bad Gateway\r\n\r\n")
statusReady = []byte("HTTP/1.1 200 OK\r\n\r\n")
)

View File

@ -0,0 +1,54 @@
package cproxy
import (
"io"
"sync"
)
type defaultProxy struct {
client Socket
server Socket
waiter *sync.WaitGroup
}
func newProxy(client, server Socket) *defaultProxy {
waiter := &sync.WaitGroup{}
waiter.Add(2) // wait on both client->server and server->client streams
return &defaultProxy{
waiter: waiter,
client: client,
server: server,
}
}
func (this *defaultProxy) Proxy() {
go this.streamAndClose(this.client, this.server)
go this.streamAndClose(this.server, this.client)
this.closeSockets()
}
func (this *defaultProxy) streamAndClose(reader, writer Socket) {
_, _ = io.Copy(writer, reader)
tryCloseRead(reader)
tryCloseWrite(writer)
this.waiter.Done()
}
func tryCloseRead(socket Socket) {
if tcp, ok := socket.(tcpSocket); ok {
_ = tcp.CloseRead()
}
}
func tryCloseWrite(socket Socket) {
if tcp, ok := socket.(tcpSocket); ok {
_ = tcp.CloseWrite()
}
}
func (this *defaultProxy) closeSockets() {
this.waiter.Wait()
_ = this.client.Close()
_ = this.server.Close()
}

View File

@ -0,0 +1,24 @@
package cproxy
type defaultServerConnector struct {
dialer Dialer
initializer initializer
}
func newServerConnector(dialer Dialer, initializer initializer) *defaultServerConnector {
return &defaultServerConnector{dialer: dialer, initializer: initializer}
}
func (this *defaultServerConnector) Connect(client Socket, serverAddress string) proxy {
server := this.dialer.Dial(serverAddress)
if server == nil {
return nil
}
if !this.initializer.Initialize(client, server) {
_ = server.Close()
return nil
}
return newProxy(client, server)
}

View File

@ -0,0 +1,32 @@
package cproxy
import "net/http"
type hostnameFilter struct {
authorized []string
}
func NewHostnameFilter(authorized []string) Filter {
return &hostnameFilter{authorized: authorized}
}
func (this hostnameFilter) IsAuthorized(_ http.ResponseWriter, request *http.Request) bool {
if len(this.authorized) == 0 {
return true
}
host := request.URL.Host
hostLength := len(host)
for _, authorized := range this.authorized {
if authorized[:2] == "*." {
have, want := hostLength, len(authorized)-1
if have > want && authorized[1:] == host[hostLength-want:] {
return true
}
} else if authorized == host {
return true
}
}
return false
}

View File

@ -0,0 +1,26 @@
package cproxy
import (
"net/http"
"strings"
)
type hostnameSuffixFilter struct {
authorized []string
}
func NewHostnameSuffixFilter(authorized []string) Filter {
return &hostnameSuffixFilter{authorized: authorized}
}
func (this hostnameSuffixFilter) IsAuthorized(_ http.ResponseWriter, request *http.Request) bool {
host := request.URL.Host
for _, authorized := range this.authorized {
if strings.HasSuffix(host, authorized) {
return true
}
}
return false
}

View File

@ -0,0 +1,67 @@
package cproxy
import (
"io"
"net"
"net/http"
)
type (
Filter interface {
IsAuthorized(http.ResponseWriter, *http.Request) bool
}
clientConnector interface {
Connect(http.ResponseWriter) Socket
}
)
type (
Dialer interface {
Dial(string) Socket
}
serverConnector interface {
Connect(Socket, string) proxy
}
initializer interface {
Initialize(Socket, Socket) bool
}
proxy interface {
Proxy()
}
)
type (
Socket interface {
io.ReadWriteCloser
RemoteAddr() net.Addr
}
tcpSocket interface {
Socket
CloseRead() error
CloseWrite() error
}
)
type (
monitor interface {
Measure(int)
}
logger interface {
Printf(string, ...interface{})
}
)
const (
MeasurementHTTPRequest int = iota
MeasurementBadMethod
MeasurementUnauthorizedRequest
MeasurementClientConnectionFailed
MeasurementServerConnectionFailed
MeasurementProxyReady
MeasurementProxyComplete
)

View File

@ -0,0 +1,24 @@
package cproxy
type loggingInitializer struct {
logger logger
inner initializer
}
func newLoggingInitializer(config *configuration) initializer {
if !config.LogConnections {
return config.Initializer
}
return &loggingInitializer{inner: config.Initializer, logger: config.Logger}
}
func (this *loggingInitializer) Initialize(client, server Socket) bool {
result := this.inner.Initialize(client, server)
if !result {
this.logger.Printf("[INFO] Connection failed [%s] -> [%s]", client.RemoteAddr(), server.RemoteAddr())
}
return result
}

View File

@ -0,0 +1,36 @@
package cproxy
import (
"fmt"
"io"
"net"
"strings"
)
type proxyProtocolInitializer struct{}
func newProxyProtocolInitializer() *proxyProtocolInitializer {
return &proxyProtocolInitializer{}
}
func (this *proxyProtocolInitializer) Initialize(client, server Socket) bool {
header := formatHeader(client.RemoteAddr(), server.RemoteAddr())
_, err := io.WriteString(server, header)
return err == nil
}
func formatHeader(client, server net.Addr) string {
clientAddress, clientPort := parseAddress(client.String())
serverAddress, serverPort := parseAddress(server.String())
if strings.Contains(clientAddress, ":") {
return fmt.Sprintf(proxyProtocolIPv6Preamble, clientAddress, serverAddress, clientPort, serverPort)
}
return fmt.Sprintf(proxyProtocolIPv4Preamble, clientAddress, serverAddress, clientPort, serverPort)
}
func parseAddress(address string) (string, string) {
address, port, _ := net.SplitHostPort(address)
return address, port
}
const proxyProtocolIPv4Preamble = "PROXY TCP4 %s %s %s %s\r\n"
const proxyProtocolIPv6Preamble = "PROXY TCP6 %s %s %s %s\r\n"

View File

@ -0,0 +1,18 @@
package cproxy
type routingDialer struct {
inner Dialer
targetAddress string
}
func newRoutingDialer(config *configuration) Dialer {
if len(config.DialAddress) == 0 {
return config.Dialer
}
return &routingDialer{inner: config.Dialer, targetAddress: config.DialAddress}
}
func (this *routingDialer) Dial(string) Socket {
return this.inner.Dial(this.targetAddress)
}

View File

@ -0,0 +1,137 @@
package forwardproxy
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"time"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/forwardproxy/cproxy"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils"
)
type ZrFilter struct {
//To be implemented
}
type Handler struct {
server *http.Server
handler *http.Handler
running bool
db *database.Database
logger *logger.Logger
Port int
}
func NewForwardProxy(sysdb *database.Database, port int, logger *logger.Logger) *Handler {
thisFilter := ZrFilter{}
handler := cproxy.New(cproxy.Options.Filter(thisFilter))
return &Handler{
db: sysdb,
server: nil,
handler: &handler,
running: false,
logger: logger,
Port: port,
}
}
// Start the forward proxy
func (h *Handler) Start() error {
if h.running {
return errors.New("forward proxy already running")
}
server := &http.Server{Addr: ":" + strconv.Itoa(h.Port), Handler: *h.handler}
h.server = server
go func() {
if err := server.ListenAndServe(); err != nil {
if err != nil {
log.Println(err.Error())
}
}
}()
h.running = true
return nil
}
// Stop the forward proxy
func (h *Handler) Stop() error {
if h.running && h.server != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := h.server.Shutdown(ctx); err != nil {
return err
}
h.running = false
h.server = nil
}
return nil
}
// Update the port number of the forward proxy
func (h *Handler) UpdatePort(newPort int) error {
h.Stop()
h.Port = newPort
return h.Start()
}
func (it ZrFilter) IsAuthorized(w http.ResponseWriter, r *http.Request) bool {
return true
}
// Handle port change of the forward proxy
func (h *Handler) HandlePort(w http.ResponseWriter, r *http.Request) {
port, err := utils.PostInt(r, "port")
if err != nil {
js, _ := json.Marshal(h.Port)
utils.SendJSONResponse(w, string(js))
} else {
//Update the port
err = h.UpdatePort(port)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy port updated to :"+strconv.Itoa(h.Port), nil)
h.db.Write("fwdproxy", "port", port)
utils.SendOK(w)
}
}
// Handle power toggle of the forward proxys
func (h *Handler) HandleToogle(w http.ResponseWriter, r *http.Request) {
enabled, err := utils.PostBool(r, "enable")
if err != nil {
//Get the current state of the forward proxy
js, _ := json.Marshal(h.running)
utils.SendJSONResponse(w, string(js))
} else {
if enabled {
err = h.Start()
if err != nil {
h.logger.PrintAndLog("Forward Proxy", "Unable to start forward proxy server", err)
utils.SendErrorResponse(w, err.Error())
return
}
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy Started, listening on :"+strconv.Itoa(h.Port), nil)
} else {
err = h.Stop()
if err != nil {
h.logger.PrintAndLog("Forward Proxy", "Unable to stop forward proxy server", err)
utils.SendErrorResponse(w, err.Error())
return
}
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy Stopped", nil)
}
h.db.Write("fwdproxy", "enabled", enabled)
utils.SendOK(w)
}
}

View File

@ -85,7 +85,10 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
r.Header.Set("Zr-Origin-Upgrade", "websocket")
requestURL = strings.TrimPrefix(requestURL, "/")
u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
wspHandler := websocketproxy.NewProxy(u, false)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: false,
SkipOriginCheck: false,
})
wspHandler.ServeHTTP(w, r)
return
}

View File

@ -47,19 +47,26 @@ type WebsocketProxy struct {
// If nil, DefaultDialer is used.
Dialer *websocket.Dialer
Verbal bool
SkipTlsValidation bool
Verbal bool
Options Options
}
// Additional options for websocket proxy runtime
type Options struct {
SkipTLSValidation bool //Skip backend TLS validation
SkipOriginCheck bool //Skip origin check
}
// ProxyHandler returns a new http.Handler interface that reverse proxies the
// request to the given target.
func ProxyHandler(target *url.URL, skipTlsValidation bool) http.Handler {
return NewProxy(target, skipTlsValidation)
func ProxyHandler(target *url.URL, options Options) http.Handler {
return NewProxy(target, options)
}
// NewProxy returns a new Websocket reverse proxy that rewrites the
// URL's to the scheme, host and base path provider in target.
func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
func NewProxy(target *url.URL, options Options) *WebsocketProxy {
backend := func(r *http.Request) *url.URL {
// Shallow copy
u := *target
@ -68,7 +75,7 @@ func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
u.RawQuery = r.URL.RawQuery
return &u
}
return &WebsocketProxy{Backend: backend, Verbal: false, SkipTlsValidation: skipTlsValidation}
return &WebsocketProxy{Backend: backend, Verbal: false, Options: options}
}
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
@ -88,7 +95,7 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
dialer := w.Dialer
if w.Dialer == nil {
if w.SkipTlsValidation {
if w.Options.SkipTLSValidation {
//Disable TLS secure check if target allow skip verification
bypassDialer := websocket.DefaultDialer
bypassDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
@ -171,6 +178,13 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
upgrader = DefaultUpgrader
}
//Fixing issue #107 by bypassing request origin check
if w.Options.SkipOriginCheck {
upgrader.CheckOrigin = func(r *http.Request) bool {
return true
}
}
// Only pass those headers to the upgrader.
upgradeHeader := http.Header{}
if hdr := resp.Header.Get("Sec-Websocket-Protocol"); hdr != "" {

View File

@ -28,7 +28,10 @@ func TestProxy(t *testing.T) {
}
u, _ := url.Parse(backendURL)
proxy := NewProxy(u, false)
proxy := NewProxy(u, Options{
SkipTLSValidation: false,
SkipOriginCheck: false,
})
proxy.Upgrader = upgrader
mux := http.NewServeMux()

View File

@ -12,6 +12,7 @@ import (
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
@ -219,6 +220,18 @@ func startupSequence() {
//Create an analytic loader
AnalyticLoader = analytic.NewDataLoader(sysdb, statisticCollector)
//Create basic forward proxy
sysdb.NewTable("fwdproxy")
fwdProxyEnabled := false
fwdProxyPort := 5587
sysdb.Read("fwdproxy", "port", &fwdProxyPort)
sysdb.Read("fwdproxy", "enabled", &fwdProxyEnabled)
forwardProxy = forwardproxy.NewForwardProxy(sysdb, fwdProxyPort, SystemWideLogger)
if fwdProxyEnabled {
SystemWideLogger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy Listening on :"+strconv.Itoa(forwardProxy.Port), nil)
forwardProxy.Start()
}
/*
ACME API
@ -241,4 +254,5 @@ func finalSequence() {
//Inject routing rules
registerBuildInRoutingRules()
}

View File

@ -84,7 +84,23 @@
Paste to Terminal <code style="float: right;">Shift + Insert</code>
</div>
</div>
<div class="ui divider"></div>
<h2>Forward Proxy</h2>
<p>Setup a basic HTTP forward proxy to access web server in another LAN<br>
To enable forward proxy in your domain, add a proxy rule to 127.0.0.1:{selected_port}</p>
<form class="ui form">
<div class="field">
<label>Listening Port</label>
<div class="ui action input">
<input id="forwardProxyPort" type="number" placeholder="5587" step="1", min="1024" max="65535" value="5587">
<button onclick="updateForwardProxyPort(); event.preventDefault();" class="ui basic button"><i class="ui green check icon"></i> Apply</button>
</div>
</div>
<div id="forwardProxyButtons" class="field">
<button onclick="toggleForwadProxy(true); event.preventDefault();" class="ui basic small green button startBtn"><i class="ui green arrow alternate circle up icon"></i> Start</button>
<button onclick="toggleForwadProxy(false); event.preventDefault();" class="ui basic small red button stopBtn"><i class="ui red minus circle icon"></i> Stop</button>
</div>
</form>
<div class="ui divider"></div>
<h2>Wake On LAN</h2>
<p>Wake up a remote server by WOL Magic Packet or an IoT device</p>
@ -558,6 +574,68 @@ function renderWhoisDomainTable(jsonData) {
}
//Forward Proxy
function initForwardProxyInfo(){
$.get("/api/tools/fwdproxy/enable", function(data){
if (data == true){
//Disable the start btn
$("#forwardProxyButtons").find(".startBtn").addClass('disabled');
$("#forwardProxyButtons").find(".stopBtn").removeClass('disabled');
}else{
$("#forwardProxyButtons").find(".startBtn").removeClass('disabled');
$("#forwardProxyButtons").find(".stopBtn").addClass('disabled');
}
});
$.get("/api/tools/fwdproxy/port", function(data){
$("#forwardProxyPort").val(data);
})
}
initForwardProxyInfo();
function toggleForwadProxy(enabled){
$.ajax({
url: "/api/tools/fwdproxy/enable",
method: "POST",
data: {
"enable": enabled
},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false);
}else{
msgbox(`Forward proxy ${enabled?"enabled":"disabled"}`)
}
initForwardProxyInfo();
}
})
}
function updateForwardProxyPort(){
let newPortNumber = $("#forwardProxyPort").val();
if (newPortNumber < 1024 || newPortNumber > 65535){
$("#newPortNumber").parent().addClass('error');
}else{
$("#newPortNumber").parent().removeClass('error');
}
$.ajax({
url: "/api/tools/fwdproxy/port",
method: "POST",
data: {
"port": newPortNumber
},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false);
}
msgbox("Forward proxy port updated");
initForwardProxyInfo();
}
});
}
</script>

View File

@ -4,12 +4,12 @@
<p>You might find these tools or information helpful when setting up your gateway server</p>
</div>
<div class="ui top attached tabular menu">
<a class="nettools item active" data-tab="tab1"><i class="ui user circle blue icon"></i> Accounts</a>
<a class="nettools item" data-tab="tab2">Toolbox</a>
<a class="nettools item" data-tab="tab3">System</a>
<a class="utils item active" data-tab="utiltab1"><i class="ui user circle blue icon"></i> Accounts</a>
<a class="utils item" data-tab="utiltab2">Toolbox</a>
<a class="utils item" data-tab="utiltab3">System</a>
</div>
<div class="ui bottom attached tab segment nettoolstab active" data-tab="tab1">
<div class="ui bottom attached tab segment utilitiesTabs active" data-tab="utiltab1">
<div class="extAuthOnly" style="display:none;">
<div class="ui basic segment">
<i class="ui green circle check icon"></i> Account options are not available due to -noauth flag is set to true.
@ -99,7 +99,7 @@
</form>
</div>
</div>
<div class="ui bottom attached tab segment nettoolstab" data-tab="tab2">
<div class="ui bottom attached tab segment utilitiesTabs" data-tab="utiltab2">
<h3> IP Address to CIDR</h3>
<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
<div class="ui basic segment">
@ -128,7 +128,7 @@
</div>
<div class="ui divider"></div>
</div>
<div class="ui bottom attached tab segment nettoolstab" data-tab="tab3">
<div class="ui bottom attached tab segment utilitiesTabs" data-tab="utiltab3">
<!-- Config Tools -->
<h3>System Backup & Restore</h3>
<p>Options related to system backup, migrate and restore.</p>
@ -175,7 +175,16 @@
<br>
</div>
<script>
$('.menu .nettools.item').tab();
$('.menu .utils.item').tab();
// Switch tabs when clicking on the menu items
$('.menu .utils.item').on('click', function() {
$('.menu .utils.item').removeClass('active');
$(this).addClass('active');
var tab = $(this).attr('data-tab');
$('.utilitiesTabs.tab.segment').removeClass('active');
$('div[data-tab="' + tab + '"]').addClass('active');
});
/*
Account Password utilities
*/