Browse Source

auto update script executed

Toby Chui 1 year ago
parent
commit
3090893776

+ 10 - 8
api.go

@@ -16,8 +16,10 @@ import (
 
 */
 
+var requireAuth = true
+
 func initAPIs() {
-	requireAuth := !(*noauth || handler.IsUsingExternalPermissionManager())
+
 	authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
 		AuthAgent:   authAgent,
 		RequireAuth: requireAuth,
@@ -27,14 +29,13 @@ func initAPIs() {
 	})
 
 	//Register the standard web services urls
-	fs := http.FileServer(http.Dir("./web"))
-	if requireAuth {
-		//Add a layer of middleware for auth control
-		authHandler := AuthFsHandler(fs)
-		http.Handle("/", authHandler)
-	} else {
-		http.Handle("/", fs)
+	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)
+	http.Handle("/", advHandler)
 
 	//Authentication APIs
 	registerAuthAPIs(requireAuth)
@@ -85,6 +86,7 @@ func initAPIs() {
 	//Network utilities
 	authRouter.HandleFunc("/api/tools/ipscan", HandleIpScan)
 	authRouter.HandleFunc("/api/tools/webssh", HandleCreateProxySession)
+	authRouter.HandleFunc("/api/tools/websshSupported", HandleWebSshSupportCheck)
 
 	//If you got APIs to add, append them here
 }

+ 1 - 1
geoip.go

@@ -18,7 +18,7 @@ func getCountryCodeFromRequest(r *http.Request) string {
 	}
 
 	// Open the GeoIP database
-	db, err := geoip2.Open("./system/GeoIP2-Country.mmdb")
+	db, err := geoip2.Open("./tmp/GeoIP2-Country.mmdb")
 	if err != nil {
 		// Handle the error
 		return countryCode

+ 20 - 5
main.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"embed"
 	"flag"
 	"fmt"
 	"log"
@@ -30,10 +31,20 @@ var showver = flag.Bool("version", false, "Show version of this server")
 var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
 
 var (
-	name     = "Zoraxy"
-	version  = "2.11"
-	nodeUUID = "generic"
-
+	name        = "Zoraxy"
+	version     = "2.11"
+	nodeUUID    = "generic"
+	development = false //Set this to false to use embedded web fs
+
+	/*
+		Binary Embedding File System
+	*/
+	//go:embed web/*
+	webres embed.FS
+
+	/*
+		Handler Modules
+	*/
 	handler            *aroz.ArozHandler      //Handle arozos managed permission system
 	sysdb              *database.Database     //System database
 	authAgent          *auth.AuthAgent        //Authentication agent
@@ -59,6 +70,9 @@ func SetupCloseHandler() {
 		mdnsTickerStop <- true
 		mdnsScanner.Close()
 
+		//Remove the tmp folder
+		os.RemoveAll("./tmp")
+
 		//Close database, final
 		sysdb.Close()
 		os.Exit(0)
@@ -88,7 +102,7 @@ func main() {
 	SetupCloseHandler()
 
 	//Read or create the system uuid
-	uuidRecord := "./system/sys.uuid"
+	uuidRecord := "./sys.uuid"
 	if !utils.FileExists(uuidRecord) {
 		newSystemUUID := uuid.New().String()
 		os.WriteFile(uuidRecord, []byte(newSystemUUID), 0775)
@@ -104,6 +118,7 @@ func main() {
 	startupSequence()
 
 	//Initiate management interface APIs
+	requireAuth = !(*noauth || handler.IsUsingExternalPermissionManager())
 	initAPIs()
 
 	//Start the reverse proxy server in go routine

+ 1 - 0
mod/dynamicproxy/dynamicproxy.go

@@ -118,6 +118,7 @@ func (router *Router) StartProxyService() error {
 		ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.Option.Port), config)
 		if err != nil {
 			log.Println(err)
+			router.Running = false
 			return err
 		}
 		router.tlsListener = ln

BIN
mod/geodb/GeoLite2-Country.mmdb


+ 17 - 0
mod/geodb/geodb.go

@@ -1,14 +1,20 @@
 package geodb
 
 import (
+	"embed"
 	"net"
 	"net/http"
+	"os"
 	"strings"
 
 	"github.com/oschwald/geoip2-golang"
 	"imuslab.com/zoraxy/mod/database"
+	"imuslab.com/zoraxy/mod/utils"
 )
 
+//go:embed GeoLite2-Country.mmdb
+var geodb embed.FS
+
 type Store struct {
 	Enabled bool
 	geodb   *geoip2.Reader
@@ -21,6 +27,17 @@ type CountryInfo struct {
 }
 
 func NewGeoDb(sysdb *database.Database, dbfile string) (*Store, error) {
+	if !utils.FileExists(dbfile) {
+		//Unzip it from binary
+		geodbFile, err := geodb.ReadFile("GeoLite2-Country.mmdb")
+		if err != nil {
+			return nil, err
+		}
+		err = os.WriteFile(dbfile, geodbFile, 0775)
+		if err != nil {
+			return nil, err
+		}
+	}
 	db, err := geoip2.Open(dbfile)
 	if err != nil {
 		return nil, err

+ 0 - 0
system/gotty/.gotty → mod/sshprox/gotty/.gotty


+ 0 - 0
system/gotty/gotty_linux_amd64 → mod/sshprox/gotty/gotty_linux_amd64


+ 0 - 0
system/gotty/gotty_linux_arm → mod/sshprox/gotty/gotty_linux_arm


+ 0 - 0
system/gotty/gotty_linux_arm64 → mod/sshprox/gotty/gotty_linux_arm64


+ 38 - 2
mod/sshprox/sshprox.go

@@ -1,8 +1,10 @@
 package sshprox
 
 import (
+	"embed"
 	"errors"
 	"fmt"
+	"log"
 	"net/http"
 	"net/url"
 	"os"
@@ -26,6 +28,16 @@ import (
 	online ssh terminal
 */
 
+/*
+	Bianry embedding
+
+	Make sure when compile, gotty binary exists in static.gotty
+*/
+var (
+	//go:embed gotty/*
+	gotty embed.FS
+)
+
 type Manager struct {
 	StartingPort int
 	Instances    []*Instance
@@ -107,11 +119,35 @@ func (m *Manager) NewSSHProxy(binaryRoot string) (*Instance, error) {
 	if runtime.GOOS == "windows" {
 		binary = binary + ".exe"
 	}
+
+	//Extract it from embedfs if not exists locally
 	execPath := filepath.Join(binaryRoot, binary)
 
+	//Create the storage folder structure
+	os.MkdirAll(filepath.Dir(execPath), 0775)
+
+	//Create config file if not exists
+	if !utils.FileExists(filepath.Join(filepath.Dir(execPath), ".gotty")) {
+		configFile, _ := gotty.ReadFile("gotty/.gotty")
+		os.WriteFile(filepath.Join(filepath.Dir(execPath), ".gotty"), configFile, 0775)
+	}
+
+	//Create web.ssh binary if not exists
 	if !utils.FileExists(execPath) {
-		//Binary not found
-		return nil, errors.New("binary not found at " + execPath)
+		//Try to extract it from embedded fs
+		executable, err := gotty.ReadFile("gotty/" + binary)
+		if err != nil {
+			//Binary not found in embedded
+			return nil, errors.New("platform not supported")
+		}
+
+		//Extract to target location
+		err = os.WriteFile(execPath, executable, 0777)
+		if err != nil {
+			//Binary not found in embedded
+			log.Println("Extract web.ssh failed: " + err.Error())
+			return nil, errors.New("web.ssh sub-program extract failed")
+		}
 	}
 
 	//Convert the binary path to realpath

+ 20 - 0
mod/sshprox/utils.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"net"
 	"net/url"
+	"runtime"
 	"strings"
 	"time"
 )
@@ -14,6 +15,25 @@ func RewriteURL(rooturl string, requestURL string) (*url.URL, error) {
 	return url.Parse(rewrittenURL)
 }
 
+//Check if the current platform support web.ssh function
+func IsWebSSHSupported() bool {
+	//Check if the binary exists in system/gotty/
+	binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH
+
+	if runtime.GOOS == "windows" {
+		binary = binary + ".exe"
+	}
+
+	//Check if the target gotty terminal exists
+	f, err := gotty.Open("gotty/" + binary)
+	if err != nil {
+		return false
+	}
+
+	f.Close()
+	return true
+}
+
 //Check if a given domain and port is a valid ssh server
 func IsSSHConnectable(ipOrDomain string, port int) bool {
 	timeout := time.Second * 3

+ 0 - 0
system/localhost.crt → mod/tlscert/localhost.crt


+ 0 - 0
system/localhost.key → mod/tlscert/localhost.key


+ 18 - 3
mod/tlscert/tlscert.go

@@ -3,6 +3,7 @@ package tlscert
 import (
 	"crypto/tls"
 	"crypto/x509"
+	"embed"
 	"encoding/pem"
 	"io"
 	"io/ioutil"
@@ -19,6 +20,9 @@ type Manager struct {
 	verbal    bool
 }
 
+//go:embed localhost.crt localhost.key
+var buildinCertStore embed.FS
+
 func NewManager(certStore string) (*Manager, error) {
 	if !utils.FileExists(certStore) {
 		os.MkdirAll(certStore, 0775)
@@ -62,8 +66,19 @@ func (m *Manager) ListCerts() ([]string, error) {
 
 func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
 	//Check if the domain corrisponding cert exists
-	pubKey := "./system/localhost.crt"
-	priKey := "./system/localhost.key"
+	pubKey := "./tmp/localhost.crt"
+	priKey := "./tmp/localhost.key"
+
+	//Check if this is initial setup
+	if !utils.FileExists(pubKey) {
+		buildInPubKey, _ := buildinCertStore.ReadFile(filepath.Base(pubKey))
+		os.WriteFile(pubKey, buildInPubKey, 0775)
+	}
+
+	if !utils.FileExists(priKey) {
+		buildInPriKey, _ := buildinCertStore.ReadFile(filepath.Base(priKey))
+		os.WriteFile(priKey, buildInPriKey, 0775)
+	}
 
 	if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".crt")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
 		pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".crt")
@@ -85,7 +100,7 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
 			}
 		} else {
 			if m.verbal {
-				log.Println("Matching certificate not found. Serving with default. Requesting server name: ", helloInfo.ServerName)
+				log.Println("Matching certificate not found. Serving with build-in certificate. Requesting server name: ", helloInfo.ServerName)
 			}
 		}
 	}

+ 33 - 4
router.go

@@ -3,6 +3,8 @@ package main
 import (
 	"fmt"
 	"net/http"
+	"net/url"
+	"path/filepath"
 	"strings"
 
 	"imuslab.com/zoraxy/mod/sshprox"
@@ -15,17 +17,36 @@ import (
 	for the reverse proxy service
 */
 
-func AuthFsHandler(handler http.Handler) http.Handler {
+func FSHandler(handler http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		/*
+			Development Mode Override
+			=> Web root is located in /
+		*/
+		if development && strings.HasPrefix(r.URL.Path, "/web/") {
+			u, _ := url.Parse(strings.TrimPrefix(r.URL.Path, "/web"))
+			r.URL = u
+		}
+
+		/*
+			Production Mode Override
+			=> Web root is located in /web
+		*/
+		if !development && r.URL.Path == "/" {
+			//Redirect to web UI
+			http.Redirect(w, r, "/web/", http.StatusTemporaryRedirect)
+			return
+		}
+
 		// Allow access to /script/*, /img/pubic/* and /login.html without authentication
-		if strings.HasPrefix(r.URL.Path, "/script/") || strings.HasPrefix(r.URL.Path, "/img/public/") || r.URL.Path == "/login.html" || r.URL.Path == "/favicon.png" {
+		if strings.HasPrefix(r.URL.Path, ppf("/script/")) || strings.HasPrefix(r.URL.Path, ppf("/img/public/")) || r.URL.Path == ppf("/login.html") || r.URL.Path == ppf("/favicon.png") {
 			handler.ServeHTTP(w, r)
 			return
 		}
 
 		// check authentication
-		if !authAgent.CheckAuth(r) {
-			http.Redirect(w, r, "/login.html", http.StatusTemporaryRedirect)
+		if !authAgent.CheckAuth(r) && requireAuth {
+			http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
 			return
 		}
 
@@ -57,3 +78,11 @@ func AuthFsHandler(handler http.Handler) http.Handler {
 		handler.ServeHTTP(w, r)
 	})
 }
+
+//Production path fix wrapper. Fix the path on production or development environment
+func ppf(relativeFilepath string) string {
+	if !development {
+		return strings.ReplaceAll(filepath.Join("/web/", relativeFilepath), "\\", "/")
+	}
+	return relativeFilepath
+}

+ 6 - 2
start.go

@@ -3,6 +3,7 @@ package main
 import (
 	"log"
 	"net/http"
+	"os"
 	"strconv"
 	"strings"
 	"time"
@@ -42,6 +43,9 @@ func startupSequence() {
 	//Create tables for the database
 	sysdb.NewTable("settings")
 
+	//Create tmp folder
+	os.MkdirAll("./tmp", 0775)
+
 	//Create an auth agent
 	sessionKey, err := auth.GetSessionKey(sysdb)
 	if err != nil {
@@ -49,7 +53,7 @@ func startupSequence() {
 	}
 	authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, func(w http.ResponseWriter, r *http.Request) {
 		//Not logged in. Redirecting to login page
-		http.Redirect(w, r, "/login.html", http.StatusTemporaryRedirect)
+		http.Redirect(w, r, ppf("/login.html"), http.StatusTemporaryRedirect)
 	})
 
 	//Create a TLS certificate manager
@@ -65,7 +69,7 @@ func startupSequence() {
 	}
 
 	//Create a geodb store
-	geodbStore, err = geodb.NewGeoDb(sysdb, "./system/GeoLite2-Country.mmdb")
+	geodbStore, err = geodb.NewGeoDb(sysdb, "./tmp/GeoLite2-Country.mmdb")
 	if err != nil {
 		panic(err)
 	}

+ 1 - 0
sys.uuid

@@ -0,0 +1 @@
+0dc57054-6e06-42b1-ac84-c6d29aa5208d

+ 0 - 17
system/gomod-license.csv

@@ -1,17 +0,0 @@
-github.com/boltdb/bolt,https://github.com/boltdb/bolt/blob/v1.3.1/LICENSE,MIT
-github.com/gorilla/securecookie,https://github.com/gorilla/securecookie/blob/v1.1.1/LICENSE,BSD-3-Clause
-github.com/gorilla/sessions,https://github.com/gorilla/sessions/blob/v1.2.1/LICENSE,BSD-3-Clause
-github.com/gorilla/websocket,https://github.com/gorilla/websocket/blob/v1.4.2/LICENSE,BSD-2-Clause
-github.com/oschwald/geoip2-golang,https://github.com/oschwald/geoip2-golang/blob/v1.8.0/LICENSE,ISC
-github.com/oschwald/maxminddb-golang,https://github.com/oschwald/maxminddb-golang/blob/v1.10.0/LICENSE,ISC
-gitlab.com/NebulousLabs/fastrand,https://gitlab.com/NebulousLabs/fastrand/blob/603482d69e40/LICENSE,MIT
-gitlab.com/NebulousLabs/go-upnp,https://gitlab.com/NebulousLabs/go-upnp/blob/11da932010b6/LICENSE,MIT
-gitlab.com/NebulousLabs/go-upnp/goupnp,https://gitlab.com/NebulousLabs/go-upnp/blob/11da932010b6/goupnp\LICENSE,BSD-2-Clause
-golang.org/x/crypto/blake2b,https://cs.opensource.google/go/x/crypto/+/0c34fe9e:LICENSE,BSD-3-Clause
-golang.org/x/net/html,https://cs.opensource.google/go/x/net/+/afb366fc:LICENSE,BSD-3-Clause
-golang.org/x/sys,https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE,BSD-3-Clause
-golang.org/x/text,https://cs.opensource.google/go/x/text/+/v0.3.6:LICENSE,BSD-3-Clause
-imuslab.com/zoraxy,Unknown,MIT
-imuslab.com/zoraxy/mod/dynamicproxy/dpcore,Unknown,MIT
-imuslab.com/zoraxy/mod/reverseproxy,Unknown,MIT
-imuslab.com/zoraxy/mod/websocketproxy,Unknown,MIT

+ 0 - 2
system/gotty/bashstart

@@ -1,2 +0,0 @@
-export TERM=xterm
-cd ~/

+ 0 - 1
system/sys.uuid

@@ -1 +0,0 @@
-ae4a7901-104d-4ad2-bbaa-a54ea25dc086

+ 20 - 2
web/components/networktools.html

@@ -22,7 +22,7 @@
     </div>
 
     <div class="ui bottom attached tab segment" data-tab="tab2">
-        <div style="position: relative;">
+        <div id="websshTool" style="position: relative;">
             <h2>Web SSH</h2>
             <p>Connect to a network node within the local area network of the gateway</p>
             <div class="ui small form">
@@ -49,6 +49,8 @@
         </div>
        
         <div class="ui divider"></div>
+        <h2>Wake On LAN</h2>
+        <p>Wake up a remote server by WOL Magic Packet or an IoT device</p>
     </div>
 
     <div class="ui bottom attached tab segment" data-tab="tab3">
@@ -80,8 +82,24 @@
         var tab = $(this).attr('data-tab');
         $('.tab.segment').removeClass('active');
         $('div[data-tab="' + tab + '"]').addClass('active');
-    });
+    }); 
+
+    //Check if web.ssh is supported
+    function checkWebSSHSupport(){
+        $.get("/api/tools/websshSupported", function(data){
+            if (data == false){
+                $("#websshTool").css({
+                    "opacity": "0.6",
+                    "pointer-events": "none",
+                    "user-select": "none",
+                });
+                $("#websshTool").find("button").addClass("disabled");
+            }
+        })
+    }
+    checkWebSSHSupport();
 
+    //Connect SSH using web.ssh tool
     function connectSSH(){
         function validateForm() {
             var serverInput = document.getElementById("ssh_server");

+ 24 - 14
web/components/status.html

@@ -539,7 +539,10 @@
     function updateChart() {
         //networkStatisticChart.data.datasets[0].data = rxValues;
         //networkStatisticChart.data.datasets[1].data = txValues;
-        networkStatisticChart.update();
+        if (networkStatisticChart != undefined){
+            networkStatisticChart.update();
+        }
+        
     }
 
     function updateChartSize(){
@@ -549,11 +552,12 @@
         }else{
             newSize = $("#networkActWrapper").width() - 500;
         }
-        networkStatisticChart.resize(newSize, 200);
+        if (networkStatisticChart != undefined){
+            networkStatisticChart.resize(newSize, 200);
+        }
     }
 
-    var chartResizeTimeout;
-    window.addEventListener('resize', () => {
+    function handleChartAccumulateResize(){
         $("#networkActivity").hide();
         $("#networkActivityPlaceHolder").show();
         if (chartResizeTimeout != undefined){
@@ -565,24 +569,30 @@
             $("#networkActivity").show();
             updateChartSize();
         }, 300);
+    }
+
+    var chartResizeTimeout;
+    window.addEventListener('resize', () => {
+        handleChartAccumulateResize();
     });
 
     //Bind event to tab switch
     tabSwitchEventBind["status"] = function(){
         //On switch over to this page, resize the chart
-        $("#networkActivity").hide();
-        $("#networkActivityPlaceHolder").show();
-        if (chartResizeTimeout != undefined){
-            clearTimeout(chartResizeTimeout);
-        }
-        chartResizeTimeout = setTimeout(function(){
-            chartResizeTimeout = undefined;
-            $("#networkActivityPlaceHolder").hide();
-            $("#networkActivity").show();
+        $("#networkActivityPlaceHolder").hide();
+        $("#networkActivity").show().delay(100, function(){
             updateChartSize();
-        }, 300);
+        });
+        
     }
 
+    document.addEventListener("visibilitychange", () => {
+        // it could be either hidden or visible
+        //handleChartAccumulateResize();
+    });
+
+
+
     //Initialize chart data
     initChart();
     fetchData();

+ 31 - 4
webssh.go

@@ -2,7 +2,6 @@ package main
 
 import (
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"strconv"
 	"strings"
@@ -57,7 +56,7 @@ func HandleCreateProxySession(w http.ResponseWriter, r *http.Request) {
 	}
 
 	//Create a new proxy instance
-	instance, err := webSshManager.NewSSHProxy("./system/gotty")
+	instance, err := webSshManager.NewSSHProxy("./tmp/gotty")
 	if err != nil {
 		utils.SendErrorResponse(w, strings.ReplaceAll(err.Error(), "\\", "/"))
 		return
@@ -75,6 +74,34 @@ func HandleCreateProxySession(w http.ResponseWriter, r *http.Request) {
 	utils.SendJSONResponse(w, string(js))
 }
 
-func HandleTest(w http.ResponseWriter, r *http.Request) {
-	fmt.Println(sshprox.IsSSHConnectable("192.168.1.120", 22))
+//Check if the host support ssh, or if the target domain (and port, optional) support ssh
+func HandleWebSshSupportCheck(w http.ResponseWriter, r *http.Request) {
+	domain, err := utils.PostPara(r, "domain")
+	if err != nil {
+		//Check if ssh supported on this host
+		isSupport := sshprox.IsWebSSHSupported()
+		js, _ := json.Marshal(isSupport)
+		utils.SendJSONResponse(w, string(js))
+	} else {
+		//Domain is given. Check if port is given
+		portString, err := utils.PostPara(r, "port")
+		if err != nil {
+			portString = "22"
+		}
+
+		port, err := strconv.Atoi(portString)
+		if err != nil {
+			utils.SendErrorResponse(w, "invalid port number given")
+			return
+		}
+
+		if port < 1 || port > 65534 {
+			utils.SendErrorResponse(w, "invalid port number given")
+			return
+		}
+
+		looksLikeSSHServer := sshprox.IsSSHConnectable(domain, port)
+		js, _ := json.Marshal(looksLikeSSHServer)
+		utils.SendJSONResponse(w, string(js))
+	}
 }