Merge pull request #399 from tobychui/v3.1.4

V3.1.4
This commit is contained in:
Toby Chui 2024-11-24 14:47:53 +08:00 committed by GitHub
commit 0eb0696670
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 25809 additions and 22851 deletions

View File

@ -33,6 +33,7 @@ A general purpose HTTP reverse proxy and forwarding tool. Now written in Go!
- Basic single-admin management mode - Basic single-admin management mode
- External permission management system for easy system integration - External permission management system for easy system integration
- SMTP config for password reset - SMTP config for password reset
- Dark Theme Mode
## Downloads ## Downloads
@ -102,6 +103,8 @@ Usage of zoraxy:
Enable auto config upgrade if breaking change is detected (default true) Enable auto config upgrade if breaking change is detected (default true)
-docker -docker
Run Zoraxy in docker compatibility mode Run Zoraxy in docker compatibility mode
-earlyrenew int
Number of days to early renew a soon expiring certificate (days) (default 30)
-fastgeoip -fastgeoip
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices) Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
-mdns -mdns
@ -119,7 +122,7 @@ Usage of zoraxy:
-webfm -webfm
Enable web file manager for static web server root folder (default true) Enable web file manager for static web server root folder (default true)
-webroot string -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 -ztauth string
ZeroTier authtoken for the local node ZeroTier authtoken for the local node
-ztport int -ztport int

View File

@ -230,7 +230,17 @@ func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
return 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) utils.SendOK(w)
} }
@ -254,7 +264,17 @@ func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
return 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) utils.SendOK(w)
} }
@ -397,7 +417,17 @@ func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
p := bluemonday.StrictPolicy() p := bluemonday.StrictPolicy()
comment = p.Sanitize(comment) 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) utils.SendOK(w)
} }
@ -420,7 +450,17 @@ func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
return 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) utils.SendOK(w)
} }

View File

