Merge pull request #138 from tobychui/v3.0.3

Update V3.0.3

- Updated SMTP UI for non email login username
- Fixed ACME cert store reload after cert request
- Fixed default rule not applying to default site when default site is set to proxy target
- Fixed blacklist-ip not working with CIDR bug
- Fixed minor vdir bug in tailing slash detection and redirect logic
- Added custom mdns name support (-mdnsname flag)
- Added LAN tag in statistic
This commit is contained in:
Toby Chui 2024-04-30 14:39:49 +08:00 committed by GitHub
commit 176249a7d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 85 additions and 52 deletions

View File

@ -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

View File

@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
return
}
domain, err := utils.PostPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "domain cannot be empty")
return
}
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),
@ -206,7 +199,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

View File

@ -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)")
@ -51,7 +52,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()

View File

@ -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)

View File

@ -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
}

View File

@ -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
@ -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, "/") {

View File

@ -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, "")

View File

@ -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

View File

@ -13,18 +13,16 @@ 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
SenderAddr string //e.g. admin@arozos.com
}
//Create a new email sender object
func NewEmailSender(hostname string, domain string, port int, username string, password string, senderAddr string) *Sender {
// Create a new email sender object
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,
@ -33,13 +31,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 +50,9 @@ 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)
//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 {
return err

View File

@ -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 ""

View File

@ -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

View File

@ -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",

View File

@ -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()}]` );
}

View File

@ -65,21 +65,9 @@
<div class="field">
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
<div class="two fields">
<div class="field">
<label>Sender Username</label>
<input type="text" name="username" placeholder="E.g. admin">
</div>
<div class="field">
<label>Sender Domain</label>
<div class="ui labeled input">
<div class="ui basic label">
@
</div>
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
</div>
</div>
<div class="field">
<label>Sender Username / Email</label>
<input type="text" name="username" placeholder="e.g. admin or admin@mydomain.com">
</div>
</div>
<div class="field">
@ -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,14 +331,6 @@
form.find('input[name="hostname"]').parent().removeClass('error');
}
// validate domain
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();