Browse Source

auto update script executed

Toby Chui 1 year ago
parent
commit
6b55d8805c
8 changed files with 274 additions and 11 deletions
  1. 2 0
      api.go
  2. 2 1
      main.go
  3. 56 9
      mod/sshprox/sshprox.go
  4. 44 0
      mod/sshprox/utils.go
  5. 3 0
      start.go
  6. 4 1
      web/components/networktools.html
  7. 96 0
      web/tools/sshconn.html
  8. 67 0
      webssh.go

+ 2 - 0
api.go

@@ -81,8 +81,10 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing)
 	authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning)
 	authRouter.HandleFunc("/api/ssdp/discover", HandleSsdpDiscovery)
+
 	//Network utilities
 	authRouter.HandleFunc("/api/tools/ipscan", HandleIpScan)
+	authRouter.HandleFunc("/api/tools/webssh", HandleCreateProxySession)
 
 	//If you got APIs to add, append them here
 }

+ 2 - 1
main.go

@@ -17,6 +17,7 @@ import (
 	"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
 	"imuslab.com/zoraxy/mod/geodb"
 	"imuslab.com/zoraxy/mod/mdns"
+	"imuslab.com/zoraxy/mod/sshprox"
 	"imuslab.com/zoraxy/mod/statistic"
 	"imuslab.com/zoraxy/mod/tlscert"
 	"imuslab.com/zoraxy/mod/uptime"
@@ -41,7 +42,7 @@ var (
 	statisticCollector *statistic.Collector   //Collecting statistic from visitors
 	uptimeMonitor      *uptime.Monitor        //Uptime monitor service worker
 	mdnsScanner        *mdns.MDNSHost         //mDNS discovery services
-
+	webSshManager      *sshprox.Manager       //Web SSH connection service
 )
 
 // Kill signal handler. Do something before the system the core terminate.

+ 56 - 9
mod/sshprox/sshprox.go

@@ -8,6 +8,7 @@ import (
 	"os/exec"
 	"path/filepath"
 	"runtime"
+	"strconv"
 
 	"imuslab.com/zoraxy/mod/reverseproxy"
 	"imuslab.com/zoraxy/mod/utils"
@@ -21,13 +22,50 @@ import (
 	online ssh terminal
 */
 
+type Manager struct {
+	StartingPort  int
+	ReservedPorts map[string]int
+	Instances     []*Instance
+}
+
 type Instance struct {
 	ExecPath string
+	conn     *reverseproxy.ReverseProxy //HTTP proxy
+	tty      *exec.Cmd                  //SSH connection ported to web interface
+}
+
+func NewSSHProxyManager() *Manager {
+	return &Manager{
+		StartingPort:  14810,
+		ReservedPorts: map[string]int{},
+		Instances:     []*Instance{},
+	}
+}
+
+//Get the next free port in the list
+func (m *Manager) GetNextPort() int {
+	nextPort := m.StartingPort
+	for {
+		if _, exists := m.ReservedPorts[strconv.Itoa(nextPort)]; !exists {
+			if !isPortInUse(nextPort) {
+				return nextPort
+			}
+		}
+
+		if nextPort == 65534 {
+			return -1
+		}
+		nextPort++
+	}
 }
 
-func NewSSHProxy(binaryRoot string) (*Instance, error) {
+func (m *Manager) NewSSHProxy(binaryRoot string) (*Instance, error) {
 	//Check if the binary exists in system/gotty/
 	binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH
+
+	if runtime.GOOS == "windows" {
+		binary = binary + ".exe"
+	}
 	execPath := filepath.Join(binaryRoot, binary)
 
 	if !utils.FileExists(execPath) {
@@ -47,22 +85,31 @@ func NewSSHProxy(binaryRoot string) (*Instance, error) {
 }
 
 //Create a new Connection to target address
-func (i *Instance) CreateNewConnection(ipaddr string) (*reverseproxy.ReverseProxy, error) {
+func (i *Instance) CreateNewConnection(listenPort int, remoteIpAddr string, remotePort int) error {
+
 	//Create a gotty instance
-	cmd := exec.Command(i.ExecPath, "-p", "8080", "-w", "ssh", ipaddr)
+	cmd := exec.Command(i.ExecPath, "-w", "-p", strconv.Itoa(listenPort), "-a", "127.0.0.1", "--once", "--ws-origin", `\w*`, "ssh", remoteIpAddr, "-p", strconv.Itoa(remotePort))
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
-	cmd.Run()
+	go func() {
+		cmd.Run()
+	}()
+	i.tty = cmd
+
 	//Create a new proxy agent for this root
-	path, err := url.Parse(ipaddr)
+	path, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(listenPort))
 	if err != nil {
-		return nil, err
+		return err
 	}
 
+	//Create a new proxy object to the proxy
 	proxy := reverseproxy.NewReverseProxy(path)
-	return proxy, nil
-}
+	i.conn = proxy
 
-func HandleWebSSHConnection(w http.ResponseWriter, r *http.Request) {
+	return nil
+}
 
+//Bridge the remote connection to reverse proxy
+func (i *Instance) BridgeConnection(w http.ResponseWriter, r *http.Request) error {
+	return i.conn.ProxyHTTP(w, r)
 }

+ 44 - 0
mod/sshprox/utils.go

@@ -0,0 +1,44 @@
+package sshprox
+
+import (
+	"fmt"
+	"net"
+	"time"
+)
+
+//Check if a given domain and port is a valid ssh server
+func IsSSHConnectable(ipOrDomain string, port int) bool {
+	timeout := time.Second * 3
+	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ipOrDomain, port), timeout)
+	if err != nil {
+		return false
+	}
+	defer conn.Close()
+
+	// Send an SSH version identification string to the server to check if it's SSH
+	_, err = conn.Write([]byte("SSH-2.0-Go\r\n"))
+	if err != nil {
+		return false
+	}
+
+	// Wait for a response from the server
+	buf := make([]byte, 1024)
+	_, err = conn.Read(buf)
+	if err != nil {
+		return false
+	}
+
+	// Check if the response starts with "SSH-2.0"
+	return string(buf[:7]) == "SSH-2.0"
+}
+
+//Check if the port is used by other process or application
+func isPortInUse(port int) bool {
+	address := fmt.Sprintf(":%d", port)
+	listener, err := net.Listen("tcp", address)
+	if err != nil {
+		return true
+	}
+	listener.Close()
+	return false
+}

+ 3 - 0
start.go

@@ -12,6 +12,7 @@ import (
 	"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
 	"imuslab.com/zoraxy/mod/geodb"
 	"imuslab.com/zoraxy/mod/mdns"
+	"imuslab.com/zoraxy/mod/sshprox"
 	"imuslab.com/zoraxy/mod/statistic"
 	"imuslab.com/zoraxy/mod/tlscert"
 )
@@ -123,4 +124,6 @@ func startupSequence() {
 	}()
 	mdnsTickerStop = stopChan
 
+	//Create WebSSH Manager
+	webSshManager = sshprox.NewSSHProxyManager()
 }

+ 4 - 1
web/components/networktools.html

@@ -22,7 +22,10 @@
     </div>
 
     <div class="ui bottom attached tab segment" data-tab="tab2">
-    <p>Content of tab 2</p>
+        <h2>Web SSH</h2>
+        <p>Connect to a network node within the local area network of the gateway</p>
+        <button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/sshconn.html',1000, 640);">Connect via SSH</button>
+        <div class="ui divider"></div>
     </div>
 
     <div class="ui bottom attached tab segment" data-tab="tab3">

+ 96 - 0
web/tools/sshconn.html

@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta name="apple-mobile-web-app-capable" content="yes" />
+        <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
+        <meta charset="UTF-8">
+        <meta name="theme-color" content="#4b75ff">
+        <link rel="icon" type="image/png" href="./favicon.png" />
+        <title>Web SSH | Zoraxy</title>
+        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
+        <script src="../script/jquery-3.6.0.min.js"></script>
+        <script src="../../script/ao_module.js"></script>
+        <script src="../script/semantic/semantic.min.js"></script>
+        <script src="../script/tablesort.js"></script>
+        <link rel="stylesheet" href="../main.css">
+        <style>
+        </style>
+    </head>
+    <body>
+        <br>
+        <div class="ui container">
+            <div class="ui form">
+                <div class="two fields">
+                  <div class="field">
+                    <label>Server Name or IP Address</label>
+                    <input type="text" name="server" placeholder="e.g. example.com or 192.168.1.1">
+                  </div>
+                  <div class="field">
+                    <label>Port Number</label>
+                    <input type="number" name="port" placeholder="e.g. 22 or 2022">
+                  </div>
+                </div>
+            </div>
+        </div>
+        
+        <div style="float: right;">
+            <button class="ui basic button"  onclick="connectSSH()"><i class="ui blue exchange icon"></i> Connect</button>
+            <button class="ui basic button" style="margin-right: 1em;" onclick="window.open('', '_self', ''); window.close();"><i class="ui red remove icon"></i> Close</button>
+        </div>
+        <br><br><br>
+        <script>
+
+            //Start the SSH connection process
+            function connectSSH() {
+                const serverValue = $('input[name="server"]').val().trim();
+                const portValue = parseInt($('input[name="port"]').val().trim(), 10);
+                
+                // Validate server name or IP address
+                const validServer = isValidServerNameOrIPAddress(serverValue);
+                
+                // Validate port number
+                const validPort = (portValue >= 1 && portValue <= 65535);
+                
+                if (validServer && validPort) {
+                    // Call doSomething function if both inputs are valid
+                    createSSHProxy(serverValue, portValue);
+                } else {
+                    // Display an error message if either input is invalid
+                    const errorLabel = $('<div>').addClass('ui red basic label');
+                    if (!validServer) {
+                    errorLabel.text('Invalid server name or IP address');
+                    $('input[name="server"]').addClass('error');
+                    } else if (!validPort) {
+                    errorLabel.text('Invalid port number');
+                    $('input[name="port"]').addClass('error');
+                    }
+                    const field = $('input[name="server"]').closest('.field');
+                    field.append(errorLabel);
+                }
+            }
+
+            //Try to ask the server side to create a ssh proxy object
+            function createSSHProxy(){
+                
+            }
+
+
+            function isValidServerNameOrIPAddress(str) {
+                // First, check if the string is a valid IP address
+                const ipAddressRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
+                if (ipAddressRegex.test(str)) {
+                    return true;
+                }
+
+                // If the string is not an IP address, check if it is a valid domain name or server name
+                const serverNameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$/;
+                if (serverNameRegex.test(str)) {
+                    return true;
+                }
+
+                // If the string is neither an IP address nor a server name, return false
+                return false;
+            }
+        </script>
+    </body>
+</html>

+ 67 - 0
webssh.go

@@ -0,0 +1,67 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"imuslab.com/zoraxy/mod/sshprox"
+	"imuslab.com/zoraxy/mod/utils"
+)
+
+/*
+	webssh.go
+
+	This script handle the establish of a new ssh proxy object
+*/
+
+func HandleCreateProxySession(w http.ResponseWriter, r *http.Request) {
+	//Get what ip address and port to connect to
+	ipaddr, err := utils.GetPara(r, "ipaddr")
+	if err != nil {
+		http.Error(w, "Invalid Usage", http.StatusInternalServerError)
+		return
+	}
+
+	portString, err := utils.GetPara(r, "port")
+	if err != nil {
+		portString = "22"
+	}
+
+	port, err := strconv.Atoi(portString)
+	if err != nil {
+		http.Error(w, "Invalid port number given", http.StatusInternalServerError)
+		return
+	}
+
+	//Check if the target is a valid ssh endpoint
+	if !sshprox.IsSSHConnectable(ipaddr, port) {
+		http.Error(w, ipaddr+":"+strconv.Itoa(port)+" is not a valid SSH server", http.StatusInternalServerError)
+		return
+	}
+
+	//Create a new proxy instance
+	instance, err := webSshManager.NewSSHProxy("./system/gotty")
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	//Create an ssh process to the target address
+	err = instance.CreateNewConnection(webSshManager.GetNextPort(), ipaddr, port)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	//Everything seems ok. Bridge the connection
+	err = instance.BridgeConnection(w, r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
+func HandleTest(w http.ResponseWriter, r *http.Request) {
+	fmt.Println(sshprox.IsSSHConnectable("192.168.1.120", 22))
+}