@ -41,6 +41,20 @@ func initACME() *acme.ACMEHandler {
return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb, SystemWideLogger) 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 // create the special routing rule for ACME
func acmeRegisterSpecialRoutingRule() { func acmeRegisterSpecialRoutingRule() {
SystemWideLogger.Println("Assigned temporary port:" + acmeHandler.Getport()) 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 // This function check if the renew setup is satisfied. If not, toggle them automatically
func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) { func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
isForceHttpsRedirectEnabledOriginally := false isForceHttpsRedirectEnabledOriginally := false

View File

@ -8,6 +8,7 @@ import (
"imuslab.com/zoraxy/mod/acme/acmedns" "imuslab.com/zoraxy/mod/acme/acmedns"
"imuslab.com/zoraxy/mod/acme/acmewizard" "imuslab.com/zoraxy/mod/acme/acmewizard"
"imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/ipscan" "imuslab.com/zoraxy/mod/ipscan"
"imuslab.com/zoraxy/mod/netstat" "imuslab.com/zoraxy/mod/netstat"
"imuslab.com/zoraxy/mod/netutils" "imuslab.com/zoraxy/mod/netutils"
@ -18,34 +19,11 @@ import (
API.go API.go
This file contains all the API called by the web management interface This file contains all the API called by the web management interface
*/ */
var requireAuth = true // Register the APIs for HTTP proxy management functions
func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
func initAPIs(targetMux *http.ServeMux) { /* Reverse Proxy Settings & Status */
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
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff) authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint) authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus) 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/setAlias", ReverseProxyHandleAlias)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint) authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials) 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/setIncoming", HandleIncomingPortSet)
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect) authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener) authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck) authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange) 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/list", ReverseProxyUpstreamList)
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd) authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority) authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate) authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete) 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/list", ReverseProxyListVdir)
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir) authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir) authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir)
authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir) 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/list", HandleCustomHeaderList)
authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd) authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd)
authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove) 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/handleHopByHop", HandleHopByHop)
authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite) authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite)
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy) 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/list", ListProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths) authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths) 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/tls", handleToggleTLSProxy)
authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest) authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
authRouter.HandleFunc("/api/cert/upload", handleCertUpload) 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/listdomains", handleListDomains)
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck) authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
authRouter.HandleFunc("/api/cert/delete", handleCertRemove) 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/status", ssoHandler.HandleSSOStatus)
authRouter.HandleFunc("/api/sso/enable", ssoHandler.HandleSSOEnable) authRouter.HandleFunc("/api/sso/enable", ssoHandler.HandleSSOEnable)
authRouter.HandleFunc("/api/sso/setPort", ssoHandler.HandlePortChange) 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/add", ssoHandler.HandleAddUser)
authRouter.HandleFunc("/api/sso/user/edit", ssoHandler.HandleEditUser) authRouter.HandleFunc("/api/sso/user/edit", ssoHandler.HandleEditUser)
authRouter.HandleFunc("/api/sso/user/remove", ssoHandler.HandleRemoveUser) 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/list", handleListRedirectionRules)
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule) authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule) authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport) 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/list", handleListAccessRules)
authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost) authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost)
authRouter.HandleFunc("/api/access/create", handleCreateAccessRule) authRouter.HandleFunc("/api/access/create", handleCreateAccessRule)
authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule) authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule)
authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule) authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule)
//Blacklist APIs /* Blacklist */
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted) authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd) authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove) authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd) authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove) authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable) authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
//Whitelist APIs /* Whitelist */
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted) authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd) authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove) authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove)
authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd) authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd)
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove) authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable) 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/add", pathRuleHandler.HandleAddBlockingPath)
authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath) authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath)
authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath) 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/summary", statisticCollector.HandleTodayStatLoad)
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary) authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats) authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats)
authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats) authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats)
authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces) 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) 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/info", ganManager.HandleGetNodeID)
authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork) authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork) 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/name", ganManager.HandleMemberNaming)
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization) authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete) 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/add", streamProxyManager.HandleAddProxyConfig)
authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs) authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs)
authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs) 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/stop", streamProxyManager.HandleStopProxy)
authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy) authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy)
authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus) 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/list", HandleMdnsListing)
authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning) authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning)
}
//Zoraxy Analytic // Register the APIs for ACME and Auto Renewer management functions
authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList) func RegisterACMEAndAutoRenewerAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary) /* ACME Core */
authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary) authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains)
authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport) authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate)
authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset) /* 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/ipscan", ipscan.HandleIpScan)
authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort) authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort)
authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute) 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/smtp/test", HandleTestEmailSend)
authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle) authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle)
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort) 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 // Register the APIs for Auth functions, due to scoping issue some functions are defined here
func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) { func RegisterAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
//Auth APIs
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin) targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout) targetMux.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
targetMux.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) { 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) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return return
} }
js, _ := json.Marshal(username) js, _ := json.Marshal(username)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
}) })
targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) { targetMux.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
uc := authAgent.GetUserCounts() js, _ := json.Marshal(authAgent.GetUserCounts())
js, _ := json.Marshal(uc)
utils.SendJSONResponse(w, string(js)) utils.SendJSONResponse(w, string(js))
}) })
targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) { targetMux.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
if authAgent.GetUserCounts() == 0 { if authAgent.GetUserCounts() == 0 {
//Allow register root admin //Allow register root admin
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) { authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {})
})
} else { } else {
//This function is disabled //This function is disabled
utils.SendErrorResponse(w, "Root management account already exists") utils.SendErrorResponse(w, "Root management account already exists")
@ -332,5 +313,60 @@ func registerAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
authAgent.UnregisterUser(username) authAgent.UnregisterUser(username)
authAgent.CreateUserAccount(username, newPassword, "") 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)
} }

View File

@ -177,7 +177,10 @@ func handleListDomains(w http.ResponseWriter, r *http.Request) {
// Handle front-end toggling TLS mode // Handle front-end toggling TLS mode
func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) { 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") { if sysdb.KeyExists("settings", "usetls") {
sysdb.Read("settings", "usetls", &currentTlsSetting) sysdb.Read("settings", "usetls", &currentTlsSetting)
} }

138
src/def.go Normal file
View 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
)

View File

@ -1,7 +1,36 @@
package main 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 ( import (
"embed"
"flag" "flag"
"fmt" "fmt"
"log" "log"
@ -13,100 +42,12 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/csrf" "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/update"
"imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/utils" "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 ( /* SIGTERM handler, do shutdown sequences before closing */
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.
func SetupCloseHandler() { func SetupCloseHandler() {
c := make(chan os.Signal, 2) c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -118,9 +59,7 @@ func SetupCloseHandler() {
} }
func ShutdownSeq() { func ShutdownSeq() {
SystemWideLogger.Println("Shutting down " + name) SystemWideLogger.Println("Shutting down " + SYSTEM_NAME)
//SystemWideLogger.Println("Closing GeoDB")
//geodbStore.Close()
SystemWideLogger.Println("Closing Netstats Listener") SystemWideLogger.Println("Closing Netstats Listener")
netstatBuffers.Close() netstatBuffers.Close()
SystemWideLogger.Println("Closing Statistic Collector") SystemWideLogger.Println("Closing Statistic Collector")
@ -152,7 +91,7 @@ func main() {
//Parse startup flags //Parse startup flags
flag.Parse() flag.Parse()
if *showver { if *showver {
fmt.Println(name + " - Version " + version) fmt.Println(SYSTEM_NAME + " - Version " + SYSTEM_VERSION)
os.Exit(0) os.Exit(0)
} }
@ -163,7 +102,7 @@ func main() {
if *enableAutoUpdate { if *enableAutoUpdate {
fmt.Println("Checking required config update") fmt.Println("Checking required config update")
update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(version)) update.RunConfigUpdate(0, update.GetVersionIntFromVersionNumber(SYSTEM_VERSION))
} }
SetupCloseHandler() SetupCloseHandler()
@ -185,7 +124,7 @@ func main() {
webminPanelMux = http.NewServeMux() webminPanelMux = http.NewServeMux()
csrfMiddleware = csrf.Protect( csrfMiddleware = csrf.Protect(
[]byte(nodeUUID), []byte(nodeUUID),
csrf.CookieName("zoraxy-csrf"), csrf.CookieName(CSRF_COOKIENAME),
csrf.Secure(false), csrf.Secure(false),
csrf.Path("/"), csrf.Path("/"),
csrf.SameSite(csrf.SameSiteLaxMode), csrf.SameSite(csrf.SameSiteLaxMode),
@ -208,11 +147,10 @@ func main() {
//Start the finalize sequences //Start the finalize sequences
finalSequence() 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)) err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -86,6 +86,13 @@ func (a *ACMEHandler) Logf(message string, err error) {
a.Logger.PrintAndLog("ACME", message, err) 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. // 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) { 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) a.Logf("Obtaining certificate for: "+strings.Join(domains, ", "), nil)

View File

@ -308,7 +308,6 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
} }
if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) { if CertExpireSoon(certBytes, a.EarlyRenewDays) || CertIsExpired(certBytes) {
//This cert is expired //This cert is expired
DNSName, err := ExtractDomains(certBytes) DNSName, err := ExtractDomains(certBytes)
if err != nil { if err != nil {
//Maybe self signed. Ignore this //Maybe self signed. Ignore this
@ -355,6 +354,7 @@ func (a *AutoRenewer) CheckAndRenewCertificates() ([]string, error) {
return a.renewExpiredDomains(expiredCertList) return a.renewExpiredDomains(expiredCertList)
} }
// Close the auto renewer
func (a *AutoRenewer) Close() { func (a *AutoRenewer) Close() {
if a.TickerstopChan != nil { if a.TickerstopChan != nil {
a.TickerstopChan <- true a.TickerstopChan <- true
@ -440,7 +440,7 @@ func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
} }
// Handle update auto renew DNS configuration // 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") dnsProvider, err := utils.PostPara(r, "dnsProvider")
if err != nil { if err != nil {
utils.SendErrorResponse(w, "dnsProvider not set") utils.SendErrorResponse(w, "dnsProvider not set")

View File

@ -3,7 +3,7 @@ package acme
/* /*
CA.go CA.go
This script load CA defination from embedded ca.json This script load CA definition from embedded ca.json
*/ */
import ( import (
_ "embed" _ "embed"
@ -13,7 +13,7 @@ import (
"strings" "strings"
) )
// CA Defination, load from embeded json when startup // CA definition, load from embeded json when startup
type CaDef struct { type CaDef struct {
Production map[string]string Production map[string]string
Test map[string]string Test map[string]string

View File

@ -210,8 +210,8 @@ func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
} }
session.Values["authenticated"] = false session.Values["authenticated"] = false
session.Values["username"] = nil session.Values["username"] = nil
session.Save(r, w) session.Options.MaxAge = -1
return nil return session.Save(r, w)
} }
// Get the current session username from request // Get the current session username from request
@ -339,6 +339,7 @@ func (a *AuthAgent) CheckAuth(r *http.Request) bool {
if err != nil { if err != nil {
return false return false
} }
// Check if user is authenticated // Check if user is authenticated
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
return false return false

View File

@ -1,7 +1,6 @@
package dynamicproxy package dynamicproxy
import ( import (
"log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -16,7 +15,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID) accessRule, err := h.Parent.Option.AccessController.GetAccessRuleByID(ruleID)
if err != nil { if err != nil {
//Unable to load access rule. Target rule not found? //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.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Internal Server Error")) w.Write([]byte("500 - Internal Server Error"))
return true return true

View File

@ -9,8 +9,15 @@ package domainsniff
*/ */
import ( import (
"crypto/tls"
"encoding/json"
"fmt"
"net" "net"
"net/http"
"strings"
"time" "time"
"imuslab.com/zoraxy/mod/utils"
) )
// Check if the domain is reachable and return err if not reachable // Check if the domain is reachable and return err if not reachable
@ -25,7 +32,115 @@ func DomainReachableWithError(domain string) error {
return nil 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 // Check if domain reachable
func DomainReachable(domain string) bool { func DomainReachable(domain string) bool {
return DomainReachableWithError(domain) == nil 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
}
}

View File

@ -109,6 +109,8 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2 thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).DisableCompression = true thisTransporter.(*http.Transport).DisableCompression = true
//TODO: Add user adjustable timeout option here
if dpcOptions.IgnoreTLSVerification { if dpcOptions.IgnoreTLSVerification {
//Ignore TLS certificate validation error //Ignore TLS certificate validation error
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true

View File

@ -291,7 +291,7 @@ func (router *Router) Restart() error {
return err return err
} }
time.Sleep(300 * time.Millisecond) time.Sleep(800 * time.Millisecond)
// Start the server // Start the server
err = router.StartProxyService() err = router.StartProxyService()
if err != nil { if err != nil {

View File

@ -3,6 +3,7 @@ package geodb
import ( import (
_ "embed" _ "embed"
"net/http" "net/http"
"time"
"imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/netutils" "imuslab.com/zoraxy/mod/netutils"
@ -15,17 +16,22 @@ var geoipv4 []byte //Geodb dataset for ipv4
var geoipv6 []byte //Geodb dataset for ipv6 var geoipv6 []byte //Geodb dataset for ipv6
type Store struct { type Store struct {
geodb [][]string //Parsed geodb list geodb [][]string //Parsed geodb list
geodbIpv6 [][]string //Parsed geodb list for ipv6 geodbIpv6 [][]string //Parsed geodb list for ipv6
geotrie *trie geotrie *trie
geotrieIpv6 *trie geotrieIpv6 *trie
sysdb *database.Database sysdb *database.Database
option *StoreOptions 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 { type StoreOptions struct {
AllowSlowIpv4LookUp bool AllowSlowIpv4LookUp bool
AllowSloeIpv6Lookup bool AllowSlowIpv6Lookup bool
SlowLookupCacheClearInterval time.Duration //Clear slow lookup cache interval
} }
type CountryInfo struct { type CountryInfo struct {
@ -50,18 +56,44 @@ func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) {
} }
var ipv6Trie *trie var ipv6Trie *trie
if !option.AllowSloeIpv6Lookup { if !option.AllowSlowIpv6Lookup {
ipv6Trie = constrctTrieTree(parsedGeoDataIpv6) ipv6Trie = constrctTrieTree(parsedGeoDataIpv6)
} }
return &Store{ if option.SlowLookupCacheClearInterval == 0 {
geodb: parsedGeoData, option.SlowLookupCacheClearInterval = 15 * time.Minute
geotrie: ipv4Trie, }
geodbIpv6: parsedGeoDataIpv6,
geotrieIpv6: ipv6Trie, //Create a new store
sysdb: sysdb, thisGeoDBStore := &Store{
option: option, geodb: parsedGeoData,
}, nil 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) { 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() { 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 { func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {

View File

@ -44,6 +44,7 @@ func TestResolveCountryCodeFromIP(t *testing.T) {
store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{ store, err := geodb.NewGeoDb(nil, &geodb.StoreOptions{
false, false,
true, true,
0,
}) })
if err != nil { if err != nil {
t.Errorf("error creating store: %v", err) 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

View File

@ -56,6 +56,12 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
if isReservedIP(ipAddr) { if isReservedIP(ipAddr) {
return "" return ""
} }
//Check if already in cache
if cc, ok := s.slowLookupCacheIpv4[ipAddr]; ok {
return cc
}
for _, ipRange := range s.geodb { for _, ipRange := range s.geodb {
startIp := ipRange[0] startIp := ipRange[0]
endIp := ipRange[1] endIp := ipRange[1]
@ -63,6 +69,8 @@ func (s *Store) slowSearchIpv4(ipAddr string) string {
inRange, _ := isIPv4InRange(startIp, endIp, ipAddr) inRange, _ := isIPv4InRange(startIp, endIp, ipAddr)
if inRange { if inRange {
//Add to cache
s.slowLookupCacheIpv4[ipAddr] = cc
return cc return cc
} }
} }
@ -73,6 +81,12 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
if isReservedIP(ipAddr) { if isReservedIP(ipAddr) {
return "" return ""
} }
//Check if already in cache
if cc, ok := s.slowLookupCacheIpv6[ipAddr]; ok {
return cc
}
for _, ipRange := range s.geodbIpv6 { for _, ipRange := range s.geodbIpv6 {
startIp := ipRange[0] startIp := ipRange[0]
endIp := ipRange[1] endIp := ipRange[1]
@ -80,6 +94,8 @@ func (s *Store) slowSearchIpv6(ipAddr string) string {
inRange, _ := isIPv6InRange(startIp, endIp, ipAddr) inRange, _ := isIPv6InRange(startIp, endIp, ipAddr)
if inRange { if inRange {
//Add to cache
s.slowLookupCacheIpv6[ipAddr] = cc
return cc return cc
} }
} }

View File

@ -169,9 +169,16 @@ func (n *NetStatBuffers) HandleGetBufferedNetworkInterfaceStats(w http.ResponseW
} }
func (n *NetStatBuffers) Close() { func (n *NetStatBuffers) Close() {
n.StopChan <- true //Fixed issue #394 for stopping netstat listener on platforms not supported platforms
time.Sleep(300 * time.Millisecond) if n.StopChan != nil {
n.EventTicker.Stop() 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) { 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") allIfaceRxByteFiles, err := filepath.Glob("/sys/class/net/*/statistics/rx_bytes")
if err != nil { if err != nil {
//Permission denied //Permission denied
return 0, 0, errors.New("Access denied") return 0, 0, errors.New("access denied")
} }
if len(allIfaceRxByteFiles) == 0 { 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) 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, nil //no ethernet adapters with en*/<Link#*>
} }
return 0, 0, errors.New("Platform not supported") return 0, 0, errors.New("platform not supported")
} }

View File

@ -1,7 +1,7 @@
package v308 package v308
/* /*
v307 type definations v307 type definitions
This file wrap up the self-contained data structure This file wrap up the self-contained data structure
for v3.0.7 structure and allow automatic updates for v3.0.7 structure and allow automatic updates

View File

@ -1,7 +1,7 @@
package v308 package v308
/* /*
v308 type definations v308 type definition
This file wrap up the self-contained data structure This file wrap up the self-contained data structure
for v3.0.8 structure and allow automatic updates for v3.0.8 structure and allow automatic updates

View File

@ -27,18 +27,18 @@ func ReverseProxtInit() {
/* /*
Load Reverse Proxy Global Settings Load Reverse Proxy Global Settings
*/ */
inboundPort := 80 inboundPort := 443
if sysdb.KeyExists("settings", "inbound") { if sysdb.KeyExists("settings", "inbound") {
sysdb.Read("settings", "inbound", &inboundPort) sysdb.Read("settings", "inbound", &inboundPort)
SystemWideLogger.Println("Serving inbound port ", inboundPort) SystemWideLogger.Println("Serving inbound port ", inboundPort)
} else { } 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) sysdb.Read("settings", "usetls", &useTls)
if useTls { if useTls {
SystemWideLogger.Println("TLS mode enabled. Serving proxxy request with TLS") SystemWideLogger.Println("TLS mode enabled. Serving proxy request with TLS")
} else { } else {
SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http") 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") SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
} }
listenOnPort80 := false listenOnPort80 := true
sysdb.Read("settings", "listenP80", &listenOnPort80) sysdb.Read("settings", "listenP80", &listenOnPort80)
if listenOnPort80 { if listenOnPort80 {
SystemWideLogger.Println("Port 80 listener enabled") SystemWideLogger.Println("Port 80 listener enabled")
@ -67,7 +67,7 @@ func ReverseProxtInit() {
SystemWideLogger.Println("Port 80 listener disabled") SystemWideLogger.Println("Port 80 listener disabled")
} }
forceHttpsRedirect := false forceHttpsRedirect := true
sysdb.Read("settings", "redirect", &forceHttpsRedirect) sysdb.Read("settings", "redirect", &forceHttpsRedirect)
if forceHttpsRedirect { if forceHttpsRedirect {
SystemWideLogger.Println("Force HTTPS mode enabled") SystemWideLogger.Println("Force HTTPS mode enabled")
@ -85,7 +85,7 @@ func ReverseProxtInit() {
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{ dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
HostUUID: nodeUUID, HostUUID: nodeUUID,
HostVersion: version, HostVersion: SYSTEM_VERSION,
Port: inboundPort, Port: inboundPort,
UseTls: useTls, UseTls: useTls,
ForceTLSLatest: forceLatestTLSVersion, ForceTLSLatest: forceLatestTLSVersion,
@ -1085,6 +1085,7 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
if dynamicProxyRouter.Running { if dynamicProxyRouter.Running {
dynamicProxyRouter.StopProxyService() dynamicProxyRouter.StopProxyService()
dynamicProxyRouter.Option.Port = newIncomingPortInt dynamicProxyRouter.Option.Port = newIncomingPortInt
time.Sleep(1 * time.Second) //Fixed start fail issue
dynamicProxyRouter.StartProxyService() dynamicProxyRouter.StartProxyService()
} else { } else {
//Only change setting but not starting the proxy service //Only change setting but not starting the proxy service
@ -1173,7 +1174,7 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
return return
} }
//Create a Custom Header Defination type //Create a Custom Header Definition type
var rewriteDirection rewrite.HeaderDirection var rewriteDirection rewrite.HeaderDirection
if direction == "toOrigin" { if direction == "toOrigin" {
rewriteDirection = rewrite.HeaderDirection_ZoraxyToUpstream rewriteDirection = rewrite.HeaderDirection_ZoraxyToUpstream

View File

@ -27,7 +27,7 @@ func FSHandler(handler http.Handler) http.Handler {
Development Mode Override Development Mode Override
=> Web root is located in / => 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")) u, _ := url.Parse(strings.TrimPrefix(r.URL.Path, "/web"))
r.URL = u r.URL = u
} }
@ -36,7 +36,7 @@ func FSHandler(handler http.Handler) http.Handler {
Production Mode Override Production Mode Override
=> Web root is located in /web => Web root is located in /web
*/ */
if !development && r.URL.Path == "/" { if !DEVELOPMENT_BUILD && r.URL.Path == "/" {
//Redirect to web UI //Redirect to web UI
http.Redirect(w, r, "/web/", http.StatusTemporaryRedirect) http.Redirect(w, r, "/web/", http.StatusTemporaryRedirect)
return return
@ -93,7 +93,7 @@ func FSHandler(handler http.Handler) http.Handler {
// Production path fix wrapper. Fix the path on production or development environment // Production path fix wrapper. Fix the path on production or development environment
func ppf(relativeFilepath string) string { func ppf(relativeFilepath string) string {
if !development { if !DEVELOPMENT_BUILD {
return strings.ReplaceAll(filepath.Join("/web/", relativeFilepath), "\\", "/") return strings.ReplaceAll(filepath.Join("/web/", relativeFilepath), "\\", "/")
} }
return 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:] == "/" { if len(relativeFilepath) > 0 && relativeFilepath[len(relativeFilepath)-1:] == "/" {
relativeFilepath = relativeFilepath + "index.html" relativeFilepath = relativeFilepath + "index.html"
} }
if development { if DEVELOPMENT_BUILD {
//Load from disk //Load from disk
targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/") targetFilePath := strings.ReplaceAll(filepath.Join("web/", relativeFilepath), "\\", "/")
content, err = os.ReadFile(targetFilePath) content, err = os.ReadFile(targetFilePath)

View File

@ -52,19 +52,19 @@ var (
func startupSequence() { func startupSequence() {
//Start a system wide logger and log viewer //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 { if err == nil {
SystemWideLogger = l SystemWideLogger = l
} else { } else {
panic(err) panic(err)
} }
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{ LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
RootFolder: "./log", RootFolder: LOG_FOLDER,
Extension: ".log", Extension: LOG_EXTENSION,
}) })
//Create database //Create database
db, err := database.NewDatabase("sys.db", false) db, err := database.NewDatabase(DATABASE_PATH, false)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -73,21 +73,21 @@ func startupSequence() {
sysdb.NewTable("settings") sysdb.NewTable("settings")
//Create tmp folder and conf folder //Create tmp folder and conf folder
os.MkdirAll("./tmp", 0775) os.MkdirAll(TMP_FOLDER, 0775)
os.MkdirAll("./conf/proxy/", 0775) os.MkdirAll(CONF_HTTP_PROXY, 0775)
//Create an auth agent //Create an auth agent
sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger) sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger)
if err != nil { if err != nil {
log.Fatal(err) 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 //Not logged in. Redirecting to login page
http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect) http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
}) })
//Create a TLS certificate manager //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 { if err != nil {
panic(err) panic(err)
} }
@ -96,15 +96,16 @@ func startupSequence() {
db.NewTable("redirect") db.NewTable("redirect")
redirectAllowRegexp := false redirectAllowRegexp := false
db.Read("redirect", "regex", &redirectAllowRegexp) db.Read("redirect", "regex", &redirectAllowRegexp)
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp, SystemWideLogger) redirectTable, err = redirection.NewRuleTable(CONF_REDIRECTION, redirectAllowRegexp, SystemWideLogger)
if err != nil { if err != nil {
panic(err) panic(err)
} }
//Create a geodb store //Create a geodb store
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{ geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup, AllowSlowIpv4LookUp: !*enableHighSpeedGeoIPLookup,
AllowSloeIpv6Lookup: !*enableHighSpeedGeoIPLookup, AllowSlowIpv6Lookup: !*enableHighSpeedGeoIPLookup,
SlowLookupCacheClearInterval: GEODB_CACHE_CLEAR_INTERVAL * time.Minute,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -121,7 +122,7 @@ func startupSequence() {
accessController, err = access.NewAccessController(&access.Options{ accessController, err = access.NewAccessController(&access.Options{
Database: sysdb, Database: sysdb,
GeoDB: geodbStore, GeoDB: geodbStore,
ConfigFolder: "./conf/access", ConfigFolder: CONF_ACCESS_RULE,
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -154,7 +155,7 @@ func startupSequence() {
//Start the static web server //Start the static web server
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{ staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
Sysdb: sysdb, Sysdb: sysdb,
Port: "5487", //Default Port Port: strconv.Itoa(WEBSERV_DEFAULT_PORT), //Default Port
WebRoot: *staticWebServerRoot, WebRoot: *staticWebServerRoot,
EnableDirectoryListing: true, EnableDirectoryListing: true,
EnableWebDirManager: *allowWebFileManager, EnableWebDirManager: *allowWebFileManager,
@ -179,7 +180,7 @@ func startupSequence() {
pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{ pathRuleHandler = pathrule.NewPathRuleHandler(&pathrule.Options{
Enabled: false, Enabled: false,
ConfigFolder: "./conf/rules/pathrules", ConfigFolder: CONF_PATH_RULE,
}) })
/* /*
@ -197,7 +198,7 @@ func startupSequence() {
hostName := *mdnsName hostName := *mdnsName
if hostName == "" { if hostName == "" {
hostName = "zoraxy_" + nodeUUID hostName = MDNS_HOSTNAME_PREFIX + nodeUUID
} else { } else {
//Trim off the suffix //Trim off the suffix
hostName = strings.TrimSuffix(hostName, ".local") hostName = strings.TrimSuffix(hostName, ".local")
@ -206,24 +207,24 @@ func startupSequence() {
mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{ mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{
HostName: hostName, HostName: hostName,
Port: portInt, Port: portInt,
Domain: "zoraxy.aroz.org", Domain: MDNS_IDENTIFY_DOMAIN,
Model: "Network Gateway", Model: MDNS_IDENTIFY_DEVICE_TYPE,
UUID: nodeUUID, UUID: nodeUUID,
Vendor: "imuslab.com", Vendor: MDNS_IDENTIFY_VENDOR,
BuildVersion: version, BuildVersion: SYSTEM_VERSION,
}, "") }, "")
if err != nil { if err != nil {
SystemWideLogger.Println("Unable to startup mDNS service. Disabling mDNS services") SystemWideLogger.Println("Unable to startup mDNS service. Disabling mDNS services")
} else { } else {
//Start initial scanning //Start initial scanning
go func() { go func() {
hosts := mdnsScanner.Scan(30, "") hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
previousmdnsScanResults = hosts previousmdnsScanResults = hosts
SystemWideLogger.Println("mDNS Startup scan completed") SystemWideLogger.Println("mDNS Startup scan completed")
}() }()
//Create a ticker to update mDNS results every 5 minutes //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) stopChan := make(chan bool)
go func() { go func() {
for { for {
@ -231,7 +232,7 @@ func startupSequence() {
case <-stopChan: case <-stopChan:
ticker.Stop() ticker.Stop()
case <-ticker.C: case <-ticker.C:
hosts := mdnsScanner.Scan(30, "") hosts := mdnsScanner.Scan(MDNS_SCAN_TIMEOUT, "")
previousmdnsScanResults = hosts previousmdnsScanResults = hosts
SystemWideLogger.Println("mDNS scan result updated") SystemWideLogger.Println("mDNS scan result updated")
} }
@ -265,7 +266,7 @@ func startupSequence() {
//Create TCP Proxy Manager //Create TCP Proxy Manager
streamProxyManager, err = streamproxy.NewStreamProxy(&streamproxy.Options{ streamProxyManager, err = streamproxy.NewStreamProxy(&streamproxy.Options{
AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess, AccessControlHandler: accessController.DefaultAccessRule.AllowConnectionAccess,
ConfigStore: "./conf/streamproxy", ConfigStore: CONF_STREAM_PROXY,
Logger: SystemWideLogger, Logger: SystemWideLogger,
}) })
if err != nil { if err != nil {
@ -303,8 +304,8 @@ func startupSequence() {
sysdb.NewTable("acmepref") sysdb.NewTable("acmepref")
acmeHandler = initACME() acmeHandler = initACME()
acmeAutoRenewer, err = acme.NewAutoRenewer( acmeAutoRenewer, err = acme.NewAutoRenewer(
"./conf/acme_conf.json", ACME_AUTORENEW_CONFIG_PATH,
"./conf/certs/", CONF_CERT_STORE,
int64(*acmeAutoRenewInterval), int64(*acmeAutoRenewInterval),
*acmeCertAutoRenewDays, *acmeCertAutoRenewDays,
acmeHandler, acmeHandler,

View File

@ -841,6 +841,25 @@
function initBannedCountryList(){ function initBannedCountryList(){
$.get("/api/blacklist/list?type=country&id=" + currentEditingAccessRule, function(data) { $.get("/api/blacklist/list?type=country&id=" + currentEditingAccessRule, function(data) {
let bannedListHtml = ''; 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) => { data.forEach((countryCode) => {
bannedListHtml += ` bannedListHtml += `
<tr> <tr>
@ -919,18 +938,48 @@
//Whitelist country table //Whitelist country table
function initWhitelistCountryList(){ function initWhitelistCountryList(){
$.get("/api/whitelist/list?type=country&id=" + currentEditingAccessRule, function(data) { $.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) => { data.forEach((countryWhitelistEntry) => {
let countryCode = countryWhitelistEntry.CC; let countryCode = countryWhitelistEntry.CC;
bannedListHtml += ` whiteListHTML += `
<tr> <tr>
<td><i class="${countryCode} flag"></i> ${getCountryName(countryCode)} (${countryCode.toUpperCase()})</td> <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> <td><button class="ui red basic mini icon button" onclick="removeFromWhiteList('${countryCode}')"><i class="trash icon"></i></button></td>
</tr> </tr>
`; `;
}); });
$('#whitelistCountryList').html(bannedListHtml); $('#whitelistCountryList').html(whiteListHTML);
filterCountries(data, "#countrySelectorWhitelist .menu .item");
//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) { if (data.length === 0) {
$('#whitelistCountryList').append(` $('#whitelistCountryList').append(`
<tr> <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() { function addCountryToBlacklist() {
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase(); var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
let ccs = [countryCode]; let ccs = [countryCode];
@ -1025,48 +1078,50 @@
ccs = countryCode.split(","); ccs = countryCode.split(",");
} }
let counter = 0; //If the ccs includes "eu", remove the "eu" and add all eu country code to the list
for(var i = 0; i < ccs.length; i++){ if (ccs.includes("eu")){
let thisCountryCode = ccs[i]; ccs = ccs.concat(getEUCCs());
$.cjax({ ccs = ccs.filter(function(item){
type: "POST", return item != "eu";
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
}
}); });
} }
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'); $('#countrySelector').dropdown('clear');
} }
function removeFromBannedList(countryCode){ function removeFromBannedList(countryCode){
countryCode = countryCode.toLowerCase();
let countryName = getCountryName(countryCode); let countryName = getCountryName(countryCode);
if (countryCode == "eu"){
let euCountries = getEUCCs();
countryCode = euCountries.join(",");
countryName = "European Union";
}else{
countryCode = countryCode.toLowerCase();
}
$.cjax({ $.cjax({
url: "/api/blacklist/country/remove", url: "/api/blacklist/country/remove",
method: "POST", method: "POST",
@ -1162,44 +1217,53 @@
//Usually just a few countries a for loop will get the job done //Usually just a few countries a for loop will get the job done
ccs = countryCode.split(","); 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)){ //If the ccs includes "eu", remove the "eu" and add all eu country code to the list
setTimeout(function(){ if (ccs.includes("eu")){
initWhitelistCountryList(); ccs = ccs.filter(function(item){
if (ccs.length == 1){ return item != "eu";
//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
}
}); });
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'); $('#countrySelectorWhitelist').dropdown('clear');
} }
function removeFromWhiteList(countryCode){ //Remove from whitelist, accepts a country code or "eu" for all EU countries
if (confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){ 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(); countryCode = countryCode.toLowerCase();
}
if (skipConfirm || confirm("Confirm removing " + getCountryName(countryCode) + " from whitelist?")){
$.cjax({ $.cjax({
url: "/api/whitelist/country/remove", url: "/api/whitelist/country/remove",
method: "POST", method: "POST",
@ -1208,6 +1272,7 @@
if (response.error != undefined){ if (response.error != undefined){
msgbox(response.error, false); msgbox(response.error, false);
} }
msgbox(countryName + " removed from whitelist");
initWhitelistCountryList(); initWhitelistCountryList();
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
@ -1276,19 +1341,27 @@
/* /*
Common Utilities Common Utilities
*/ */
function filterCountries(codesToShow, selector="#countrySelector .menu .item") { function filterCountries(alreadySelectedCCs, selector="#countrySelector .menu .item") {
// get all items in the dropdown // get all items in the dropdown
const items = document.querySelectorAll(selector); 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 // loop through all items
items.forEach(item => { items.forEach(item => {
// get the value of the item (i.e. the country code) // get the value of the item (i.e. the country code)
const code = item.dataset.value; const code = item.dataset.value;
// if the code is in the array of codes to show, show the item if (alreadySelectedCCs.includes(code)) {
if (codesToShow.includes(code)) { //This country code already selected. Hide it
item.style.display = 'none'; item.style.display = 'none';
} } else {
// otherwise, hide the item // otherwise, show the item
else {
item.style.display = 'block'; item.style.display = 'block';
} }
}); });

View File

@ -101,7 +101,7 @@
<p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p> <p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
<div class="ui buttons"> <div class="ui buttons">
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button> <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> </div>
<div class="ui divider"></div> <div class="ui divider"></div>

View File

@ -30,7 +30,7 @@
<i class="ui green checkmark icon"></i> Redirection Rule Deleted <i class="ui green checkmark icon"></i> Redirection Rule Deleted
</div> </div>
<!-- Options --> <!-- 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="ui accordion advanceSettings">
<div class="title"> <div class="title">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
@ -173,7 +173,7 @@
}); });
if (data.length == 0){ 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>`);
} }
}); });

View File

@ -50,7 +50,7 @@
</div> </div>
</div> </div>
<!-- Advance configs --> <!-- 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 id="advanceProxyRules" class="ui fluid accordion">
<div class="title"> <div class="title">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
@ -295,15 +295,25 @@
//Automatic check if the site require TLS and check the checkbox if needed //Automatic check if the site require TLS and check the checkbox if needed
function autoCheckTls(targetDomain){ function autoCheckTls(targetDomain){
$.cjax({ $.cjax({
url: "/api/proxy/tlscheck", url: "/api/proxy/tlscheck?selfsignchk=true",
data: {url: targetDomain}, data: {url: targetDomain},
success: function(data){ success: function(data){
if (data.error != undefined){ if (data.error != undefined){
msgbox(data.error, false); msgbox(data.error, false);
}else if (data == "https"){ }else{
$("#reqTls").parent().checkbox("set checked"); //Check if the site require TLS
}else if (data == "http"){ if (data.protocol == "https"){
$("#reqTls").parent().checkbox("set unchecked"); $("#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");
}
} }
} }
}) })

View File

@ -73,28 +73,30 @@
<p>Inbound Port (Reverse Proxy Listening Port)</p> <p>Inbound Port (Reverse Proxy Listening Port)</p>
<div class="ui action fluid notloopbackOnly input" tourstep="incomingPort"> <div class="ui action fluid notloopbackOnly input" tourstep="incomingPort">
<small id="applyButtonReminder">Click "Apply" button to confirm listening port changes</small> <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> <button class="ui green notloopbackOnly button" style="background: linear-gradient(60deg, #27e7ff, #00ca52);" onclick="handlePortChange();"><i class="ui checkmark icon"></i> Apply</button>
</div> </div>
<br> <br>
<div id="tls" class="ui toggle notloopbackOnly checkbox"> <div id="tls" class="ui toggle notloopbackOnly checkbox">
<input type="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> </div>
<br> <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"> <input type="checkbox">
<label>Enable HTTP server on port 80<br> <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> </div>
<br> <br>
<div tourstep="forceHttpsRedirect" style="display: inline-block;"> <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"> <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> </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="ui accordion advanceSettings">
<div class="title"> <div class="title">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
@ -359,10 +361,10 @@
return; return;
} }
if (enabled){ if (enabled){
$("#redirect").show(); //$("#redirect").show();
msgbox("Port 80 listener enabled"); msgbox("Port 80 listener enabled");
}else{ }else{
$("#redirect").hide(); //$("#redirect").hide();
msgbox("Port 80 listener disabled"); msgbox("Port 80 listener disabled");
} }
} }
@ -400,10 +402,10 @@
$.get("/api/proxy/listenPort80", function(data){ $.get("/api/proxy/listenPort80", function(data){
if (data){ if (data){
$("#listenP80").checkbox("set checked"); $("#listenP80").checkbox("set checked");
$("#redirect").show(); //$("#redirect").show();
}else{ }else{
$("#listenP80").checkbox("set unchecked"); $("#listenP80").checkbox("set unchecked");
$("#redirect").hide(); //$("#redirect").hide();
} }
$("#listenP80").find("input").on("change", function(){ $("#listenP80").find("input").on("change", function(){
@ -579,7 +581,7 @@
let timestamps = []; let timestamps = [];
for(var i = 0; i < dataCount; i++){ 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() { function fetchData() {
@ -600,10 +602,8 @@
txValues.shift(); txValues.shift();
} }
timestamps.push(new Date(Date.now()).toLocaleString().replace(',', ''));
timestamps.push(parseInt(Date.now() / 1000));
timestamps.shift(); timestamps.shift();
updateChart(); updateChart();
} }
}) })

View File

@ -70,7 +70,7 @@
initUptimeTable(); initUptimeTable();
function reloadUptimeList(){ 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 active inverted dimmer" style="z-index: 2;">
<div class="ui text loader">Loading</div> <div class="ui text loader">Loading</div>
</div> </div>

View File

@ -69,7 +69,7 @@
</div> </div>
<!-- Advance configs --> <!-- 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 id="advanceProxyRules" class="ui fluid accordion">
<div class="title"> <div class="title">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
@ -191,14 +191,19 @@
var targetDomain = $("#virtualDirectoryDomain").val().trim(); var targetDomain = $("#virtualDirectoryDomain").val().trim();
if (targetDomain != ""){ if (targetDomain != ""){
$.cjax({ $.cjax({
url: "/api/proxy/tlscheck", url: "/api/proxy/tlscheck?selfsignchk=true",
data: {url: targetDomain}, data: {url: targetDomain},
success: function(data){ success: function(data){
if (data.error != undefined){ if (data.error != undefined){
}else if (data == "https"){ }else if (data.protocol == "https"){
$("#vdReqTls").parent().checkbox("set checked"); $("#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"); $("#vdReqTls").parent().checkbox("set unchecked");
} }
} }

1133
src/web/darktheme.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,22 +16,24 @@
<script src="script/chart.js"></script> <script src="script/chart.js"></script>
<script src="script/utils.js"></script> <script src="script/utils.js"></script>
<link rel="stylesheet" href="main.css"> <link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="darktheme.css">
</head> </head>
<body> <body>
<script src="script/darktheme.js"></script>
<div class="menubar"> <div class="menubar">
<div class="item"> <div class="item">
<img class="logo" src="img/logo.svg"> <img class="logo" src="img/logo.svg">
</div> </div>
<div class="ui right floated buttons menutoggle" style="padding-top: 2px;"> <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>
<div class="ui right floated buttons" style="padding-top: 2px; padding-right: 0.4em;"> <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> <button class="ui basic white icon button" onclick="logout();"><i class="sign-out icon"></i></button>
</div> </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> <button id="themeColorButton" class="ui icon button" onclick="toggleTheme();"><i class="sun icon"></i></button>
</div> --> </div>
</div> </div>
<div class="wrapper"> <div class="wrapper">
<div class="toolbar"> <div class="toolbar">
@ -269,11 +271,18 @@
function toggleTheme(){ function toggleTheme(){
if ($("body").hasClass("darkTheme")){ if ($("body").hasClass("darkTheme")){
$("body").removeClass("darkTheme") setDarkTheme(false);
$("#themeColorButton").html(`<i class="ui moon icon"></i>`); //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{ }else{
$("body").addClass("darkTheme"); setDarkTheme(true);
$("#themeColorButton").html(`<i class="ui sun icon"></i>`); //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);
}
} }
} }

View File

@ -1,50 +1,8 @@
/* /*
index.html style overwrite 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 */ /* Theme color palletes are defined in darktheme.css */
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 Toggle CSS */ /* Theme Toggle CSS */
#themeColorButton{ #themeColorButton{
@ -368,7 +326,7 @@ body{
} }
.basic.segment.advanceoptions{ .basic.segment.advanceoptions{
background-color: #f7f7f7; background-color: var(--theme_advance);
border-radius: 1em; border-radius: 1em;
} }

6
src/web/robots.txt Normal file
View 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: /

View 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);
}

View File

@ -14,69 +14,72 @@
top: 0.4em; top: 0.4em;
right: 1em; right: 1em;
} }
</style> </style>
</head> </head>
<body> <body>
<br> <link rel="stylesheet" href="../darktheme.css">
<div class="ui container"> <script src="../script/darktheme.js"></script>
<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> <br>
</div> <div class="ui container">
<div class="ui bottom attached tab segment" data-tab="edit"> <div class="ui header">
<p>Select an Access Rule to edit</p> <div class="content">
<button id="refreshAccessRuleListBtn" class="ui circular basic icon button" onclick="reloadAccessRuleList()"><i class="ui green refresh icon"></i></button> Access Rule Editor
<div class="ui selection fluid dropdown" id="accessRuleSelector"> <div class="sub header">Create, Edit or Remove Access Rules</div>
<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>
</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> <br>
<form class="ui form" id="modifyRuleInfo"> <button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
<div class="disabled field"> <br><br><br>
<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>
</div> </div>
<script> <script>

View File

@ -25,6 +25,8 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">
@ -50,7 +52,7 @@
</div> </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> <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>
<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="ui accordion advanceSettings">
<div class="title"> <div class="title">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
@ -437,11 +439,15 @@
let optionalFieldsHTML = ""; let optionalFieldsHTML = "";
for (const [key, datatype] of Object.entries(data)) { for (const [key, datatype] of Object.entries(data)) {
if (datatype == "int"){ 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;"> <div class="ui basic blue label" style="font-weight: 300;">
${key} ${key}
</div> </div>
<input type="number" value="300"> <input type="number" value="${defaultValue}">
</div>`); </div>`);
}else if (datatype == "bool"){ }else if (datatype == "bool"){
booleanFieldsHTML += (`<div class="ui checkbox dnsConfigField" key="${key}" style="margin-top: 1em !important; padding-left: 0.4em;"> booleanFieldsHTML += (`<div class="ui checkbox dnsConfigField" key="${key}" style="margin-top: 1em !important; padding-left: 0.4em;">
@ -600,8 +606,12 @@
//Boolean option //Boolean option
let checked = $(this).find("input")[0].checked; let checked = $(this).find("input")[0].checked;
dnsCredentials[thisKey] = checked; dnsCredentials[thisKey] = checked;
}else if ($(this).hasClass("typeint")){
//Int options
let value = $(this).find("input").val();
dnsCredentials[thisKey] = parseInt(value);
}else{ }else{
//String or int options //String options
let value = $(this).find("input").val().trim(); let value = $(this).find("input").val().trim();
dnsCredentials[thisKey] = value; dnsCredentials[thisKey] = value;
} }

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script> <script src="../script/utils.js"></script>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script> <script src="../script/utils.js"></script>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script> <script src="../script/utils.js"></script>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">

View File

@ -10,6 +10,8 @@
<script src="../script/utils.js"></script> <script src="../script/utils.js"></script>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">

View File

@ -23,9 +23,15 @@
#permissionPolicyEditor .experimental{ #permissionPolicyEditor .experimental{
background-color: rgb(241, 241, 241); background-color: rgb(241, 241, 241);
} }
body.darkTheme #permissionPolicyEditor .experimental{
background-color: rgb(41, 41, 41);
}
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">
@ -86,7 +92,7 @@
</div> </div>
</div> </div>
<div class="ui divider"></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="ui fluid accordion">
<div class="title"> <div class="title">
<i class="dropdown icon" tabindex="0"><div class="menu" tabindex="-1"></div></i> <i class="dropdown icon" tabindex="0"><div class="menu" tabindex="-1"></div></i>

View File

@ -8,6 +8,8 @@
<script src="../script/semantic/semantic.min.js"></script> <script src="../script/semantic/semantic.min.js"></script>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br /> <br />
<div class="ui container"> <div class="ui container">
<div class="ui form"> <div class="ui form">

View File

@ -41,9 +41,28 @@
min-height: 300px; min-height: 300px;
overflow-y: auto; 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> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">

View File

@ -8,6 +8,8 @@
<script src="../script/semantic/semantic.min.js"></script> <script src="../script/semantic/semantic.min.js"></script>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">

View File

@ -55,10 +55,64 @@
background:#3643bb; background:#3643bb;
color:white; 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> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui stackable grid"> <div class="ui stackable grid">

View File

@ -15,6 +15,8 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div class="ui active inverted dimmer"> <div class="ui active inverted dimmer">
<div class="ui text loader">Loading Snippet</div> <div class="ui text loader">Loading Snippet</div>
</div> </div>

View File

@ -15,6 +15,8 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui basic segment"> <div class="ui basic segment">

View File

@ -15,6 +15,8 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui basic segment"> <div class="ui basic segment">

View File

@ -56,6 +56,8 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header"> <div class="ui header">

View File

@ -6,7 +6,7 @@
*/ */
/* /*
Color definations Color definition
*/ */
:root{ :root{
--dark_theme_toggle: #333333; --dark_theme_toggle: #333333;
@ -1252,6 +1252,10 @@ code{
color: var(--um_file_txt) !important; color: var(--um_file_txt) !important;
} }
.darkTheme .filename{
color:var(--text_color) !important;
}
/* /*
Hot Search Related Hot Search Related

View File

@ -32,6 +32,8 @@
</style> </style>
</head> </head>
<body class="whiteTheme"> <body class="whiteTheme">
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div id="navibar" class="navibar"> <div id="navibar" class="navibar">
<!-- File Opr Group--> <!-- 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> <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 id="pathDisplayField" class="ui breadcrumb addressText pathDisplay desktopOnly" >
</div> </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>
<div class="msgbox" style="z-index:999; display:none; padding-bottom: 1em;"> <div class="msgbox" style="z-index:999; display:none; padding-bottom: 1em;">
@ -79,7 +81,7 @@
</div> </div>
<br> <br>
</div> </div>
<div id="propertiesView" class="small"> <div id="propertiesView" class="small" style="height: 100%;">
<h3 class="ui header" style="margin-top: 0.4em;"> <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> <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> <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> </span>
</div>`); </div>`);
}else{ }else{
let isDarkTheme = $("body").hasClass("darkTheme");
let extension = "." + filename.split(".").pop(); let extension = "." + filename.split(".").pop();
let fileIcon = getFileIcon(extension); let fileIcon = getFileIcon(extension);
$("#fileList").append(`<div class="fileObject item" draggable="true" filename="${filename}" filepath="${path + filename}" ondblclick="openthis(this,event);" type="file"> $("#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"> <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> </span>
</div>`); </div>`);
} }

View File

@ -17,6 +17,8 @@
<link rel="stylesheet" href="../main.css"> <link rel="stylesheet" href="../main.css">
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui yellow message"> <div class="ui yellow message">

View File

@ -21,6 +21,8 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div class="ui container"> <div class="ui container">
<br> <br>
<div class="ui segment"> <div class="ui segment">

View File

@ -22,8 +22,10 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div id="mdns-hosts"> <div id="mdns-hosts">
<span><i class="ui loading spinner icon"></i> Loading</span>
</div> </div>
<br> <br>
<div class="ui container"> <div class="ui container">

View File

@ -18,6 +18,8 @@
</head> </head>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div class="ui container"> <div class="ui container">
<br> <br>
<div class="ui segment"> <div class="ui segment">

View File

@ -33,6 +33,8 @@
</style> </style>
</head> </head>
<body> <body>
<link rel="stylesheet" href="../darktheme.css">
<script src="../script/darktheme.js"></script>
<div id="loadingUI"> <div id="loadingUI">
<div style="margin-top: 2em; margin-left: 2em; color: white;"> <div style="margin-top: 2em; margin-left: 2em; color: white;">
<i class="ui loading spinner icon"></i> <b>Creating virtual terminal session</b><br> <i class="ui loading spinner icon"></i> <b>Creating virtual terminal session</b><br>

View File

@ -18,11 +18,9 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"imuslab.com/zoraxy/mod/dynamicproxy" "imuslab.com/zoraxy/mod/dynamicproxy"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
@ -32,39 +30,6 @@ import (
"imuslab.com/zoraxy/mod/wakeonlan" "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 Statistic Summary
*/ */
@ -367,12 +332,22 @@ func HandleZoraxyInfo(w http.ResponseWriter, r *http.Request) {
ZerotierConnected bool ZerotierConnected bool
} }
displayUUID := nodeUUID
displayAllowSSHLB := *allowSshLoopback
displayBootTime := bootTime
if !authAgent.CheckAuth(r) {
displayUUID = "Unauthorized"
displayAllowSSHLB = false
displayBootTime = 0
}
info := ZoraxyInfo{ info := ZoraxyInfo{
Version: version, Version: SYSTEM_VERSION,
NodeUUID: nodeUUID, NodeUUID: displayUUID,
Development: development, Development: DEVELOPMENT_BUILD,
BootTime: bootTime, BootTime: displayBootTime,
EnableSshLoopback: *allowSshLoopback, EnableSshLoopback: displayAllowSSHLB,
ZerotierConnected: ganManager.ControllerID != "", ZerotierConnected: ganManager.ControllerID != "",
} }