From de658a3c6cf36912b26f76da00ce44db7c38f8a3 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Fri, 26 Apr 2024 22:40:27 +0800 Subject: [PATCH 1/7] Minor bug fix - Added potential fix for #130 - Added fix for disabled virtual directory check (future features) - Updated version number to v3.0.3 --- src/main.go | 2 +- src/mod/access/access.go | 4 ++++ src/mod/dynamicproxy/Server.go | 2 +- src/mod/dynamicproxy/access.go | 1 + src/mod/dynamicproxy/endpoints.go | 6 ++++-- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main.go b/src/main.go index 25cafd7..6de0125 100644 --- a/src/main.go +++ b/src/main.go @@ -51,7 +51,7 @@ var logOutputToFile = flag.Bool("log", true, "Log terminal output to file") var ( name = "Zoraxy" - version = "3.0.2" + version = "3.0.3" nodeUUID = "generic" development = false //Set this to false to use embedded web fs bootTime = time.Now().Unix() diff --git a/src/mod/access/access.go b/src/mod/access/access.go index c105c63..ae5cd9b 100644 --- a/src/mod/access/access.go +++ b/src/mod/access/access.go @@ -69,6 +69,9 @@ func NewAccessController(options *Options) (*Controller, error) { Options: options, } + //Assign default access rule parent + thisController.DefaultAccessRule.parent = &thisController + //Load all acccess rules from file configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json") if err != nil { @@ -113,6 +116,7 @@ func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) { // Load access rules to runtime, require rule ID func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) { if accessRuleID == "default" || accessRuleID == "" { + return c.DefaultAccessRule, nil } //Load from sync.Map, should be O(1) diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go index eb1314f..405d9a1 100644 --- a/src/mod/dynamicproxy/Server.go +++ b/src/mod/dynamicproxy/Server.go @@ -87,7 +87,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root { potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/") - if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled { + if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled { //Missing tailing slash. Redirect to target proxy endpoint http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect) return diff --git a/src/mod/dynamicproxy/access.go b/src/mod/dynamicproxy/access.go index b9c6857..441d0e5 100644 --- a/src/mod/dynamicproxy/access.go +++ b/src/mod/dynamicproxy/access.go @@ -21,6 +21,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter, w.Write([]byte("500 - Internal Server Error")) return true } + isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r) if isBlocked { h.logRequest(r, false, 403, blockedReason, "") diff --git a/src/mod/dynamicproxy/endpoints.go b/src/mod/dynamicproxy/endpoints.go index a0f80fb..d403110 100644 --- a/src/mod/dynamicproxy/endpoints.go +++ b/src/mod/dynamicproxy/endpoints.go @@ -70,7 +70,8 @@ func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error { func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint { for _, vdir := range ep.VirtualDirectories { if strings.HasPrefix(requestURI, vdir.MatchingPath) { - return vdir + thisVdir := vdir + return thisVdir } } return nil @@ -80,7 +81,8 @@ func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI str func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint { for _, vdir := range ep.VirtualDirectories { if vdir.MatchingPath == matchingPath { - return vdir + thisVdir := vdir + return thisVdir } } return nil From e0f543121596fae4881a447255e0eb75cc9980ab Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 27 Apr 2024 22:37:55 +0800 Subject: [PATCH 2/7] Fixed #129 - Removed requirements for Domain (now domain field can be empty and no error will be shown) --- src/emails.go | 6 +++--- src/mod/email/email.go | 26 +++++++++++++++++--------- src/web/components/utils.html | 4 +++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/emails.go b/src/emails.go index 55fd604..9946d28 100644 --- a/src/emails.go +++ b/src/emails.go @@ -27,8 +27,8 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) { domain, err := utils.PostPara(r, "domain") if err != nil { - utils.SendErrorResponse(w, "domain cannot be empty") - return + //Assume domain is empty, raised by issue #129 + domain = "" } portString, err := utils.PostPara(r, "port") @@ -206,7 +206,7 @@ var ( ) func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) { - if EmailSender.Username == "" || EmailSender.Domain == "" { + if EmailSender.Username == "" { //Reset account not setup utils.SendErrorResponse(w, "Reset account not setup.") return diff --git a/src/mod/email/email.go b/src/mod/email/email.go index 3191c7f..878b130 100644 --- a/src/mod/email/email.go +++ b/src/mod/email/email.go @@ -20,7 +20,7 @@ type Sender struct { SenderAddr string //e.g. admin@arozos.com } -//Create a new email sender object +// Create a new email sender object func NewEmailSender(hostname string, domain string, port int, username string, password string, senderAddr string) *Sender { return &Sender{ Hostname: hostname, @@ -33,13 +33,15 @@ func NewEmailSender(hostname string, domain string, port int, username string, p } /* - Send a email to a reciving addr - Example Usage: - SendEmail( - test@example.com, - "Free donuts", - "Come get your free donuts on this Sunday!" - ) +Send a email to a reciving addr +Example Usage: +SendEmail( + + test@example.com, + "Free donuts", + "Come get your free donuts on this Sunday!" + +) */ func (s *Sender) SendEmail(to string, subject string, content string) error { //Parse the email content @@ -50,7 +52,13 @@ func (s *Sender) SendEmail(to string, subject string, content string) error { content + "\n\n") //Login to the SMTP server - auth := smtp.PlainAuth("", s.Username+"@"+s.Domain, s.Password, s.Hostname) + var auth smtp.Auth + if s.Domain == "" { + auth = smtp.PlainAuth("", s.Username, s.Password, s.Hostname) + } else { + auth = smtp.PlainAuth("", s.Username+"@"+s.Domain, s.Password, s.Hostname) + } + err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg) if err != nil { return err diff --git a/src/web/components/utils.html b/src/web/components/utils.html index 9d84eab..e4dc346 100644 --- a/src/web/components/utils.html +++ b/src/web/components/utils.html @@ -345,7 +345,8 @@ form.find('input[name="hostname"]').parent().removeClass('error'); } - // validate domain + // validate domain, now allow empty string (for smtp that dont use domain as input) + /* const domain = form.find('input[name="domain"]').val().trim(); if (!domainRegex.test(domain)) { form.find('input[name="domain"]').parent().addClass('error'); @@ -353,6 +354,7 @@ } else { form.find('input[name="domain"]').parent().removeClass('error'); } + */ // validate username const username = form.find('input[name="username"]').val().trim(); From fc9240fbac568a0d70d45d6add3123ee228f68b7 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sun, 28 Apr 2024 11:27:00 +0800 Subject: [PATCH 3/7] Fixed #131 - Added LAN detection in geoip resolver - Updated UI for LAN/loopback request origin rendering --- src/mod/geodb/geodb.go | 5 +++++ src/mod/netutils/ipmatch.go | 4 ++++ src/web/components/stats.html | 5 ++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/mod/geodb/geodb.go b/src/mod/geodb/geodb.go index a25757a..8aed52d 100644 --- a/src/mod/geodb/geodb.go +++ b/src/mod/geodb/geodb.go @@ -83,6 +83,11 @@ func (s *Store) GetRequesterCountryISOCode(r *http.Request) string { if ipAddr == "" { return "" } + + if netutils.IsPrivateIP(ipAddr) { + return "LAN" + } + countryCode, err := s.ResolveCountryCodeFromIP(ipAddr) if err != nil { return "" diff --git a/src/mod/netutils/ipmatch.go b/src/mod/netutils/ipmatch.go index 2abd779..42608c5 100644 --- a/src/mod/netutils/ipmatch.go +++ b/src/mod/netutils/ipmatch.go @@ -93,6 +93,10 @@ func MatchIpCIDR(ip string, cidr string) bool { // Check if a ip is private IP range func IsPrivateIP(ipStr string) bool { + if ipStr == "127.0.0.1" || ipStr == "::1" { + //local loopback + return true + } ip := net.ParseIP(ipStr) if ip == nil { return false diff --git a/src/web/components/stats.html b/src/web/components/stats.html index ba7f43f..4f650b0 100644 --- a/src/web/components/stats.html +++ b/src/web/components/stats.html @@ -765,8 +765,11 @@ let data = Object.values(visitorData); Object.keys(visitorData).forEach(function(cc){ + console.log(cc); if (cc == ""){ - labels.push("Local / Unknown") + labels.push("Unknown") + }else if (cc == "lan"){ + labels.push(`LAN / Loopback`); }else{ labels.push(`${getCountryName(cc)} [${cc.toUpperCase()}]` ); } From e24f31bdeffa9c44ca23364cf9a397b0c6c6e712 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sun, 28 Apr 2024 22:25:05 +0800 Subject: [PATCH 4/7] Fixed #126 - Added cert store hot reload to fix newly ssl cert not loaded - Optimized SMTP structure and UI --- src/acme.go | 5 +++++ src/emails.go | 7 ------- src/mod/email/email.go | 12 +++--------- src/web/components/utils.html | 30 +++--------------------------- 4 files changed, 11 insertions(+), 43 deletions(-) diff --git a/src/acme.go b/src/acme.go index f1b54c5..4dd7cd4 100644 --- a/src/acme.go +++ b/src/acme.go @@ -101,6 +101,7 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) } else { //This port do not support ACME utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)") + return } //Add a 3 second delay to make sure everything is settle down @@ -109,6 +110,10 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) // Pass over to the acmeHandler to deal with the communication acmeHandler.HandleRenewCertificate(w, r) + //Update the TLS cert store buffer + tlsCertManager.UpdateLoadedCertList() + + //Restore original settings if dynamicProxyRouter.Option.Port == 443 { if !isForceHttpsRedirectEnabledOriginally { //Default is off. Turn the redirection off diff --git a/src/emails.go b/src/emails.go index 9946d28..529d25d 100644 --- a/src/emails.go +++ b/src/emails.go @@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) { return } - domain, err := utils.PostPara(r, "domain") - if err != nil { - //Assume domain is empty, raised by issue #129 - domain = "" - } - portString, err := utils.PostPara(r, "port") if err != nil { utils.SendErrorResponse(w, "port must be a valid integer") @@ -76,7 +70,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) { //Set the email sender properties thisEmailSender := email.Sender{ Hostname: strings.TrimSpace(hostname), - Domain: strings.TrimSpace(domain), Port: port, Username: strings.TrimSpace(username), Password: strings.TrimSpace(password), diff --git a/src/mod/email/email.go b/src/mod/email/email.go index 878b130..36946fc 100644 --- a/src/mod/email/email.go +++ b/src/mod/email/email.go @@ -13,7 +13,6 @@ import ( type Sender struct { Hostname string //E.g. mail.gandi.net - Domain string //E.g. arozos.com Port int //E.g. 587 Username string //Username of the email account Password string //Password of the email account @@ -21,10 +20,9 @@ type Sender struct { } // Create a new email sender object -func NewEmailSender(hostname string, domain string, port int, username string, password string, senderAddr string) *Sender { +func NewEmailSender(hostname string, port int, username string, password string, senderAddr string) *Sender { return &Sender{ Hostname: hostname, - Domain: domain, Port: port, Username: username, Password: password, @@ -52,12 +50,8 @@ func (s *Sender) SendEmail(to string, subject string, content string) error { content + "\n\n") //Login to the SMTP server - var auth smtp.Auth - if s.Domain == "" { - auth = smtp.PlainAuth("", s.Username, s.Password, s.Hostname) - } else { - auth = smtp.PlainAuth("", s.Username+"@"+s.Domain, s.Password, s.Hostname) - } + //Username can be username (e.g. admin) or email (e.g. admin@example.com), depending on SMTP service provider + auth := smtp.PlainAuth("", s.Username, s.Password, s.Hostname) err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg) if err != nil { diff --git a/src/web/components/utils.html b/src/web/components/utils.html index e4dc346..965d2b6 100644 --- a/src/web/components/utils.html +++ b/src/web/components/utils.html @@ -65,21 +65,9 @@

