mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-06 07:37:21 +02:00
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:
commit
176249a7d9
@ -101,6 +101,7 @@ func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request)
|
|||||||
} else {
|
} else {
|
||||||
//This port do not support ACME
|
//This port do not support ACME
|
||||||
utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)")
|
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
|
//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
|
// Pass over to the acmeHandler to deal with the communication
|
||||||
acmeHandler.HandleRenewCertificate(w, r)
|
acmeHandler.HandleRenewCertificate(w, r)
|
||||||
|
|
||||||
|
//Update the TLS cert store buffer
|
||||||
|
tlsCertManager.UpdateLoadedCertList()
|
||||||
|
|
||||||
|
//Restore original settings
|
||||||
if dynamicProxyRouter.Option.Port == 443 {
|
if dynamicProxyRouter.Option.Port == 443 {
|
||||||
if !isForceHttpsRedirectEnabledOriginally {
|
if !isForceHttpsRedirectEnabledOriginally {
|
||||||
//Default is off. Turn the redirection off
|
//Default is off. Turn the redirection off
|
||||||
|
@ -25,12 +25,6 @@ func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := utils.PostPara(r, "domain")
|
|
||||||
if err != nil {
|
|
||||||
utils.SendErrorResponse(w, "domain cannot be empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
portString, err := utils.PostPara(r, "port")
|
portString, err := utils.PostPara(r, "port")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "port must be a valid integer")
|
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
|
//Set the email sender properties
|
||||||
thisEmailSender := email.Sender{
|
thisEmailSender := email.Sender{
|
||||||
Hostname: strings.TrimSpace(hostname),
|
Hostname: strings.TrimSpace(hostname),
|
||||||
Domain: strings.TrimSpace(domain),
|
|
||||||
Port: port,
|
Port: port,
|
||||||
Username: strings.TrimSpace(username),
|
Username: strings.TrimSpace(username),
|
||||||
Password: strings.TrimSpace(password),
|
Password: strings.TrimSpace(password),
|
||||||
@ -206,7 +199,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
if EmailSender.Username == "" || EmailSender.Domain == "" {
|
if EmailSender.Username == "" {
|
||||||
//Reset account not setup
|
//Reset account not setup
|
||||||
utils.SendErrorResponse(w, "Reset account not setup.")
|
utils.SendErrorResponse(w, "Reset account not setup.")
|
||||||
return
|
return
|
||||||
|
@ -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 showver = flag.Bool("version", false, "Show version of this server")
|
||||||
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||||
var allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
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 ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
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)")
|
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 (
|
var (
|
||||||
name = "Zoraxy"
|
name = "Zoraxy"
|
||||||
version = "3.0.2"
|
version = "3.0.3"
|
||||||
nodeUUID = "generic"
|
nodeUUID = "generic"
|
||||||
development = false //Set this to false to use embedded web fs
|
development = false //Set this to false to use embedded web fs
|
||||||
bootTime = time.Now().Unix()
|
bootTime = time.Now().Unix()
|
||||||
|
@ -69,6 +69,9 @@ func NewAccessController(options *Options) (*Controller, error) {
|
|||||||
Options: options,
|
Options: options,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Assign default access rule parent
|
||||||
|
thisController.DefaultAccessRule.parent = &thisController
|
||||||
|
|
||||||
//Load all acccess rules from file
|
//Load all acccess rules from file
|
||||||
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
|
configFiles, err := filepath.Glob(options.ConfigFolder + "/*.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -113,6 +116,7 @@ func (c *Controller) GetGlobalAccessRule() (*AccessRule, error) {
|
|||||||
// Load access rules to runtime, require rule ID
|
// Load access rules to runtime, require rule ID
|
||||||
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
|
func (c *Controller) GetAccessRuleByID(accessRuleID string) (*AccessRule, error) {
|
||||||
if accessRuleID == "default" || accessRuleID == "" {
|
if accessRuleID == "default" || accessRuleID == "" {
|
||||||
|
|
||||||
return c.DefaultAccessRule, nil
|
return c.DefaultAccessRule, nil
|
||||||
}
|
}
|
||||||
//Load from sync.Map, should be O(1)
|
//Load from sync.Map, should be O(1)
|
||||||
|
@ -2,6 +2,8 @@ package access
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -71,5 +73,22 @@ func (s *AccessRule) GetAllBlacklistedIp() []string {
|
|||||||
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
|
func (s *AccessRule) IsIPBlacklisted(ipAddr string) bool {
|
||||||
IPBlacklist := *s.BlackListIP
|
IPBlacklist := *s.BlackListIP
|
||||||
_, ok := IPBlacklist[ipAddr]
|
_, 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
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
|
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
|
||||||
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
|
||||||
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
|
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
|
||||||
//Missing tailing slash. Redirect to target proxy endpoint
|
//Missing tailing slash. Redirect to target proxy endpoint
|
||||||
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
@ -102,6 +102,13 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
/*
|
/*
|
||||||
Root Router Handling
|
Root Router Handling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//Root access control based on default rule
|
||||||
|
blocked := h.handleAccessRouting("default", w, r)
|
||||||
|
if blocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//Clean up the request URI
|
//Clean up the request URI
|
||||||
proxyingPath := strings.TrimSpace(r.RequestURI)
|
proxyingPath := strings.TrimSpace(r.RequestURI)
|
||||||
if !strings.HasSuffix(proxyingPath, "/") {
|
if !strings.HasSuffix(proxyingPath, "/") {
|
||||||
|
@ -21,6 +21,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
|
|||||||
w.Write([]byte("500 - Internal Server Error"))
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||||
if isBlocked {
|
if isBlocked {
|
||||||
h.logRequest(r, false, 403, blockedReason, "")
|
h.logRequest(r, false, 403, blockedReason, "")
|
||||||
|
@ -70,7 +70,8 @@ func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
|
|||||||
func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint {
|
func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI string) *VirtualDirectoryEndpoint {
|
||||||
for _, vdir := range ep.VirtualDirectories {
|
for _, vdir := range ep.VirtualDirectories {
|
||||||
if strings.HasPrefix(requestURI, vdir.MatchingPath) {
|
if strings.HasPrefix(requestURI, vdir.MatchingPath) {
|
||||||
return vdir
|
thisVdir := vdir
|
||||||
|
return thisVdir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -80,7 +81,8 @@ func (ep *ProxyEndpoint) GetVirtualDirectoryHandlerFromRequestURI(requestURI str
|
|||||||
func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint {
|
func (ep *ProxyEndpoint) GetVirtualDirectoryRuleByMatchingPath(matchingPath string) *VirtualDirectoryEndpoint {
|
||||||
for _, vdir := range ep.VirtualDirectories {
|
for _, vdir := range ep.VirtualDirectories {
|
||||||
if vdir.MatchingPath == matchingPath {
|
if vdir.MatchingPath == matchingPath {
|
||||||
return vdir
|
thisVdir := vdir
|
||||||
|
return thisVdir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -13,18 +13,16 @@ import (
|
|||||||
|
|
||||||
type Sender struct {
|
type Sender struct {
|
||||||
Hostname string //E.g. mail.gandi.net
|
Hostname string //E.g. mail.gandi.net
|
||||||
Domain string //E.g. arozos.com
|
|
||||||
Port int //E.g. 587
|
Port int //E.g. 587
|
||||||
Username string //Username of the email account
|
Username string //Username of the email account
|
||||||
Password string //Password of the email account
|
Password string //Password of the email account
|
||||||
SenderAddr string //e.g. admin@arozos.com
|
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 {
|
func NewEmailSender(hostname string, port int, username string, password string, senderAddr string) *Sender {
|
||||||
return &Sender{
|
return &Sender{
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
Domain: domain,
|
|
||||||
Port: port,
|
Port: port,
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
@ -33,13 +31,15 @@ func NewEmailSender(hostname string, domain string, port int, username string, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Send a email to a reciving addr
|
Send a email to a reciving addr
|
||||||
Example Usage:
|
Example Usage:
|
||||||
SendEmail(
|
SendEmail(
|
||||||
test@example.com,
|
|
||||||
"Free donuts",
|
test@example.com,
|
||||||
"Come get your free donuts on this Sunday!"
|
"Free donuts",
|
||||||
)
|
"Come get your free donuts on this Sunday!"
|
||||||
|
|
||||||
|
)
|
||||||
*/
|
*/
|
||||||
func (s *Sender) SendEmail(to string, subject string, content string) error {
|
func (s *Sender) SendEmail(to string, subject string, content string) error {
|
||||||
//Parse the email content
|
//Parse the email content
|
||||||
@ -50,7 +50,9 @@ func (s *Sender) SendEmail(to string, subject string, content string) error {
|
|||||||
content + "\n\n")
|
content + "\n\n")
|
||||||
|
|
||||||
//Login to the SMTP server
|
//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)
|
err := smtp.SendMail(s.Hostname+":"+strconv.Itoa(s.Port), auth, s.SenderAddr, []string{to}, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -83,6 +83,11 @@ func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
|||||||
if ipAddr == "" {
|
if ipAddr == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if netutils.IsPrivateIP(ipAddr) {
|
||||||
|
return "LAN"
|
||||||
|
}
|
||||||
|
|
||||||
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
|
@ -93,6 +93,10 @@ func MatchIpCIDR(ip string, cidr string) bool {
|
|||||||
|
|
||||||
// Check if a ip is private IP range
|
// Check if a ip is private IP range
|
||||||
func IsPrivateIP(ipStr string) bool {
|
func IsPrivateIP(ipStr string) bool {
|
||||||
|
if ipStr == "127.0.0.1" || ipStr == "::1" {
|
||||||
|
//local loopback
|
||||||
|
return true
|
||||||
|
}
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return false
|
return false
|
||||||
|
11
src/start.go
11
src/start.go
@ -160,8 +160,17 @@ func startupSequence() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
portInt = 8000
|
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{
|
mdnsScanner, err = mdns.NewMDNS(mdns.NetworkHost{
|
||||||
HostName: "zoraxy_" + nodeUUID,
|
HostName: hostName,
|
||||||
Port: portInt,
|
Port: portInt,
|
||||||
Domain: "zoraxy.arozos.com",
|
Domain: "zoraxy.arozos.com",
|
||||||
Model: "Network Gateway",
|
Model: "Network Gateway",
|
||||||
|
@ -765,8 +765,11 @@
|
|||||||
let data = Object.values(visitorData);
|
let data = Object.values(visitorData);
|
||||||
|
|
||||||
Object.keys(visitorData).forEach(function(cc){
|
Object.keys(visitorData).forEach(function(cc){
|
||||||
|
console.log(cc);
|
||||||
if (cc == ""){
|
if (cc == ""){
|
||||||
labels.push("Local / Unknown")
|
labels.push("Unknown")
|
||||||
|
}else if (cc == "lan"){
|
||||||
|
labels.push(`LAN / Loopback`);
|
||||||
}else{
|
}else{
|
||||||
labels.push(`${getCountryName(cc)} [${cc.toUpperCase()}]` );
|
labels.push(`${getCountryName(cc)} [${cc.toUpperCase()}]` );
|
||||||
}
|
}
|
||||||
|
@ -65,21 +65,9 @@
|
|||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
|
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
|
||||||
<div class="two fields">
|
<div class="field">
|
||||||
<div class="field">
|
<label>Sender Username / Email</label>
|
||||||
<label>Sender Username</label>
|
<input type="text" name="username" placeholder="e.g. admin or admin@mydomain.com">
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@ -272,7 +260,6 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var data = {
|
var data = {
|
||||||
hostname: $('input[name=hostname]').val(),
|
hostname: $('input[name=hostname]').val(),
|
||||||
domain: $('input[name=domain]').val(),
|
|
||||||
port: parseInt($('input[name=port]').val()),
|
port: parseInt($('input[name=port]').val()),
|
||||||
username: $('input[name=username]').val(),
|
username: $('input[name=username]').val(),
|
||||||
password: $('input[name=password]').val(),
|
password: $('input[name=password]').val(),
|
||||||
@ -306,7 +293,6 @@
|
|||||||
function initSMTPSettings(){
|
function initSMTPSettings(){
|
||||||
$.get("/api/tools/smtp/get", function(data){
|
$.get("/api/tools/smtp/get", function(data){
|
||||||
$('#email-form input[name=hostname]').val(data.Hostname);
|
$('#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=port]').val(data.Port);
|
||||||
$('#email-form input[name=username]').val(data.Username);
|
$('#email-form input[name=username]').val(data.Username);
|
||||||
$('#email-form input[name=senderAddr]').val(data.SenderAddr);
|
$('#email-form input[name=senderAddr]').val(data.SenderAddr);
|
||||||
@ -345,14 +331,6 @@
|
|||||||
form.find('input[name="hostname"]').parent().removeClass('error');
|
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
|
// validate username
|
||||||
const username = form.find('input[name="username"]').val().trim();
|
const username = form.find('input[name="username"]').val().trim();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user