mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
commit
0eb0696670
@ -33,6 +33,7 @@ A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
|
||||
- Basic single-admin management mode
|
||||
- External permission management system for easy system integration
|
||||
- SMTP config for password reset
|
||||
- Dark Theme Mode
|
||||
|
||||
## Downloads
|
||||
|
||||
@ -102,6 +103,8 @@ Usage of zoraxy:
|
||||
Enable auto config upgrade if breaking change is detected (default true)
|
||||
-docker
|
||||
Run Zoraxy in docker compatibility mode
|
||||
-earlyrenew int
|
||||
Number of days to early renew a soon expiring certificate (days) (default 30)
|
||||
-fastgeoip
|
||||
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
|
||||
-mdns
|
||||
@ -119,7 +122,7 @@ Usage of zoraxy:
|
||||
-webfm
|
||||
Enable web file manager for static web server root folder (default true)
|
||||
-webroot string
|
||||
Static web server root folder. Only allow change in start parameters (default "./www")
|
||||
Static web server root folder. Only allow chnage in start paramters (default "./www")
|
||||
-ztauth string
|
||||
ZeroTier authtoken for the local node
|
||||
-ztport int
|
||||
|
@ -230,7 +230,17 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||
//Check if the country code contains comma, if yes, split it
|
||||
if strings.Contains(countryCode, ",") {
|
||||
codes := strings.Split(countryCode, ",")
|
||||
for _, code := range codes {
|
||||
code = strings.TrimSpace(code)
|
||||
rule.AddCountryCodeToBlackList(code, comment)
|
||||
}
|
||||
} else {
|
||||
countryCode = strings.TrimSpace(countryCode)
|
||||
rule.AddCountryCodeToBlackList(countryCode, comment)
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -254,7 +264,17 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||
//Check if the country code contains comma, if yes, split it
|
||||
if strings.Contains(countryCode, ",") {
|
||||
codes := strings.Split(countryCode, ",")
|
||||
for _, code := range codes {
|
||||
code = strings.TrimSpace(code)
|
||||
rule.RemoveCountryCodeFromBlackList(code)
|
||||
}
|
||||
} else {
|
||||
countryCode = strings.TrimSpace(countryCode)
|
||||
rule.RemoveCountryCodeFromBlackList(countryCode)
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -397,7 +417,17 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
p := bluemonday.StrictPolicy()
|
||||
comment = p.Sanitize(comment)
|
||||
|
||||
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||
//Check if the country code contains comma, if yes, split it
|
||||
if strings.Contains(countryCode, ",") {
|
||||
codes := strings.Split(countryCode, ",")
|
||||
for _, code := range codes {
|
||||
code = strings.TrimSpace(code)
|
||||
rule.AddCountryCodeToWhitelist(code, comment)
|
||||
}
|
||||
} else {
|
||||
countryCode = strings.TrimSpace(countryCode)
|
||||
rule.AddCountryCodeToWhitelist(countryCode, comment)
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
@ -420,7 +450,17 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||
//Check if the country code contains comma, if yes, split it
|
||||
if strings.Contains(countryCode, ",") {
|
||||
codes := strings.Split(countryCode, ",")
|
||||
for _, code := range codes {
|
||||
code = strings.TrimSpace(code)
|
||||
rule.RemoveCountryCodeFromWhitelist(code)
|
||||
}
|
||||
} else {
|
||||
countryCode = strings.TrimSpace(countryCode)
|
||||
rule.RemoveCountryCodeFromWhitelist(countryCode)
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
20
src/acme.go
20
src/acme.go
@ -41,6 +41,20 @@ func initACME() *acme.ACMEHandler {
|
||||
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb, SystemWideLogger)
|
||||
}
|
||||
|
||||
// Restart ACME handler and auto renewer
|
||||
func restartACMEHandler() {
|
||||
SystemWideLogger.Println("Restarting ACME handler")
|
||||
//Clos the current handler and auto renewer
|
||||
acmeHandler.Close()
|
||||
acmeAutoRenewer.Close()
|
||||
acmeDeregisterSpecialRoutingRule()
|
||||
|
||||
//Reinit the handler with a new random port
|
||||
acmeHandler = initACME()
|
||||
|
||||
acmeRegisterSpecialRoutingRule()
|
||||
}
|
||||
|
||||
// create the special routing rule for ACME
|
||||
func acmeRegisterSpecialRoutingRule() {
|
||||
SystemWideLogger.Println("Assigned temporary port:" + acmeHandler.Getport())
|
||||
@ -82,6 +96,12 @@ func acmeRegisterSpecialRoutingRule() {
|
||||
}
|
||||
}
|
||||
|
||||
// remove the special routing rule for ACME
|
||||
func acmeDeregisterSpecialRoutingRule() {
|
||||
SystemWideLogger.Println("Removing ACME routing rule")
|
||||
dynamicProxyRouter.RemoveRoutingRule("acme-autorenew")
|
||||
}
|
||||
|
||||
// This function check if the renew setup is satisfied. If not, toggle them automatically
|
||||
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
|
||||
isForceHttpsRedirectEnabledOriginally := false
|
||||
|
264
src/api.go
264
src/api.go
@ -8,6 +8,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/acme/acmedns"
|
||||
"imuslab.com/zoraxy/mod/acme/acmewizard"
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
|
||||
"imuslab.com/zoraxy/mod/ipscan"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
@ -18,34 +19,11 @@ import (
|
||||
API.go
|
||||
|
||||
This file contains all the API called by the web management interface
|
||||
|
||||
*/
|
||||
|
||||
var requireAuth = true
|
||||
|
||||
func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
|
||||
AuthAgent: authAgent,
|
||||
RequireAuth: requireAuth,
|
||||
TargetMux: targetMux,
|
||||
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
||||
},
|
||||
})
|
||||
|
||||
//Register the standard web services urls
|
||||
fs := http.FileServer(http.FS(webres))
|
||||
if development {
|
||||
fs = http.FileServer(http.Dir("web/"))
|
||||
}
|
||||
//Add a layer of middleware for advance control
|
||||
advHandler := FSHandler(fs)
|
||||
targetMux.Handle("/", advHandler)
|
||||
|
||||
//Authentication APIs
|
||||
registerAuthAPIs(requireAuth, targetMux)
|
||||
|
||||
//Reverse proxy
|
||||
// Register the APIs for HTTP proxy management functions
|
||||
func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
|
||||
/* Reverse Proxy Settings & Status */
|
||||
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
|
||||
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||
@ -56,24 +34,24 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
||||
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS)
|
||||
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
|
||||
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||
//Reverse proxy upstream (load balance) APIs
|
||||
/* Reverse proxy upstream (load balance) */
|
||||
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
||||
//Reverse proxy virtual directory APIs
|
||||
/* Reverse proxy virtual directory */
|
||||
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
|
||||
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir)
|
||||
//Reverse proxy user define header apis
|
||||
/* Reverse proxy user-defined header */
|
||||
authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList)
|
||||
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
|
||||
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove)
|
||||
@ -81,12 +59,14 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop)
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite)
|
||||
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy)
|
||||
//Reverse proxy auth related APIs
|
||||
/* Reverse proxy auth related */
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths)
|
||||
}
|
||||
|
||||
//TLS / SSL config
|
||||
// Register the APIs for TLS / SSL certificate management functions
|
||||
func RegisterTLSAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
|
||||
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
|
||||
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
|
||||
@ -95,8 +75,10 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
||||
}
|
||||
|
||||
//SSO and Oauth
|
||||
// Register the APIs for SSO and Oauth functions, WIP
|
||||
func RegisterSSOAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/sso/status", ssoHandler.HandleSSOStatus)
|
||||
authRouter.HandleFunc("/api/sso/enable", ssoHandler.HandleSSOEnable)
|
||||
authRouter.HandleFunc("/api/sso/setPort", ssoHandler.HandlePortChange)
|
||||
@ -110,48 +92,67 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter.HandleFunc("/api/sso/user/add", ssoHandler.HandleAddUser)
|
||||
authRouter.HandleFunc("/api/sso/user/edit", ssoHandler.HandleEditUser)
|
||||
authRouter.HandleFunc("/api/sso/user/remove", ssoHandler.HandleRemoveUser)
|
||||
}
|
||||
|
||||
//Redirection config
|
||||
// Register the APIs for redirection rules management functions
|
||||
func RegisterRedirectionAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
||||
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||
}
|
||||
|
||||
//Access Rules API
|
||||
// Register the APIs for access rules management functions
|
||||
func RegisterAccessRuleAPIs(authRouter *auth.RouterDef) {
|
||||
/* Access Rules Settings & Status */
|
||||
authRouter.HandleFunc("/api/access/list", handleListAccessRules)
|
||||
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
|
||||
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
|
||||
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
|
||||
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
|
||||
//Blacklist APIs
|
||||
/* Blacklist */
|
||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
|
||||
authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove)
|
||||
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
|
||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||
//Whitelist APIs
|
||||
/* Whitelist */
|
||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||
authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove)
|
||||
authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd)
|
||||
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
|
||||
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
|
||||
}
|
||||
|
||||
//Path Blocker APIs
|
||||
// Register the APIs for path blocking rules management functions, WIP
|
||||
func RegisterPathRuleAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath)
|
||||
authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath)
|
||||
authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath)
|
||||
}
|
||||
|
||||
//Statistic & uptime monitoring API
|
||||
// Register the APIs statistic anlysis and uptime monitoring functions
|
||||
func RegisterStatisticalAPIs(authRouter *auth.RouterDef) {
|
||||
/* Traffic Summary */
|
||||
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
||||
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
||||
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
|
||||
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
|
||||
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces)
|
||||
/* Zoraxy Analytic */
|
||||
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
|
||||
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
|
||||
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
|
||||
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
|
||||
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
|
||||
/* UpTime Monitor */
|
||||
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
||||
}
|
||||
|
||||
//Global Area Network APIs
|
||||
// Register the APIs for Global Area Network management functions, Will be moving to plugin soon
|
||||
func RegisterGANAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/gan/network/info", ganManager.HandleGetNodeID)
|
||||
authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
|
||||
authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork)
|
||||
@ -166,8 +167,10 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
||||
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||
}
|
||||
|
||||
//Stream (TCP / UDP) Proxy
|
||||
// Register the APIs for Stream (TCP / UDP) Proxy management functions
|
||||
func RegisterStreamProxyAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
|
||||
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs)
|
||||
@ -175,19 +178,57 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
|
||||
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus)
|
||||
}
|
||||
|
||||
//mDNS APIs
|
||||
// Register the APIs for mDNS service management functions
|
||||
func RegisterMDNSAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
|
||||
authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning)
|
||||
}
|
||||
|
||||
//Zoraxy Analytic
|
||||
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList)
|
||||
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary)
|
||||
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary)
|
||||
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport)
|
||||
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset)
|
||||
// Register the APIs for ACME and Auto Renewer management functions
|
||||
func RegisterACMEAndAutoRenewerAPIs(authRouter *auth.RouterDef) {
|
||||
/* ACME Core */
|
||||
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
|
||||
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
|
||||
/* Auto Renewer */
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HandleSetDNS)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
||||
/* ACME Wizard */
|
||||
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck)
|
||||
}
|
||||
|
||||
//Network utilities
|
||||
// Register the APIs for Static Web Server management functions
|
||||
func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
|
||||
/* Static Web Server Controls */
|
||||
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
|
||||
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
|
||||
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
||||
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
||||
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||
/* File Manager */
|
||||
if *allowWebFileManager {
|
||||
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
||||
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
|
||||
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
|
||||
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
|
||||
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
|
||||
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
|
||||
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
|
||||
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
|
||||
}
|
||||
}
|
||||
|
||||
// Register the APIs for Network Utilities functions
|
||||
func RegisterNetworkUtilsAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/tools/ipscan", ipscan.HandleIpScan)
|
||||
authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort)
|
||||
authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute)
|
||||
@ -202,66 +243,10 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
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
|
||||
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
||||
|
||||
//ACME & Auto Renewer
|
||||
authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
|
||||
authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HanldeSetDNS)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
|
||||
authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)
|
||||
authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson)
|
||||
authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard
|
||||
|
||||
//Static Web Server
|
||||
authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus)
|
||||
authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer)
|
||||
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
|
||||
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
|
||||
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
|
||||
if *allowWebFileManager {
|
||||
//Web Directory Manager file operation functions
|
||||
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
|
||||
authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload)
|
||||
authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload)
|
||||
authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder)
|
||||
authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy)
|
||||
authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove)
|
||||
authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties)
|
||||
authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete)
|
||||
}
|
||||
|
||||
//Docker UX Optimizations
|
||||
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
|
||||
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
|
||||
|
||||
//Others
|
||||
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
||||
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
||||
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
|
||||
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
|
||||
|
||||
//Debug
|
||||
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||
|
||||
//If you got APIs to add, append them here
|
||||
|
||||
}
|
||||
|
||||
// Function to renders Auth related APIs
|
||||
func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||
//Auth APIs
|
||||
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
|
||||
func RegisterAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
||||
targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
|
||||
targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -277,21 +262,17 @@ func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(username)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
})
|
||||
targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
|
||||
uc := authAgent.GetUserCounts()
|
||||
js, _ := json.Marshal(uc)
|
||||
js, _ := json.Marshal(authAgent.GetUserCounts())
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
})
|
||||
targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
|
||||
if authAgent.GetUserCounts() == 0 {
|
||||
//Allow register root admin
|
||||
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
|
||||
|
||||
})
|
||||
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {})
|
||||
} else {
|
||||
//This function is disabled
|
||||
utils.SendErrorResponse(w, "Root management account already exists")
|
||||
@ -332,5 +313,60 @@ func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||
authAgent.UnregisterUser(username)
|
||||
authAgent.CreateUserAccount(username, newPassword, "")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/* Register all the APIs */
|
||||
func initAPIs(targetMux *http.ServeMux) {
|
||||
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
|
||||
AuthAgent: authAgent,
|
||||
RequireAuth: requireAuth,
|
||||
TargetMux: targetMux,
|
||||
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
|
||||
},
|
||||
})
|
||||
|
||||
//Register the standard web services urls
|
||||
fs := http.FileServer(http.FS(webres))
|
||||
if DEVELOPMENT_BUILD {
|
||||
fs = http.FileServer(http.Dir("web/"))
|
||||
}
|
||||
//Add a layer of middleware for advance control
|
||||
advHandler := FSHandler(fs)
|
||||
targetMux.Handle("/", advHandler)
|
||||
|
||||
//Register the APIs
|
||||
RegisterAuthAPIs(requireAuth, targetMux)
|
||||
RegisterHTTPProxyAPIs(authRouter)
|
||||
RegisterTLSAPIs(authRouter)
|
||||
//RegisterSSOAPIs(authRouter)
|
||||
RegisterRedirectionAPIs(authRouter)
|
||||
RegisterAccessRuleAPIs(authRouter)
|
||||
RegisterPathRuleAPIs(authRouter)
|
||||
RegisterStatisticalAPIs(authRouter)
|
||||
RegisterGANAPIs(authRouter)
|
||||
RegisterStreamProxyAPIs(authRouter)
|
||||
RegisterMDNSAPIs(authRouter)
|
||||
RegisterNetworkUtilsAPIs(authRouter)
|
||||
RegisterACMEAndAutoRenewerAPIs(authRouter)
|
||||
RegisterStaticWebServerAPIs(authRouter)
|
||||
|
||||
//Account Reset
|
||||
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||
targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
||||
|
||||
//Docker UX Optimizations
|
||||
authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable)
|
||||
authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList)
|
||||
|
||||
//Others
|
||||
targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||
authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup)
|
||||
authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip)
|
||||
authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip)
|
||||
authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog)
|
||||
authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog)
|
||||
|
||||
//Debug
|
||||
authRouter.HandleFunc("/api/info/pprof", pprof.Index)
|
||||
}
|
||||
|
@ -177,7 +177,10 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Handle front-end toggling TLS mode
|
||||
func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
|
||||
currentTlsSetting := false
|
||||
currentTlsSetting := true //Default to true
|
||||
if dynamicProxyRouter.Option != nil {
|
||||
currentTlsSetting = dynamicProxyRouter.Option.UseTls
|
||||
}
|
||||
if sysdb.KeyExists("settings", "usetls") {
|
||||
sysdb.Read("settings", "usetls", ¤tTlsSetting)
|
||||
}
|
||||
|
138
src/def.go
Normal file
138
src/def.go
Normal file
@ -0,0 +1,138 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
Type and flag definations
|
||||
|
||||
This file contains all the type and flag definations
|
||||
Author: tobychui
|
||||
*/
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/acme"
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/auth/sso"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/dockerux"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"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"
|
||||
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||
"imuslab.com/zoraxy/mod/mdns"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
"imuslab.com/zoraxy/mod/pathrule"
|
||||
"imuslab.com/zoraxy/mod/sshprox"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
"imuslab.com/zoraxy/mod/webserv"
|
||||
)
|
||||
|
||||
const (
|
||||
/* Build Constants */
|
||||
SYSTEM_NAME = "Zoraxy"
|
||||
SYSTEM_VERSION = "3.1.4"
|
||||
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
|
||||
|
||||
/* System Constants */
|
||||
DATABASE_PATH = "sys.db"
|
||||
TMP_FOLDER = "./tmp"
|
||||
WEBSERV_DEFAULT_PORT = 5487
|
||||
MDNS_HOSTNAME_PREFIX = "zoraxy_" /* Follow by node UUID */
|
||||
MDNS_IDENTIFY_DEVICE_TYPE = "Network Gateway"
|
||||
MDNS_IDENTIFY_DOMAIN = "zoraxy.aroz.org"
|
||||
MDNS_IDENTIFY_VENDOR = "imuslab.com"
|
||||
MDNS_SCAN_TIMEOUT = 30 /* Seconds */
|
||||
MDNS_SCAN_UPDATE_INTERVAL = 15 /* Minutes */
|
||||
GEODB_CACHE_CLEAR_INTERVAL = 15 /* Minutes */
|
||||
ACME_AUTORENEW_CONFIG_PATH = "./conf/acme_conf.json"
|
||||
CSRF_COOKIENAME = "zoraxy_csrf"
|
||||
LOG_PREFIX = "zr"
|
||||
LOG_FOLDER = "./log"
|
||||
LOG_EXTENSION = ".log"
|
||||
|
||||
/* Configuration Folder Storage Path Constants */
|
||||
CONF_HTTP_PROXY = "./conf/proxy"
|
||||
CONF_STREAM_PROXY = "./conf/streamproxy"
|
||||
CONF_CERT_STORE = "./conf/certs"
|
||||
CONF_REDIRECTION = "./conf/redirect"
|
||||
CONF_ACCESS_RULE = "./conf/access"
|
||||
CONF_PATH_RULE = "./conf/rules/pathrules"
|
||||
)
|
||||
|
||||
/* System Startup Flags */
|
||||
var (
|
||||
webUIPort = flag.String("port", ":8000", "Management web interface listening port")
|
||||
noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
||||
showver = flag.Bool("version", false, "Show version of this server")
|
||||
allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||
allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||
mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||
ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||
ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
|
||||
acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||
acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
|
||||
enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
||||
staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||
allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||
enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||
)
|
||||
|
||||
/* Global Variables and Handlers */
|
||||
var (
|
||||
nodeUUID = "generic" //System uuid in uuidv4 format, load from database on startup
|
||||
bootTime = time.Now().Unix()
|
||||
requireAuth = true //Require authentication for webmin panel, override from flag
|
||||
|
||||
/*
|
||||
Binary Embedding File System
|
||||
*/
|
||||
//go:embed web/*
|
||||
webres embed.FS
|
||||
|
||||
/*
|
||||
Handler Modules
|
||||
*/
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
webminPanelMux *http.ServeMux //Server mux for handling webmin panel APIs
|
||||
csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
|
||||
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||
accessController *access.Controller //Access controller, handle black list and white list
|
||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||
webSshManager *sshprox.Manager //Web SSH connection service
|
||||
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
|
||||
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
|
||||
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||
ssoHandler *sso.SSOHandler //Single Sign On handler
|
||||
|
||||
//Helper modules
|
||||
EmailSender *email.Sender //Email sender that handle email sending
|
||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||
LogViewer *logviewer.Viewer //Log viewer HTTP handlers
|
||||
)
|
134
src/main.go
134
src/main.go
@ -1,7 +1,36 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
______
|
||||
|___ /
|
||||
/ / ___ _ __ __ ___ ___ _
|
||||
/ / / _ \| '__/ _` \ \/ / | | |
|
||||
/ /_| (_) | | | (_| |> <| |_| |
|
||||
/_____\___/|_| \__,_/_/\_\\__, |
|
||||
__/ |
|
||||
|___/
|
||||
|
||||
Zoraxy - A general purpose HTTP reverse proxy and forwarding tool
|
||||
Author: tobychui
|
||||
License: AGPLv3
|
||||
|
||||
--------------------------------------------
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, version 3 of the License or any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@ -13,100 +42,12 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/csrf"
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/acme"
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/auth/sso"
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/dockerux"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"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"
|
||||
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||
"imuslab.com/zoraxy/mod/mdns"
|
||||
"imuslab.com/zoraxy/mod/netstat"
|
||||
"imuslab.com/zoraxy/mod/pathrule"
|
||||
"imuslab.com/zoraxy/mod/sshprox"
|
||||
"imuslab.com/zoraxy/mod/statistic"
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
"imuslab.com/zoraxy/mod/update"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
"imuslab.com/zoraxy/mod/webserv"
|
||||
)
|
||||
|
||||
// General flags
|
||||
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
|
||||
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
|
||||
var showver = flag.Bool("version", false, "Show version of this server")
|
||||
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||
var mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||
var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
var runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
|
||||
var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||
var acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
|
||||
var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
||||
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||
var enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||
|
||||
var (
|
||||
name = "Zoraxy"
|
||||
version = "3.1.3"
|
||||
nodeUUID = "generic" //System uuid, in uuidv4 format
|
||||
development = false //Set this to false to use embedded web fs
|
||||
bootTime = time.Now().Unix()
|
||||
|
||||
/*
|
||||
Binary Embedding File System
|
||||
*/
|
||||
//go:embed web/*
|
||||
webres embed.FS
|
||||
|
||||
/*
|
||||
Handler Modules
|
||||
*/
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
webminPanelMux *http.ServeMux //Server mux for handling webmin panel APIs
|
||||
csrfMiddleware func(http.Handler) http.Handler //CSRF protection middleware
|
||||
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||
accessController *access.Controller //Access controller, handle black list and white list
|
||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||
mdnsScanner *mdns.MDNSHost //mDNS discovery services
|
||||
ganManager *ganserv.NetworkManager //Global Area Network Manager
|
||||
webSshManager *sshprox.Manager //Web SSH connection service
|
||||
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
|
||||
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
|
||||
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||
ssoHandler *sso.SSOHandler //Single Sign On handler
|
||||
|
||||
//Helper modules
|
||||
EmailSender *email.Sender //Email sender that handle email sending
|
||||
AnalyticLoader *analytic.DataLoader //Data loader for Zoraxy Analytic
|
||||
DockerUXOptimizer *dockerux.UXOptimizer //Docker user experience optimizer, community contribution only
|
||||
SystemWideLogger *logger.Logger //Logger for Zoraxy
|
||||
LogViewer *logviewer.Viewer
|
||||
)
|
||||
|
||||
// Kill signal handler. Do something before the system the core terminate.
|
||||
/* SIGTERM handler, do shutdown sequences before closing */
|
||||
func SetupCloseHandler() {
|
||||
c := make(chan os.Signal, 2)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
@ -118,9 +59,7 @@ func SetupCloseHandler() {
|
||||
}
|
||||
|
||||
func ShutdownSeq() {
|
||||
SystemWideLogger.Println("Shutting down " + name)
|
||||
//SystemWideLogger.Println("Closing GeoDB")
|
||||
//geodbStore.Close()
|
||||
SystemWideLogger.Println("Shutting down " + SYSTEM_NAME)
|
||||
SystemWideLogger.Println("Closing Netstats Listener")
|
||||
netstatBuffers.Close()
|
||||
SystemWideLogger.Println("Closing Statistic Collector")
|
||||
@ -152,7 +91,7 @@ func main() {
|
||||
//Parse startup flags
|
||||
flag.Parse()
|
||||
if *showver {
|
||||
fmt.Println(name + " - Version " + version)
|
||||
fmt.Println(SYSTEM_NAME + " - Version " + SYSTEM_VERSION)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@ -163,7 +102,7 @@ func main() {
|
||||
|
||||
if *enableAutoUpdate {
|
||||
fmt.Println("Checking required config update")
|
||||
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version))
|
||||
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(SYSTEM_VERSION))
|
||||
}
|
||||
|
||||
SetupCloseHandler()
|
||||
@ -185,7 +124,7 @@ func main() {
|
||||
webminPanelMux = http.NewServeMux()
|
||||
csrfMiddleware = csrf.Protect(
|
||||
[]byte(nodeUUID),
|
||||
csrf.CookieName("zoraxy-csrf"),
|
||||
csrf.CookieName(CSRF_COOKIENAME),
|
||||
csrf.Secure(false),
|
||||
csrf.Path("/"),
|
||||
csrf.SameSite(csrf.SameSiteLaxMode),
|
||||
@ -208,11 +147,10 @@ func main() {
|
||||
//Start the finalize sequences
|
||||
finalSequence()
|
||||
|
||||
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
|
||||
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
|
||||
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -86,6 +86,13 @@ func (a *ACMEHandler) Logf(message string, err error) {
|
||||
a.Logger.PrintAndLog("ACME", message, err)
|
||||
}
|
||||
|
||||
// Close closes the ACMEHandler.
|
||||
// ACME Handler does not need to close anything
|
||||
// Function defined for future compatibility
|
||||
func (a *ACMEHandler) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObtainCert obtains a certificate for the specified domains.
|
||||
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool, propagationTimeout int) (bool, error) {
|
||||
a.Logf("Obtaining certificate for: "+strings.Join(domains, ", "), nil)
|
||||
|
@ -308,7 +308,6 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
||||
}
|
||||
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
|
||||
//This cert is expired
|
||||
|
||||
DNSName, err := ExtractDomains(certBytes)
|
||||
if err != nil {
|
||||
//Maybe self signed. Ignore this
|
||||
@ -355,6 +354,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
|
||||
return a.renewExpiredDomains(expiredCertList)
|
||||
}
|
||||
|
||||
// Close the auto renewer
|
||||
func (a *AutoRenewer) Close() {
|
||||
if a.TickerstopChan != nil {
|
||||
a.TickerstopChan <- true
|
||||
@ -440,7 +440,7 @@ func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Handle update auto renew DNS configuration
|
||||
func (a *AutoRenewer) HanldeSetDNS(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *AutoRenewer) HandleSetDNS(w http.ResponseWriter, r *http.Request) {
|
||||
dnsProvider, err := utils.PostPara(r, "dnsProvider")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "dnsProvider not set")
|
||||
|
@ -3,7 +3,7 @@ package acme
|
||||
/*
|
||||
CA.go
|
||||
|
||||
This script load CA defination from embedded ca.json
|
||||
This script load CA definition from embedded ca.json
|
||||
*/
|
||||
import (
|
||||
_ "embed"
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CA Defination, load from embeded json when startup
|
||||
// CA definition, load from embeded json when startup
|
||||
type CaDef struct {
|
||||
Production map[string]string
|
||||
Test map[string]string
|
||||
|
@ -210,8 +210,8 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
session.Values["authenticated"] = false
|
||||
session.Values["username"] = nil
|
||||
session.Save(r, w)
|
||||
return nil
|
||||
session.Options.MaxAge = -1
|
||||
return session.Save(r, w)
|
||||
}
|
||||
|
||||
// Get the current session username from request
|
||||
@ -339,6 +339,7 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if user is authenticated
|
||||
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
|
||||
return false
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -16,7 +15,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
|
||||
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
|
||||
if err != nil {
|
||||
//Unable to load access rule. Target rule not found?
|
||||
log.Println("[Proxy] Unable to load access rule: " + ruleID)
|
||||
h.Parent.Option.Logger.PrintAndLog("proxy-access", "Unable to load access rule: "+ruleID, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("500 - Internal Server Error"))
|
||||
return true
|
||||
|
@ -9,8 +9,15 @@ package domainsniff
|
||||
|
||||
*/
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
// Check if the domain is reachable and return err if not reachable
|
||||
@ -25,7 +32,115 @@ func DomainReachableWithError(domain string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if a domain have TLS but it is self-signed or expired
|
||||
// Return false if sniff error
|
||||
func DomainIsSelfSigned(domain string) bool {
|
||||
//Extract the domain from URl in case the user input the full URL
|
||||
host, port, err := net.SplitHostPort(domain)
|
||||
if err != nil {
|
||||
host = domain
|
||||
} else {
|
||||
domain = host + ":" + port
|
||||
}
|
||||
if !strings.Contains(domain, ":") {
|
||||
domain = domain + ":443"
|
||||
}
|
||||
|
||||
//Get the certificate
|
||||
conn, err := net.Dial("tcp", domain)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
//Connect with TLS using secure verify
|
||||
tlsConn := tls.Client(conn, nil)
|
||||
err = tlsConn.Handshake()
|
||||
if err == nil {
|
||||
//This is a valid certificate
|
||||
fmt.Println()
|
||||
return false
|
||||
}
|
||||
|
||||
//Connect with TLS using insecure skip verify
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
tlsConn = tls.Client(conn, config)
|
||||
err = tlsConn.Handshake()
|
||||
//If the handshake is successful, this is a self-signed certificate
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Check if domain reachable
|
||||
func DomainReachable(domain string) bool {
|
||||
return DomainReachableWithError(domain) == nil
|
||||
}
|
||||
|
||||
// Check if domain is served by a web server using HTTPS
|
||||
func DomainUsesTLS(targetURL string) bool {
|
||||
//Check if the site support https
|
||||
httpsUrl := fmt.Sprintf("https://%s", targetURL)
|
||||
httpUrl := fmt.Sprintf("http://%s", targetURL)
|
||||
|
||||
client := http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
resp, err := client.Head(httpsUrl)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
return true
|
||||
}
|
||||
|
||||
resp, err = client.Head(httpUrl)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
return false
|
||||
}
|
||||
|
||||
//If the site is not reachable, return false
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
Request Handlers
|
||||
*/
|
||||
//Check if site support TLS
|
||||
//Pass in ?selfsignchk=true to also check for self-signed certificate
|
||||
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
|
||||
targetURL, err := utils.PostPara(r, "url")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid url given")
|
||||
return
|
||||
}
|
||||
|
||||
//If the selfsign flag is set, also chec for self-signed certificate
|
||||
_, err = utils.PostBool(r, "selfsignchk")
|
||||
if err == nil {
|
||||
//Return the https and selfsign status
|
||||
type result struct {
|
||||
Protocol string `json:"protocol"`
|
||||
SelfSign bool `json:"selfsign"`
|
||||
}
|
||||
|
||||
scanResult := result{Protocol: "http", SelfSign: false}
|
||||
|
||||
if DomainUsesTLS(targetURL) {
|
||||
scanResult.Protocol = "https"
|
||||
if DomainIsSelfSigned(targetURL) {
|
||||
scanResult.SelfSign = true
|
||||
}
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(scanResult)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
}
|
||||
|
||||
if DomainUsesTLS(targetURL) {
|
||||
js, _ := json.Marshal("https")
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
} else {
|
||||
js, _ := json.Marshal("http")
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,8 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
|
||||
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
|
||||
thisTransporter.(*http.Transport).DisableCompression = true
|
||||
|
||||
//TODO: Add user adjustable timeout option here
|
||||
|
||||
if dpcOptions.IgnoreTLSVerification {
|
||||
//Ignore TLS certificate validation error
|
||||
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
|
||||
|
@ -291,7 +291,7 @@ func (router *Router) Restart() error {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
// Start the server
|
||||
err = router.StartProxyService()
|
||||
if err != nil {
|
||||
|
@ -3,6 +3,7 @@ package geodb
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
@ -15,17 +16,22 @@ var geoipv4 []byte //Geodb dataset for ipv4
|
||||
var geoipv6 []byte //Geodb dataset for ipv6
|
||||
|
||||
type Store struct {
|
||||
geodb [][]string //Parsed geodb list
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
sysdb *database.Database
|
||||
option *StoreOptions
|
||||
geodb [][]string //Parsed geodb list
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
sysdb *database.Database
|
||||
slowLookupCacheIpv4 map[string]string //Cache for slow lookup
|
||||
slowLookupCacheIpv6 map[string]string //Cache for slow lookup
|
||||
cacheClearTicker *time.Ticker //Ticker for clearing cache
|
||||
cacheClearTickerStopChan chan bool //Stop channel for cache clear ticker
|
||||
option *StoreOptions
|
||||
}
|
||||
|
||||
type StoreOptions struct {
|
||||
AllowSlowIpv4LookUp bool
|
||||
AllowSloeIpv6Lookup bool
|
||||
AllowSlowIpv4LookUp bool
|
||||
AllowSlowIpv6Lookup bool
|
||||
SlowLookupCacheClearInterval time.Duration //Clear slow lookup cache interval
|
||||
}
|
||||
|
||||
type CountryInfo struct {
|
||||
@ -50,18 +56,44 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
|
||||
}
|
||||
|
||||
var ipv6Trie *trie
|
||||
if !option.AllowSloeIpv6Lookup {
|
||||
if !option.AllowSlowIpv6Lookup {
|
||||
ipv6Trie = constrctTrieTree(parsedGeoDataIpv6)
|
||||
}
|
||||
|
||||
return &Store{
|
||||
geodb: parsedGeoData,
|
||||
geotrie: ipv4Trie,
|
||||
geodbIpv6: parsedGeoDataIpv6,
|
||||
geotrieIpv6: ipv6Trie,
|
||||
sysdb: sysdb,
|
||||
option: option,
|
||||
}, nil
|
||||
if option.SlowLookupCacheClearInterval == 0 {
|
||||
option.SlowLookupCacheClearInterval = 15 * time.Minute
|
||||
}
|
||||
|
||||
//Create a new store
|
||||
thisGeoDBStore := &Store{
|
||||
geodb: parsedGeoData,
|
||||
geotrie: ipv4Trie,
|
||||
geodbIpv6: parsedGeoDataIpv6,
|
||||
geotrieIpv6: ipv6Trie,
|
||||
sysdb: sysdb,
|
||||
slowLookupCacheIpv4: make(map[string]string),
|
||||
slowLookupCacheIpv6: make(map[string]string),
|
||||
cacheClearTicker: time.NewTicker(option.SlowLookupCacheClearInterval),
|
||||
cacheClearTickerStopChan: make(chan bool),
|
||||
option: option,
|
||||
}
|
||||
|
||||
//Start cache clear ticker
|
||||
if option.AllowSlowIpv4LookUp || option.AllowSlowIpv6Lookup {
|
||||
go func(store *Store) {
|
||||
for {
|
||||
select {
|
||||
case <-store.cacheClearTickerStopChan:
|
||||
return
|
||||
case <-thisGeoDBStore.cacheClearTicker.C:
|
||||
thisGeoDBStore.slowLookupCacheIpv4 = make(map[string]string)
|
||||
thisGeoDBStore.slowLookupCacheIpv6 = make(map[string]string)
|
||||
}
|
||||
}
|
||||
}(thisGeoDBStore)
|
||||
}
|
||||
|
||||
return thisGeoDBStore, nil
|
||||
}
|
||||
|
||||
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
|
||||
@ -73,8 +105,12 @@ func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error)
|
||||
|
||||
}
|
||||
|
||||
// Close the store
|
||||
func (s *Store) Close() {
|
||||
|
||||
if s.option.AllowSlowIpv4LookUp || s.option.AllowSlowIpv6Lookup {
|
||||
//Stop cache clear ticker
|
||||
s.cacheClearTickerStopChan <- true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
||||
|
@ -44,6 +44,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
|
||||
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
|
||||
false,
|
||||
true,
|
||||
0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("error creating store: %v", err)
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -56,6 +56,12 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
|
||||
if isReservedIP(ipAddr) {
|
||||
return ""
|
||||
}
|
||||
|
||||
//Check if already in cache
|
||||
if cc, ok := s.slowLookupCacheIpv4[ipAddr]; ok {
|
||||
return cc
|
||||
}
|
||||
|
||||
for _, ipRange := range s.geodb {
|
||||
startIp := ipRange[0]
|
||||
endIp := ipRange[1]
|
||||
@ -63,6 +69,8 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
|
||||
|
||||
inRange, _ := isIPv4InRange(startIp, endIp, ipAddr)
|
||||
if inRange {
|
||||
//Add to cache
|
||||
s.slowLookupCacheIpv4[ipAddr] = cc
|
||||
return cc
|
||||
}
|
||||
}
|
||||
@ -73,6 +81,12 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
|
||||
if isReservedIP(ipAddr) {
|
||||
return ""
|
||||
}
|
||||
|
||||
//Check if already in cache
|
||||
if cc, ok := s.slowLookupCacheIpv6[ipAddr]; ok {
|
||||
return cc
|
||||
}
|
||||
|
||||
for _, ipRange := range s.geodbIpv6 {
|
||||
startIp := ipRange[0]
|
||||
endIp := ipRange[1]
|
||||
@ -80,6 +94,8 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
|
||||
|
||||
inRange, _ := isIPv6InRange(startIp, endIp, ipAddr)
|
||||
if inRange {
|
||||
//Add to cache
|
||||
s.slowLookupCacheIpv6[ipAddr] = cc
|
||||
return cc
|
||||
}
|
||||
}
|
||||
|
@ -169,9 +169,16 @@ func (n *NetStatBuffers) HandleGetBufferedNetworkInterfaceStats(w http.ResponseW
|
||||
}
|
||||
|
||||
func (n *NetStatBuffers) Close() {
|
||||
n.StopChan <- true
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
n.EventTicker.Stop()
|
||||
//Fixed issue #394 for stopping netstat listener on platforms not supported platforms
|
||||
if n.StopChan != nil {
|
||||
n.StopChan <- true
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
|
||||
if n.EventTicker != nil {
|
||||
n.EventTicker.Stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r *http.Request) {
|
||||
@ -270,11 +277,11 @@ func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
|
||||
allIfaceRxByteFiles, err := filepath.Glob("/sys/class/net/*/statistics/rx_bytes")
|
||||
if err != nil {
|
||||
//Permission denied
|
||||
return 0, 0, errors.New("Access denied")
|
||||
return 0, 0, errors.New("access denied")
|
||||
}
|
||||
|
||||
if len(allIfaceRxByteFiles) == 0 {
|
||||
return 0, 0, errors.New("No valid iface found")
|
||||
return 0, 0, errors.New("no valid iface found")
|
||||
}
|
||||
|
||||
rxSum := int64(0)
|
||||
@ -334,5 +341,5 @@ func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
|
||||
return 0, 0, nil //no ethernet adapters with en*/<Link#*>
|
||||
}
|
||||
|
||||
return 0, 0, errors.New("Platform not supported")
|
||||
return 0, 0, errors.New("platform not supported")
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package v308
|
||||
|
||||
/*
|
||||
v307 type definations
|
||||
v307 type definitions
|
||||
|
||||
This file wrap up the self-contained data structure
|
||||
for v3.0.7 structure and allow automatic updates
|
||||
|
@ -1,7 +1,7 @@
|
||||
package v308
|
||||
|
||||
/*
|
||||
v308 type definations
|
||||
v308 type definition
|
||||
|
||||
This file wrap up the self-contained data structure
|
||||
for v3.0.8 structure and allow automatic updates
|
||||
|
@ -27,18 +27,18 @@ func ReverseProxtInit() {
|
||||
/*
|
||||
Load Reverse Proxy Global Settings
|
||||
*/
|
||||
inboundPort := 80
|
||||
inboundPort := 443
|
||||
if sysdb.KeyExists("settings", "inbound") {
|
||||
sysdb.Read("settings", "inbound", &inboundPort)
|
||||
SystemWideLogger.Println("Serving inbound port ", inboundPort)
|
||||
} else {
|
||||
SystemWideLogger.Println("Inbound port not set. Using default (80)")
|
||||
SystemWideLogger.Println("Inbound port not set. Using default (443)")
|
||||
}
|
||||
|
||||
useTls := false
|
||||
useTls := true
|
||||
sysdb.Read("settings", "usetls", &useTls)
|
||||
if useTls {
|
||||
SystemWideLogger.Println("TLS mode enabled. Serving proxxy request with TLS")
|
||||
SystemWideLogger.Println("TLS mode enabled. Serving proxy request with TLS")
|
||||
} else {
|
||||
SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http")
|
||||
}
|
||||
@ -59,7 +59,7 @@ func ReverseProxtInit() {
|
||||
SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
|
||||
}
|
||||
|
||||
listenOnPort80 := false
|
||||
listenOnPort80 := true
|
||||
sysdb.Read("settings", "listenP80", &listenOnPort80)
|
||||
if listenOnPort80 {
|
||||
SystemWideLogger.Println("Port 80 listener enabled")
|
||||
@ -67,7 +67,7 @@ func ReverseProxtInit() {
|
||||
SystemWideLogger.Println("Port 80 listener disabled")
|
||||
}
|
||||
|
||||
forceHttpsRedirect := false
|
||||
forceHttpsRedirect := true
|
||||
sysdb.Read("settings", "redirect", &forceHttpsRedirect)
|
||||
if forceHttpsRedirect {
|
||||
SystemWideLogger.Println("Force HTTPS mode enabled")
|
||||
@ -85,7 +85,7 @@ func ReverseProxtInit() {
|
||||
|
||||
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
|
||||
HostUUID: nodeUUID,
|
||||
HostVersion: version,
|
||||
HostVersion: SYSTEM_VERSION,
|
||||
Port: inboundPort,
|
||||
UseTls: useTls,
|
||||
ForceTLSLatest: forceLatestTLSVersion,
|
||||
@ -1085,6 +1085,7 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
|
||||
if dynamicProxyRouter.Running {
|
||||
dynamicProxyRouter.StopProxyService()
|
||||
dynamicProxyRouter.Option.Port = newIncomingPortInt
|
||||
time.Sleep(1 * time.Second) //Fixed start fail issue
|
||||
dynamicProxyRouter.StartProxyService()
|
||||
} else {
|
||||
//Only change setting but not starting the proxy service
|
||||
@ -1173,7 +1174,7 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Create a Custom Header Defination type
|
||||
//Create a Custom Header Definition type
|
||||
var rewriteDirection rewrite.HeaderDirection
|
||||
if direction == "toOrigin" {
|
||||
rewriteDirection = rewrite.HeaderDirection_ZoraxyToUpstream
|
||||
|
@ -27,7 +27,7 @@ func FSHandler(handler http.Handler) http.Handler {
|
||||
Development Mode Override
|
||||
=> Web root is located in /
|
||||
*/
|
||||
if development && strings.HasPrefix(r.URL.Path, "/web/") {
|
||||
if DEVELOPMENT_BUILD && strings.HasPrefix(r.URL.Path, "/web/") {
|
||||
u, _ := url.Parse(strings.TrimPrefix(r.URL.Path, "/web"))
|
||||
r.URL = u
|
||||
}
|
||||
@ -36,7 +36,7 @@ func FSHandler(handler http.Handler) http.Handler {
|
||||
Production Mode Override
|
||||
=> Web root is located in /web
|
||||
*/
|
||||
if !development && r.URL.Path == "/" {
|
||||
if !DEVELOPMENT_BUILD && r.URL.Path == "/" {
|
||||
//Redirect to web UI
|
||||
http.Redirect(w, r, "/web/", http.StatusTemporaryRedirect)
|
||||
return
|
||||
@ -93,7 +93,7 @@ func FSHandler(handler http.Handler) http.Handler {
|
||||
|
||||
// Production path fix wrapper. Fix the path on production or development environment
|
||||
func ppf(relativeFilepath string) string {
|
||||
if !development {
|
||||
if !DEVELOPMENT_BUILD {
|
||||
return strings.ReplaceAll(filepath.Join("/web/", relativeFilepath), "\\", "/")
|
||||
}
|
||||
return relativeFilepath
|
||||
@ -111,7 +111,7 @@ func handleInjectHTML(w http.ResponseWriter, r *http.Request, relativeFilepath s
|
||||
if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
|
||||
relativeFilepath = relativeFilepath + "index.html"
|
||||
}
|
||||
if development {
|
||||
if DEVELOPMENT_BUILD {
|
||||
//Load from disk
|
||||
targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
|
||||
content, err = os.ReadFile(targetFilePath)
|
||||
|
51
src/start.go
51
src/start.go
@ -52,19 +52,19 @@ var (
|
||||
|
||||
func startupSequence() {
|
||||
//Start a system wide logger and log viewer
|
||||
l, err := logger.NewLogger("zr", "./log")
|
||||
l, err := logger.NewLogger(LOG_PREFIX, LOG_FOLDER)
|
||||
if err == nil {
|
||||
SystemWideLogger = l
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
|
||||
RootFolder: "./log",
|
||||
Extension: ".log",
|
||||
RootFolder: LOG_FOLDER,
|
||||
Extension: LOG_EXTENSION,
|
||||
})
|
||||
|
||||
//Create database
|
||||
db, err := database.NewDatabase("sys.db", false)
|
||||
db, err := database.NewDatabase(DATABASE_PATH, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -73,21 +73,21 @@ func startupSequence() {
|
||||
sysdb.NewTable("settings")
|
||||
|
||||
//Create tmp folder and conf folder
|
||||
os.MkdirAll("./tmp", 0775)
|
||||
os.MkdirAll("./conf/proxy/", 0775)
|
||||
os.MkdirAll(TMP_FOLDER, 0775)
|
||||
os.MkdirAll(CONF_HTTP_PROXY, 0775)
|
||||
|
||||
//Create an auth agent
|
||||
sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
|
||||
authAgent = auth.NewAuthenticationAgent(SYSTEM_NAME, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) {
|
||||
//Not logged in. Redirecting to login page
|
||||
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
|
||||
})
|
||||
|
||||
//Create a TLS certificate manager
|
||||
tlsCertManager, err = tlscert.NewManager("./conf/certs", development, SystemWideLogger)
|
||||
tlsCertManager, err = tlscert.NewManager(CONF_CERT_STORE, DEVELOPMENT_BUILD, SystemWideLogger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -96,15 +96,16 @@ func startupSequence() {
|
||||
db.NewTable("redirect")
|
||||
redirectAllowRegexp := false
|
||||
db.Read("redirect", "regex", &redirectAllowRegexp)
|
||||
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp, SystemWideLogger)
|
||||
redirectTable, err = redirection.NewRuleTable(CONF_REDIRECTION, redirectAllowRegexp, SystemWideLogger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create a geodb store
|
||||
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
|
||||
AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup,
|
||||
AllowSloeIpv6Lookup: !*enableHighSpeedGeoIPLookup,
|
||||
AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup,
|
||||
AllowSlowIpv6Lookup: !*enableHighSpeedGeoIPLookup,
|
||||
SlowLookupCacheClearInterval: GEODB_CACHE_CLEAR_INTERVAL * time.Minute,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -121,7 +122,7 @@ func startupSequence() {
|
||||
accessController, err = access.NewAccessController(&access.Options{
|
||||
Database: sysdb,
|
||||
GeoDB: geodbStore,
|
||||
ConfigFolder: "./conf/access",
|
||||
ConfigFolder: CONF_ACCESS_RULE,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -154,7 +155,7 @@ func startupSequence() {
|
||||
//Start the static web server
|
||||
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
|
||||
Sysdb: sysdb,
|
||||
Port: "5487", //Default Port
|
||||
Port: strconv.Itoa(WEBSERV_DEFAULT_PORT), //Default Port
|
||||
WebRoot: *staticWebServerRoot,
|
||||
EnableDirectoryListing: true,
|
||||
EnableWebDirManager: *allowWebFileManager,
|
||||
@ -179,7 +180,7 @@ func startupSequence() {
|
||||
|
||||
pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{
|
||||
Enabled: false,
|
||||
ConfigFolder: "./conf/rules/pathrules",
|
||||
ConfigFolder: CONF_PATH_RULE,
|
||||
})
|
||||
|
||||
/*
|
||||
@ -197,7 +198,7 @@ func startupSequence() {
|
||||
|
||||
hostName := *mdnsName
|
||||
if hostName == "" {
|
||||
hostName = "zoraxy_" + nodeUUID
|
||||
hostName = MDNS_HOSTNAME_PREFIX + nodeUUID
|
||||
} else {
|
||||
//Trim off the suffix
|
||||
hostName = strings.TrimSuffix(hostName, ".local")
|
||||
@ -206,24 +207,24 @@ func startupSequence() {
|
||||
mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{
|
||||
HostName: hostName,
|
||||
Port: portInt,
|
||||
Domain: "zoraxy.aroz.org",
|
||||
Model: "Network Gateway",
|
||||
Domain: MDNS_IDENTIFY_DOMAIN,
|
||||
Model: MDNS_IDENTIFY_DEVICE_TYPE,
|
||||
UUID: nodeUUID,
|
||||
Vendor: "imuslab.com",
|
||||
BuildVersion: version,
|
||||
Vendor: MDNS_IDENTIFY_VENDOR,
|
||||
BuildVersion: SYSTEM_VERSION,
|
||||
}, "")
|
||||
if err != nil {
|
||||
SystemWideLogger.Println("Unable to startup mDNS service. Disabling mDNS services")
|
||||
} else {
|
||||
//Start initial scanning
|
||||
go func() {
|
||||
hosts := mdnsScanner.Scan(30, "")
|
||||
hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
|
||||
previousmdnsScanResults = hosts
|
||||
SystemWideLogger.Println("mDNS Startup scan completed")
|
||||
}()
|
||||
|
||||
//Create a ticker to update mDNS results every 5 minutes
|
||||
ticker := time.NewTicker(15 * time.Minute)
|
||||
ticker := time.NewTicker(MDNS_SCAN_UPDATE_INTERVAL * time.Minute)
|
||||
stopChan := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
@ -231,7 +232,7 @@ func startupSequence() {
|
||||
case <-stopChan:
|
||||
ticker.Stop()
|
||||
case <-ticker.C:
|
||||
hosts := mdnsScanner.Scan(30, "")
|
||||
hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
|
||||
previousmdnsScanResults = hosts
|
||||
SystemWideLogger.Println("mDNS scan result updated")
|
||||
}
|
||||
@ -265,7 +266,7 @@ func startupSequence() {
|
||||
//Create TCP Proxy Manager
|
||||
streamProxyManager, err = streamproxy.NewStreamProxy(&streamproxy.Options{
|
||||
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
|
||||
ConfigStore: "./conf/streamproxy",
|
||||
ConfigStore: CONF_STREAM_PROXY,
|
||||
Logger: SystemWideLogger,
|
||||
})
|
||||
if err != nil {
|
||||
@ -303,8 +304,8 @@ func startupSequence() {
|
||||
sysdb.NewTable("acmepref")
|
||||
acmeHandler = initACME()
|
||||
acmeAutoRenewer, err = acme.NewAutoRenewer(
|
||||
"./conf/acme_conf.json",
|
||||
"./conf/certs/",
|
||||
ACME_AUTORENEW_CONFIG_PATH,
|
||||
CONF_CERT_STORE,
|
||||
int64(*acmeAutoRenewInterval),
|
||||
*acmeCertAutoRenewDays,
|
||||
acmeHandler,
|
||||
|
@ -841,6 +841,25 @@
|
||||
function initBannedCountryList(){
|
||||
$.get("/api/blacklist/list?type=country&id=" + currentEditingAccessRule, function(data) {
|
||||
let bannedListHtml = '';
|
||||
|
||||
//Check if the country code list contains all eu countries. If yes, replace it with "EU"
|
||||
let allEu = true;
|
||||
let euCountries = getEUCCs();
|
||||
for (var i = 0; i < euCountries.length; i++){
|
||||
if (!data.includes(euCountries[i])){
|
||||
allEu = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allEu){
|
||||
//Remove EU countries from the list and replace it with EU
|
||||
data = data.filter(function(value, index, arr){
|
||||
return !euCountries.includes(value);
|
||||
});
|
||||
data.push("eu");
|
||||
}
|
||||
|
||||
data.forEach((countryCode) => {
|
||||
bannedListHtml += `
|
||||
<tr>
|
||||
@ -919,18 +938,48 @@
|
||||
//Whitelist country table
|
||||
function initWhitelistCountryList(){
|
||||
$.get("/api/whitelist/list?type=country&id=" + currentEditingAccessRule, function(data) {
|
||||
let bannedListHtml = '';
|
||||
let whiteListHTML = '';
|
||||
|
||||
//Check if the country code list contains all eu countries. If yes, replace it with "EU"
|
||||
let allEu = true;
|
||||
let euCountries = getEUCCs();
|
||||
let countryCodesIndata = data.map(function(item){
|
||||
//data[n].CC is the country code
|
||||
return item.CC;
|
||||
});
|
||||
for (var i = 0; i < euCountries.length; i++){
|
||||
if (!countryCodesIndata.includes(euCountries[i])){
|
||||
allEu = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allEu){
|
||||
//Remove EU countries from the list and replace it with EU
|
||||
data = data.filter(function(value, index, arr){
|
||||
return !euCountries.includes(value.CC);
|
||||
});
|
||||
data.push({
|
||||
CC: "eu"
|
||||
});
|
||||
}
|
||||
|
||||
data.forEach((countryWhitelistEntry) => {
|
||||
let countryCode = countryWhitelistEntry.CC;
|
||||
bannedListHtml += `
|
||||
whiteListHTML += `
|
||||
<tr>
|
||||
<td><i class="${countryCode} flag"></i> ${getCountryName(countryCode)} (${countryCode.toUpperCase()})</td>
|
||||
<td><button class="ui red basic mini icon button" onclick="removeFromWhiteList('${countryCode}')"><i class="trash icon"></i></button></td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
$('#whitelistCountryList').html(bannedListHtml);
|
||||
filterCountries(data, "#countrySelectorWhitelist .menu .item");
|
||||
$('#whitelistCountryList').html(whiteListHTML);
|
||||
|
||||
//Map the data.CC to the country code
|
||||
let countryCodes = data.map(function(item){
|
||||
return item.CC;
|
||||
});
|
||||
filterCountries(countryCodes, "#countrySelectorWhitelist .menu .item");
|
||||
if (data.length === 0) {
|
||||
$('#whitelistCountryList').append(`
|
||||
<tr>
|
||||
@ -1016,6 +1065,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getEUCCs(){
|
||||
return ["at","be","bg","cy","cz","de","dk","ee","es","fi","fr","gr","hr","hu","ie","it","lt","lu","lv","mt","nl","pl","pt","se","si","sk"];
|
||||
}
|
||||
|
||||
function addCountryToBlacklist() {
|
||||
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
|
||||
let ccs = [countryCode];
|
||||
@ -1025,48 +1078,50 @@
|
||||
ccs = countryCode.split(",");
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
for(var i = 0; i < ccs.length; i++){
|
||||
let thisCountryCode = ccs[i];
|
||||
$.cjax({
|
||||
type: "POST",
|
||||
url: "/api/blacklist/country/add",
|
||||
method: "POST",
|
||||
data: { cc: thisCountryCode, id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
|
||||
if (counter == (ccs.length - 1)){
|
||||
//Last item
|
||||
setTimeout(function(){
|
||||
initBannedCountryList();
|
||||
if (ccs.length == 1){
|
||||
//Single country
|
||||
msgbox(`Added ${getCountryName(ccs[0])} to blacklist`);
|
||||
}else{
|
||||
msgbox(ccs.length + " countries added to blacklist");
|
||||
}
|
||||
|
||||
}, (ccs.length==1)?0:100);
|
||||
}
|
||||
counter++;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
//If the ccs includes "eu", remove the "eu" and add all eu country code to the list
|
||||
if (ccs.includes("eu")){
|
||||
ccs = ccs.concat(getEUCCs());
|
||||
ccs = ccs.filter(function(item){
|
||||
return item != "eu";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
let counter = ccs.length;
|
||||
$.cjax({
|
||||
type: "POST",
|
||||
url: "/api/blacklist/country/add",
|
||||
method: "POST",
|
||||
data: { cc: ccs.join(","), id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
initBannedCountryList();
|
||||
if (ccs.length == 1){
|
||||
//Single country
|
||||
msgbox(`Added ${getCountryName(ccs[0])} to blacklist`);
|
||||
}else{
|
||||
msgbox(ccs.length + " countries added to blacklist");
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
});
|
||||
$('#countrySelector').dropdown('clear');
|
||||
|
||||
}
|
||||
|
||||
function removeFromBannedList(countryCode){
|
||||
countryCode = countryCode.toLowerCase();
|
||||
let countryName = getCountryName(countryCode);
|
||||
if (countryCode == "eu"){
|
||||
let euCountries = getEUCCs();
|
||||
countryCode = euCountries.join(",");
|
||||
countryName = "European Union";
|
||||
}else{
|
||||
countryCode = countryCode.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
$.cjax({
|
||||
url: "/api/blacklist/country/remove",
|
||||
method: "POST",
|
||||
@ -1162,44 +1217,53 @@
|
||||
//Usually just a few countries a for loop will get the job done
|
||||
ccs = countryCode.split(",");
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
for(var i = 0; i < ccs.length; i++){
|
||||
let thisCountryCode = ccs[i];
|
||||
$.cjax({
|
||||
type: "POST",
|
||||
url: "/api/whitelist/country/add",
|
||||
data: { cc: thisCountryCode , id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
|
||||
if (counter == (ccs.length - 1)){
|
||||
setTimeout(function(){
|
||||
initWhitelistCountryList();
|
||||
if (ccs.length == 1){
|
||||
//Single country
|
||||
msgbox(`Added ${getCountryName(ccs[0])} to whitelist`);
|
||||
}else{
|
||||
msgbox(ccs.length + " countries added to whitelist");
|
||||
}
|
||||
}, (ccs.length==1)?0:100);
|
||||
}
|
||||
counter++;
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
//If the ccs includes "eu", remove the "eu" and add all eu country code to the list
|
||||
if (ccs.includes("eu")){
|
||||
ccs = ccs.filter(function(item){
|
||||
return item != "eu";
|
||||
});
|
||||
ccs = ccs.concat(getEUCCs());
|
||||
}
|
||||
|
||||
let counter = ccs.length;
|
||||
$.cjax({
|
||||
type: "POST",
|
||||
url: "/api/whitelist/country/add",
|
||||
data: { cc: ccs.join(",") , id: currentEditingAccessRule},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
|
||||
initWhitelistCountryList();
|
||||
if (ccs.length == 1){
|
||||
//Single country
|
||||
msgbox(`Added ${getCountryName(ccs[0])} to whitelist`);
|
||||
}else{
|
||||
msgbox(ccs.length + " countries added to whitelist");
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('#countrySelectorWhitelist').dropdown('clear');
|
||||
}
|
||||
|
||||
function removeFromWhiteList(countryCode){
|
||||
if (confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){
|
||||
//Remove from whitelist, accepts a country code or "eu" for all EU countries
|
||||
function removeFromWhiteList(countryCode, skipConfirm = true){
|
||||
let countryName = getCountryName(countryCode);
|
||||
if (countryCode == "eu"){
|
||||
let euCountries = getEUCCs();
|
||||
countryCode = euCountries.join(",");
|
||||
countryName = "European Union";
|
||||
}else{
|
||||
countryCode = countryCode.toLowerCase();
|
||||
}
|
||||
if (skipConfirm || confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){
|
||||
$.cjax({
|
||||
url: "/api/whitelist/country/remove",
|
||||
method: "POST",
|
||||
@ -1208,6 +1272,7 @@
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false);
|
||||
}
|
||||
msgbox(countryName + " removed from whitelist");
|
||||
initWhitelistCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
@ -1276,19 +1341,27 @@
|
||||
/*
|
||||
Common Utilities
|
||||
*/
|
||||
function filterCountries(codesToShow, selector="#countrySelector .menu .item") {
|
||||
function filterCountries(alreadySelectedCCs, selector="#countrySelector .menu .item") {
|
||||
// get all items in the dropdown
|
||||
const items = document.querySelectorAll(selector);
|
||||
const euCountries = getEUCCs();
|
||||
//Replce "eu" in alreadySelectedCCs with all EU countries
|
||||
if (alreadySelectedCCs.includes("eu")){
|
||||
alreadySelectedCCs = alreadySelectedCCs.filter(function(item){
|
||||
return item != "eu";
|
||||
});
|
||||
alreadySelectedCCs = alreadySelectedCCs.concat(euCountries);
|
||||
}
|
||||
|
||||
// loop through all items
|
||||
items.forEach(item => {
|
||||
// get the value of the item (i.e. the country code)
|
||||
const code = item.dataset.value;
|
||||
// if the code is in the array of codes to show, show the item
|
||||
if (codesToShow.includes(code)) {
|
||||
if (alreadySelectedCCs.includes(code)) {
|
||||
//This country code already selected. Hide it
|
||||
item.style.display = 'none';
|
||||
}
|
||||
// otherwise, hide the item
|
||||
else {
|
||||
} else {
|
||||
// otherwise, show the item
|
||||
item.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
@ -101,7 +101,7 @@
|
||||
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
|
||||
<div class="ui buttons">
|
||||
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
|
||||
<button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button>
|
||||
<button class="ui basic button" onclick="uploadPrivateKey();"><i class="grey lock icon"></i> Private Key</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
|
@ -30,7 +30,7 @@
|
||||
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
|
||||
</div>
|
||||
<!-- Options -->
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui basic segment advanceoptions">
|
||||
<div class="ui accordion advanceSettings">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
@ -173,7 +173,7 @@
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
$("#redirectionRuleList").append(`<tr colspan="4"><td><i class="green check circle icon"></i> No redirection rule</td></tr>`);
|
||||
$("#redirectionRuleList").append(`<tr><td colspan="5"><i class="green check circle icon"></i> No redirection rule</td></tr>`);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -50,7 +50,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Advance configs -->
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui basic segment advanceoptions">
|
||||
<div id="advanceProxyRules" class="ui fluid accordion">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
@ -295,15 +295,25 @@
|
||||
//Automatic check if the site require TLS and check the checkbox if needed
|
||||
function autoCheckTls(targetDomain){
|
||||
$.cjax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
url: "/api/proxy/tlscheck?selfsignchk=true",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false);
|
||||
}else if (data == "https"){
|
||||
$("#reqTls").parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
$("#reqTls").parent().checkbox("set unchecked");
|
||||
}else{
|
||||
//Check if the site require TLS
|
||||
if (data.protocol == "https"){
|
||||
$("#reqTls").parent().checkbox("set checked");
|
||||
}else if (data.protocol == "http"){
|
||||
$("#reqTls").parent().checkbox("set unchecked");
|
||||
}
|
||||
//Check if the site is using self-signed cert
|
||||
if (data.selfsign){
|
||||
$("#skipTLSValidation").parent().checkbox("set checked");
|
||||
}else{
|
||||
$("#skipTLSValidation").parent().checkbox("set unchecked");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -73,28 +73,30 @@
|
||||
<p>Inbound Port (Reverse Proxy Listening Port)</p>
|
||||
<div class="ui action fluid notloopbackOnly input" tourstep="incomingPort">
|
||||
<small id="applyButtonReminder">Click "Apply" button to confirm listening port changes</small>
|
||||
<input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
|
||||
<input type="text" id="incomingPort" placeholder="Incoming Port" value="443">
|
||||
<button class="ui green notloopbackOnly button" style="background: linear-gradient(60deg, #27e7ff, #00ca52);" onclick="handlePortChange();"><i class="ui checkmark icon"></i> Apply</button>
|
||||
</div>
|
||||
<br>
|
||||
<div id="tls" class="ui toggle notloopbackOnly checkbox">
|
||||
<input type="checkbox">
|
||||
<label>Use TLS to serve proxy request</label>
|
||||
<label>Use TLS to serve proxy request<br>
|
||||
<small>Also known as HTTPS mode</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div id="listenP80" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em;" >
|
||||
<div id="listenP80" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.4em;" >
|
||||
<input type="checkbox">
|
||||
<label>Enable HTTP server on port 80<br>
|
||||
<small>(Only apply when TLS enabled and not using port 80)</small></label>
|
||||
<small>Accept HTTP requests even if you are using HTTPS mode</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div tourstep="forceHttpsRedirect" style="display: inline-block;">
|
||||
<div id="redirect" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em; padding-left: 2em;">
|
||||
<div id="redirect" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox">
|
||||
<label>Force redirect HTTP request to HTTPS</label>
|
||||
<label>Force redirect HTTP request to HTTPS<br>
|
||||
<small>Redirect web traffic from port 80 to 443, require enabling HTTP server on port 80</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui basic segment advanceoptions">
|
||||
<div class="ui accordion advanceSettings">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
@ -359,10 +361,10 @@
|
||||
return;
|
||||
}
|
||||
if (enabled){
|
||||
$("#redirect").show();
|
||||
//$("#redirect").show();
|
||||
msgbox("Port 80 listener enabled");
|
||||
}else{
|
||||
$("#redirect").hide();
|
||||
//$("#redirect").hide();
|
||||
msgbox("Port 80 listener disabled");
|
||||
}
|
||||
}
|
||||
@ -400,10 +402,10 @@
|
||||
$.get("/api/proxy/listenPort80", function(data){
|
||||
if (data){
|
||||
$("#listenP80").checkbox("set checked");
|
||||
$("#redirect").show();
|
||||
//$("#redirect").show();
|
||||
}else{
|
||||
$("#listenP80").checkbox("set unchecked");
|
||||
$("#redirect").hide();
|
||||
//$("#redirect").hide();
|
||||
}
|
||||
|
||||
$("#listenP80").find("input").on("change", function(){
|
||||
@ -579,7 +581,7 @@
|
||||
let timestamps = [];
|
||||
|
||||
for(var i = 0; i < dataCount; i++){
|
||||
timestamps.push(parseInt(Date.now() / 1000) + i);
|
||||
timestamps.push(new Date(Date.now() + i * 1000).toLocaleString().replace(',', ''));
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
@ -600,10 +602,8 @@
|
||||
txValues.shift();
|
||||
}
|
||||
|
||||
|
||||
timestamps.push(parseInt(Date.now() / 1000));
|
||||
timestamps.push(new Date(Date.now()).toLocaleString().replace(',', ''));
|
||||
timestamps.shift();
|
||||
|
||||
updateChart();
|
||||
}
|
||||
})
|
||||
|
@ -70,7 +70,7 @@
|
||||
initUptimeTable();
|
||||
|
||||
function reloadUptimeList(){
|
||||
$("#utmrender").html(`<div class="ui segment">
|
||||
$("#utmrender").html(`<div class="ui utmloading segment">
|
||||
<div class="ui active inverted dimmer" style="z-index: 2;">
|
||||
<div class="ui text loader">Loading</div>
|
||||
</div>
|
||||
|
@ -69,7 +69,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Advance configs -->
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui basic segment advanceoptions">
|
||||
<div id="advanceProxyRules" class="ui fluid accordion">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
@ -191,14 +191,19 @@
|
||||
var targetDomain = $("#virtualDirectoryDomain").val().trim();
|
||||
if (targetDomain != ""){
|
||||
$.cjax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
url: "/api/proxy/tlscheck?selfsignchk=true",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
|
||||
}else if (data == "https"){
|
||||
}else if (data.protocol == "https"){
|
||||
$("#vdReqTls").parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
if (data.selfsign){
|
||||
$("#vdSkipTLSValidation").parent().checkbox("set checked");
|
||||
}else{
|
||||
$("#vdSkipTLSValidation").parent().checkbox("set unchecked");
|
||||
}
|
||||
}else if (data.protocol == "http"){
|
||||
$("#vdReqTls").parent().checkbox("set unchecked");
|
||||
}
|
||||
}
|
||||
|
1133
src/web/darktheme.css
Normal file
1133
src/web/darktheme.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -16,22 +16,24 @@
|
||||
<script src="script/chart.js"></script>
|
||||
<script src="script/utils.js"></script>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<link rel="stylesheet" href="darktheme.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="script/darktheme.js"></script>
|
||||
<div class="menubar">
|
||||
<div class="item">
|
||||
<img class="logo" src="img/logo.svg">
|
||||
</div>
|
||||
|
||||
<div class="ui right floated buttons menutoggle" style="padding-top: 2px;">
|
||||
<button class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
|
||||
<button id="sidemenuBtn" class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
|
||||
</div>
|
||||
<div class="ui right floated buttons" style="padding-top: 2px; padding-right: 0.4em;">
|
||||
<button class="ui basic white icon button" onclick="logout();"><i class="sign-out icon"></i></button>
|
||||
</div>
|
||||
<!-- <div class="ui right floated buttons" style="padding-top: 2px;">
|
||||
<div class="ui right floated buttons" style="padding-top: 2px; margin-right: 0.4em;">
|
||||
<button id="themeColorButton" class="ui icon button" onclick="toggleTheme();"><i class="sun icon"></i></button>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<div class="toolbar">
|
||||
@ -269,11 +271,18 @@
|
||||
|
||||
function toggleTheme(){
|
||||
if ($("body").hasClass("darkTheme")){
|
||||
$("body").removeClass("darkTheme")
|
||||
$("#themeColorButton").html(`<i class="ui moon icon"></i>`);
|
||||
setDarkTheme(false);
|
||||
//Check if the snippet iframe is opened. If yes, set the dark theme to the iframe
|
||||
if ($(".sideWrapper").is(":visible")){
|
||||
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false);
|
||||
}
|
||||
}else{
|
||||
$("body").addClass("darkTheme");
|
||||
$("#themeColorButton").html(`<i class="ui sun icon"></i>`);
|
||||
setDarkTheme(true);
|
||||
//Check if the snippet iframe is opened. If yes, set the dark theme to the iframe
|
||||
if ($(".sideWrapper").is(":visible")){
|
||||
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,50 +1,8 @@
|
||||
/*
|
||||
index.html style overwrite
|
||||
*/
|
||||
:root{
|
||||
--theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%);
|
||||
--theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%);
|
||||
--theme_green: linear-gradient(270deg, #27e7ff, #00ca52);
|
||||
--theme_red: linear-gradient(203deg, rgba(250,172,38,1) 17%, rgba(202,0,37,1) 78%);
|
||||
}
|
||||
|
||||
/* Theme Color Definations */
|
||||
body:not(.darkTheme){
|
||||
--theme_bg: #f6f6f6;
|
||||
--theme_bg_primary: #ffffff;
|
||||
--theme_bg_secondary: #ffffff;
|
||||
--theme_bg_active: #ececec;
|
||||
--theme_highlight: #a9d1f3;
|
||||
--theme_bg_inverted: #27292d;
|
||||
--theme_advance: #f8f8f9;
|
||||
--item_color: #5e5d5d;
|
||||
--item_color_select: rgba(0,0,0,.87);
|
||||
--text_color: #414141;
|
||||
--input_color: white;
|
||||
--divider_color: #cacaca;
|
||||
--text_color_inverted: #fcfcfc;
|
||||
--button_text_color: #878787;
|
||||
--button_border_color: #dedede;
|
||||
}
|
||||
|
||||
body.darkTheme{
|
||||
--theme_bg: #27292d;
|
||||
--theme_bg_primary: #3d3f47;
|
||||
--theme_bg_secondary: #373a42;
|
||||
--theme_highlight: #6682c4;
|
||||
--theme_bg_active: #292929;
|
||||
--theme_bg_inverted: #f8f8f9;
|
||||
--theme_advance: #333333;
|
||||
--item_color: #cacaca;
|
||||
--text_color: #fcfcfc;
|
||||
--text_color_secondary: #dfdfdf;
|
||||
--input_color: black;
|
||||
--divider_color: #3b3b3b;
|
||||
--item_color_select: rgba(255, 255, 255, 0.87);
|
||||
--text_color_inverted: #414141;
|
||||
--button_text_color: #e9e9e9;
|
||||
--button_border_color: #646464;
|
||||
}
|
||||
/* Theme color palletes are defined in darktheme.css */
|
||||
|
||||
/* Theme Toggle CSS */
|
||||
#themeColorButton{
|
||||
@ -368,7 +326,7 @@ body{
|
||||
}
|
||||
|
||||
.basic.segment.advanceoptions{
|
||||
background-color: #f7f7f7;
|
||||
background-color: var(--theme_advance);
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
|
6
src/web/robots.txt
Normal file
6
src/web/robots.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# robots.txt for Zoraxy project
|
||||
# In general, you should not expose the management interface to the internet.
|
||||
# In case you do, this file (hopefully) protects you from web crawlers.
|
||||
|
||||
User-agent: *
|
||||
Disallow: /
|
51
src/web/script/darktheme.js
Normal file
51
src/web/script/darktheme.js
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Dark Theme Toggle Manager
|
||||
|
||||
This script is used to manage the dark theme toggle button in the header of the website.
|
||||
It will change the theme of the website to dark mode when the toggle is clicked and back to light mode when clicked again.
|
||||
|
||||
Must be included just after the start of body tag in the HTML file.
|
||||
*/
|
||||
|
||||
function _whiteThemeHandleApplyChange(){
|
||||
$(".menubar .logo").attr("src", "img/logo.svg");
|
||||
}
|
||||
|
||||
function _darkThemeHandleApplyChange(){
|
||||
$(".menubar .logo").attr("src", "img/logo_white.svg");
|
||||
}
|
||||
|
||||
|
||||
//Check if the theme is dark, must be done before the body is loaded to prevent flickering
|
||||
function setDarkTheme(isDarkTheme = false){
|
||||
if (isDarkTheme){
|
||||
$("body").addClass("darkTheme");
|
||||
$("#themeColorButton").html(`<i class="ui sun icon"></i>`);
|
||||
localStorage.setItem("theme", "dark");
|
||||
|
||||
//Check if the page is still loading, if not change the logo
|
||||
if (document.readyState == "complete"){
|
||||
_darkThemeHandleApplyChange();
|
||||
}else{
|
||||
//Wait for the page to load and then change the logo
|
||||
$(document).ready(function(){
|
||||
_darkThemeHandleApplyChange();
|
||||
});
|
||||
}
|
||||
}else{
|
||||
$("body").removeClass("darkTheme")
|
||||
$("#themeColorButton").html(`<i class="ui moon icon"></i>`);
|
||||
localStorage.setItem("theme", "light");
|
||||
//By default the page is white theme. So no need to change the logo if page is still loading
|
||||
if (document.readyState == "complete"){
|
||||
//Switching back to light theme
|
||||
_whiteThemeHandleApplyChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem("theme") == "dark"){
|
||||
setDarkTheme(true);
|
||||
}else{
|
||||
setDarkTheme(false);
|
||||
}
|
@ -14,69 +14,72 @@
|
||||
top: 0.4em;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Access Rule Editor
|
||||
<div class="sub header">Create, Edit or Remove Access Rules</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui top attached tabular menu">
|
||||
<a class="active item" data-tab="new"><i class="ui green add icon"></i> New</a>
|
||||
<a class="item" data-tab="edit"><i class="ui grey edit icon"></i> Edit</a>
|
||||
</div>
|
||||
<div class="ui bottom attached active tab segment" data-tab="new">
|
||||
<p>Create a new Access Rule</p>
|
||||
<form class="ui form" id="accessRuleForm">
|
||||
<div class="field">
|
||||
<label>Rule Name</label>
|
||||
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<textarea name="description" placeholder="Description" required></textarea>
|
||||
</div>
|
||||
<button class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||
</form>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
</div>
|
||||
<div class="ui bottom attached tab segment" data-tab="edit">
|
||||
<p>Select an Access Rule to edit</p>
|
||||
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button>
|
||||
<div class="ui selection fluid dropdown" id="accessRuleSelector">
|
||||
<input type="hidden" name="targetAccessRule" value="default">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text"></div>
|
||||
<div class="menu" id="accessRuleList">
|
||||
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Access Rule Editor
|
||||
<div class="sub header">Create, Edit or Remove Access Rules</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui top attached tabular menu">
|
||||
<a class="active item" data-tab="new"><i class="ui green add icon"></i> New</a>
|
||||
<a class="item" data-tab="edit"><i class="ui grey edit icon"></i> Edit</a>
|
||||
</div>
|
||||
<div class="ui bottom attached active tab segment" data-tab="new">
|
||||
<p>Create a new Access Rule</p>
|
||||
<form class="ui form" id="accessRuleForm">
|
||||
<div class="field">
|
||||
<label>Rule Name</label>
|
||||
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<textarea name="description" placeholder="Description" required></textarea>
|
||||
</div>
|
||||
<button class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||
</form>
|
||||
<br>
|
||||
</div>
|
||||
<div class="ui bottom attached tab segment" data-tab="edit">
|
||||
<p>Select an Access Rule to edit</p>
|
||||
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button>
|
||||
<div class="ui selection fluid dropdown" id="accessRuleSelector">
|
||||
<input type="hidden" name="targetAccessRule" value="default">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text"></div>
|
||||
<div class="menu" id="accessRuleList">
|
||||
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<form class="ui form" id="modifyRuleInfo">
|
||||
<div class="disabled field">
|
||||
<label>Rule ID</label>
|
||||
<input type="text" name="accessRuleUUID">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Rule Name</label>
|
||||
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<textarea name="description" placeholder="Description" required></textarea>
|
||||
</div>
|
||||
<button class="ui basic button" type="submit"><i class="ui green save icon"></i> Save Changes</button>
|
||||
<button class="ui basic button" onclick="removeAccessRule(event);"><i class="ui red trash icon"></i> Remove Rule</button>
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
<form class="ui form" id="modifyRuleInfo">
|
||||
<div class="disabled field">
|
||||
<label>Rule ID</label>
|
||||
<input type="text" name="accessRuleUUID">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Rule Name</label>
|
||||
<input type="text" name="accessRuleName" placeholder="Rule Name" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<textarea name="description" placeholder="Description" required></textarea>
|
||||
</div>
|
||||
<button class="ui basic button" type="submit"><i class="ui green save icon"></i> Save Changes</button>
|
||||
<button class="ui basic button" onclick="removeAccessRule(event);"><i class="ui red trash icon"></i> Remove Rule</button>
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
|
||||
<br><br><br>
|
||||
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
|
||||
<br><br><br>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
@ -25,6 +25,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
@ -50,7 +52,7 @@
|
||||
</div>
|
||||
<small>If you don't want to share your private email address, you can also fill in an email address that point to a mailbox not exists on your domain.</small>
|
||||
</div>
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui basic segment advanceoptions">
|
||||
<div class="ui accordion advanceSettings">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
@ -437,11 +439,15 @@
|
||||
let optionalFieldsHTML = "";
|
||||
for (const [key, datatype] of Object.entries(data)) {
|
||||
if (datatype == "int"){
|
||||
$("#dnsProviderAPIFields").append(`<div class="ui fluid labeled dnsConfigField input" key="${key}" style="margin-top: 0.2em;">
|
||||
let defaultValue = 10;
|
||||
if (key == "HTTPTimeout"){
|
||||
defaultValue = 300;
|
||||
}
|
||||
$("#dnsProviderAPIFields").append(`<div class="ui fluid labeled dnsConfigField input typeint" key="${key}" style="margin-top: 0.2em;">
|
||||
<div class="ui basic blue label" style="font-weight: 300;">
|
||||
${key}
|
||||
</div>
|
||||
<input type="number" value="300">
|
||||
<input type="number" value="${defaultValue}">
|
||||
</div>`);
|
||||
}else if (datatype == "bool"){
|
||||
booleanFieldsHTML += (`<div class="ui checkbox dnsConfigField" key="${key}" style="margin-top: 1em !important; padding-left: 0.4em;">
|
||||
@ -600,8 +606,12 @@
|
||||
//Boolean option
|
||||
let checked = $(this).find("input")[0].checked;
|
||||
dnsCredentials[thisKey] = checked;
|
||||
}else if ($(this).hasClass("typeint")){
|
||||
//Int options
|
||||
let value = $(this).find("input").val();
|
||||
dnsCredentials[thisKey] = parseInt(value);
|
||||
}else{
|
||||
//String or int options
|
||||
//String options
|
||||
let value = $(this).find("input").val().trim();
|
||||
dnsCredentials[thisKey] = value;
|
||||
}
|
||||
|
@ -10,6 +10,8 @@
|
||||
<script src="../script/utils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
|
@ -10,6 +10,8 @@
|
||||
<script src="../script/utils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
|
@ -10,6 +10,8 @@
|
||||
<script src="../script/utils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
|
@ -10,6 +10,8 @@
|
||||
<script src="../script/utils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
|
@ -23,9 +23,15 @@
|
||||
#permissionPolicyEditor .experimental{
|
||||
background-color: rgb(241, 241, 241);
|
||||
}
|
||||
|
||||
body.darkTheme #permissionPolicyEditor .experimental{
|
||||
background-color: rgb(41, 41, 41);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
@ -86,7 +92,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||
<div class="ui basic segment advanceoptions">
|
||||
<div class="ui fluid accordion">
|
||||
<div class="title">
|
||||
<i class="dropdown icon" tabindex="0"><div class="menu" tabindex="-1"></div></i>
|
||||
|
@ -8,6 +8,8 @@
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br />
|
||||
<div class="ui container">
|
||||
<div class="ui form">
|
||||
|
@ -41,9 +41,28 @@
|
||||
min-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
body.darkTheme #accessRuleList{
|
||||
border: 1px solid rgb(50, 50, 50) !important;
|
||||
|
||||
}
|
||||
|
||||
body.darkTheme .ui.segment.accessRule {
|
||||
border: 1px solid var(--theme_bg_secondary) !important;
|
||||
}
|
||||
|
||||
body.darkTheme .ui.segment.accessRule:hover {
|
||||
background-color: var(--theme_bg_secondary) !important;
|
||||
}
|
||||
|
||||
body.darkTheme .ui.segment.accessRule.active {
|
||||
background-color: var(--theme_bg_secondary) !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
|
@ -8,6 +8,8 @@
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
|
@ -55,10 +55,64 @@
|
||||
background:#3643bb;
|
||||
color:white;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist {
|
||||
background-color: #1b1c1d;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.header .content .sub.header {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.divider {
|
||||
border-color: #333333;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.accordion .title,
|
||||
body.darkTheme .loglist .ui.accordion .content {
|
||||
background-color: #1b1c1d;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.accordion .title:hover {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.list .item {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.list .item .content {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.list .item .showing {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.button.filterbtn {
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.button.filterbtn:hover {
|
||||
background-color: #555555;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist .ui.toggle.checkbox label {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.darkTheme .loglist small {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui stackable grid">
|
||||
|
@ -15,6 +15,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<div class="ui active inverted dimmer">
|
||||
<div class="ui text loader">Loading Snippet</div>
|
||||
</div>
|
||||
|
@ -15,6 +15,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui basic segment">
|
||||
|
@ -15,6 +15,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui basic segment">
|
||||
|
@ -56,6 +56,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
|
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
Color definations
|
||||
Color definition
|
||||
*/
|
||||
:root{
|
||||
--dark_theme_toggle: #333333;
|
||||
@ -1252,6 +1252,10 @@ code{
|
||||
color: var(--um_file_txt) !important;
|
||||
}
|
||||
|
||||
.darkTheme .filename{
|
||||
color:var(--text_color) !important;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Hot Search Related
|
||||
|
@ -32,6 +32,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body class="whiteTheme">
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<div id="navibar" class="navibar">
|
||||
<!-- File Opr Group-->
|
||||
<button class="fileOprBtn" title="Open" onclick="openViaButton(event);"><img class="opricon" src="img/opr/open.svg"><p class="oprtxt" locale="fileopr/Open">Open</p></button>
|
||||
@ -60,7 +62,7 @@
|
||||
<div id="pathDisplayField" class="ui breadcrumb addressText pathDisplay desktopOnly" >
|
||||
|
||||
</div>
|
||||
<button id="togglePropertiesViewBtn" style="margin-left: 0.4em;" class="ui icon tiny button videmode propbar" title="Show Properties" onclick="togglePropertiesView(this);"><i class="columns icon"></i></button>
|
||||
<button id="togglePropertiesViewBtn" style="margin-left: 0.4em; " class="ui icon tiny button videmode propbar" title="Show Properties" onclick="togglePropertiesView(this);"><i class="columns icon"></i></button>
|
||||
</div>
|
||||
|
||||
<div class="msgbox" style="z-index:999; display:none; padding-bottom: 1em;">
|
||||
@ -79,7 +81,7 @@
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div id="propertiesView" class="small">
|
||||
<div id="propertiesView" class="small" style="height: 100%;">
|
||||
<h3 class="ui header" style="margin-top: 0.4em;">
|
||||
<span class="filename" style="word-break: break-all;" locale="sidebar/default/nofileselected">No File Selected</span>
|
||||
<div class="sub header vpath" style="word-break: break-all;" locale="sidebar/default/instruction">Select a file to view file properties</div>
|
||||
@ -471,11 +473,12 @@
|
||||
</span>
|
||||
</div>`);
|
||||
}else{
|
||||
let isDarkTheme = $("body").hasClass("darkTheme");
|
||||
let extension = "." + filename.split(".").pop();
|
||||
let fileIcon = getFileIcon(extension);
|
||||
$("#fileList").append(`<div class="fileObject item" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="file">
|
||||
<span style="display:inline-block !important;word-break: break-all; width:100%;" class="normal object">
|
||||
<i class="${fileIcon} icon" style="margin-right:12px; color:grey;"></i> <span class="filename">${filename} (${humanFileSize(filesize)})</span>
|
||||
<i class="${fileIcon} icon" style="margin-right:12px; color:${isDarkTheme?'white':'grey'} !important;"></i> <span class="filename">${filename} (${humanFileSize(filesize)})</span>
|
||||
</span>
|
||||
</div>`);
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
<link rel="stylesheet" href="../main.css">
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui yellow message">
|
||||
|
@ -21,6 +21,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<div class="ui container">
|
||||
<br>
|
||||
<div class="ui segment">
|
||||
|
@ -22,8 +22,10 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<div id="mdns-hosts">
|
||||
|
||||
<span><i class="ui loading spinner icon"></i> Loading</span>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
|
@ -18,6 +18,8 @@
|
||||
</head>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<div class="ui container">
|
||||
<br>
|
||||
<div class="ui segment">
|
||||
|
@ -33,6 +33,8 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<div id="loadingUI">
|
||||
<div style="margin-top: 2em; margin-left: 2em; color: white;">
|
||||
<i class="ui loading spinner icon"></i> <b>Creating virtual terminal session</b><br>
|
||||
|
@ -18,11 +18,9 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
@ -32,39 +30,6 @@ import (
|
||||
"imuslab.com/zoraxy/mod/wakeonlan"
|
||||
)
|
||||
|
||||
/*
|
||||
Proxy Utils
|
||||
*/
|
||||
//Check if site support TLS
|
||||
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
|
||||
targetURL, err := utils.PostPara(r, "url")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid url given")
|
||||
return
|
||||
}
|
||||
|
||||
httpsUrl := fmt.Sprintf("https://%s", targetURL)
|
||||
httpUrl := fmt.Sprintf("http://%s", targetURL)
|
||||
|
||||
client := http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
resp, err := client.Head(httpsUrl)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
js, _ := json.Marshal("https")
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
}
|
||||
|
||||
resp, err = client.Head(httpUrl)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
js, _ := json.Marshal("http")
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendErrorResponse(w, "invalid url given")
|
||||
}
|
||||
|
||||
/*
|
||||
Statistic Summary
|
||||
*/
|
||||
@ -367,12 +332,22 @@ func HandleZoraxyInfo(w http.ResponseWriter, r *http.Request) {
|
||||
ZerotierConnected bool
|
||||
}
|
||||
|
||||
displayUUID := nodeUUID
|
||||
displayAllowSSHLB := *allowSshLoopback
|
||||
displayBootTime := bootTime
|
||||
|
||||
if !authAgent.CheckAuth(r) {
|
||||
displayUUID = "Unauthorized"
|
||||
displayAllowSSHLB = false
|
||||
displayBootTime = 0
|
||||
}
|
||||
|
||||
info := ZoraxyInfo{
|
||||
Version: version,
|
||||
NodeUUID: nodeUUID,
|
||||
Development: development,
|
||||
BootTime: bootTime,
|
||||
EnableSshLoopback: *allowSshLoopback,
|
||||
Version: SYSTEM_VERSION,
|
||||
NodeUUID: displayUUID,
|
||||
Development: DEVELOPMENT_BUILD,
|
||||
BootTime: displayBootTime,
|
||||
EnableSshLoopback: displayAllowSSHLB,
|
||||
ZerotierConnected: ganManager.ControllerID != "",
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user