Credentials for SMTP server authentications

-
-
- - -
- -
- -
-
- @ -
- -
-
+
+ +
@@ -272,7 +260,6 @@ e.preventDefault(); var data = { hostname: $('input[name=hostname]').val(), - domain: $('input[name=domain]').val(), port: parseInt($('input[name=port]').val()), username: $('input[name=username]').val(), password: $('input[name=password]').val(), @@ -306,7 +293,6 @@ function initSMTPSettings(){ $.get("/api/tools/smtp/get", function(data){ $('#email-form input[name=hostname]').val(data.Hostname); - $('#email-form input[name=domain]').val(data.Domain); $('#email-form input[name=port]').val(data.Port); $('#email-form input[name=username]').val(data.Username); $('#email-form input[name=senderAddr]').val(data.SenderAddr); @@ -345,16 +331,6 @@ form.find('input[name="hostname"]').parent().removeClass('error'); } - // validate domain, now allow empty string (for smtp that dont use domain as input) - /* - const domain = form.find('input[name="domain"]').val().trim(); - if (!domainRegex.test(domain)) { - form.find('input[name="domain"]').parent().addClass('error'); - isValid = false; - } else { - form.find('input[name="domain"]').parent().removeClass('error'); - } - */ // validate username const username = form.find('input[name="username"]').val().trim(); From 7ba997dfc2ba447b3c8c66e4b463a88ef5e69806 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Tue, 30 Apr 2024 11:49:34 +0800 Subject: [PATCH 5/7] Added support for changing mdns name + Added `mdnsname` startup flag --- src/main.go | 1 + src/start.go | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main.go b/src/main.go index 6de0125..52aee68 100644 --- a/src/main.go +++ b/src/main.go @@ -41,6 +41,7 @@ var noauth = flag.Bool("noauth", false, "Disable authentication for management i 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 acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)") diff --git a/src/start.go b/src/start.go index f0a871e..acf3106 100644 --- a/src/start.go +++ b/src/start.go @@ -160,8 +160,17 @@ func startupSequence() { if err != nil { portInt = 8000 } + + hostName := *mdnsName + if hostName == "" { + hostName = "zoraxy_" + nodeUUID + } else { + //Trim off the suffix + hostName = strings.TrimSuffix(hostName, ".local") + } + mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{ - HostName: "zoraxy_" + nodeUUID, + HostName: hostName, Port: portInt, Domain: "zoraxy.arozos.com", Model: "Network Gateway", From a9695e969e2bf25f4ffb6f2700c33a470bb4c3fb Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Tue, 30 Apr 2024 13:25:26 +0800 Subject: [PATCH 6/7] Update Server.go Fixed default site bypassing access filter bug --- src/mod/dynamicproxy/Server.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go index 405d9a1..129192b 100644 --- a/src/mod/dynamicproxy/Server.go +++ b/src/mod/dynamicproxy/Server.go @@ -102,6 +102,13 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { /* Root Router Handling */ + + //Root access control based on default rule + blocked := h.handleAccessRouting("default", w, r) + if blocked { + return + } + //Clean up the request URI proxyingPath := strings.TrimSpace(r.RequestURI) if !strings.HasSuffix(proxyingPath, "/") { From e2a449a7bc3f1e3742ef1758b18dc5cfa973f45f Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Tue, 30 Apr 2024 13:39:48 +0800 Subject: [PATCH 7/7] Update blacklist.go Fixed blacklist CIDR not working bug --- src/mod/access/blacklist.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/mod/access/blacklist.go b/src/mod/access/blacklist.go index ec243ae..7daab35 100644 --- a/src/mod/access/blacklist.go +++ b/src/mod/access/blacklist.go @@ -2,6 +2,8 @@ package access import ( "strings" + + "imuslab.com/zoraxy/mod/netutils" ) /* @@ -71,5 +73,22 @@ func (s *AccessRule) GetAllBlacklistedIp() []string { func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool { IPBlacklist := *s.BlackListIP _, ok := IPBlacklist[ipAddr] - return ok + if ok { + return true + } + + //Check for CIDR + for ipOrCIDR, _ := range IPBlacklist { + wildcardMatch := netutils.MatchIpWildcard(ipAddr, ipOrCIDR) + if wildcardMatch { + return true + } + + cidrMatch := netutils.MatchIpCIDR(ipAddr, ipOrCIDR) + if cidrMatch { + return true + } + } + + return false }