v2 init commit

This commit is contained in:
Toby Chui
2023-05-22 23:05:59 +08:00
parent 5ac0fdde1d
commit c07d5f85df
87 changed files with 273125 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
// [string] Address to listen, all addresses will be used when empty
// address = ""
// [string] Port to listen
// port = "8080"
// [bool] Permit clients to write to the TTY
// permit_write = false
// [bool] Enable basic authentication
// enable_basic_auth = false
// [string] Default username and password of basic authentication (user:pass)
// To enable basic authentication, set `true` to `enable_basic_auth`
// credential = "user:pass"
// [bool] Enable random URL generation
// enable_random_url = false
// [int] Default length of random strings appended to URL
// To enable random URL generation, set `true` to `enable_random_url`
// random_url_length = 8
// [bool] Enable TLS/SSL
// enable_tls = false
// [string] Default TLS certificate file path
// tls_crt_file = "~/.gotty.crt"
// [string] Default TLS key file path
// tls_key_file = "~/.gotty.key"
// [bool] Enable client certificate authentication
// enable_tls_client_auth = false
// [string] Certificate file of CA for client certificates
// tls_ca_crt_file = "~/.gotty.ca.crt"
// [string] Custom index.html file
// index_file = ""
// [string] Title format of browser window
// Available variables are:
// Command Command string
// Pid PID of the process for the client
// Hostname Server hostname
// RemoteAddr Client IP address
// title_format = "GoTTY - {{ .Command }} ({{ .Hostname }})"
// [bool] Enable client side reconnection when connection closed
// enable_reconnect = false
// [int] Interval time to try reconnection (seconds)
// To enable reconnection, set `true` to `enable_reconnect`
// reconnect_time = 10
// [int] Timeout seconds for waiting a client (0 to disable)
// timeout = 60
// [int] Maximum connection to gotty, 0(default) means no limit.
// max_connection = 0
// [bool] Accept only one client and exit gotty once the client exits
// once = false
// [bool] Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB)
// permit_arguments = false
// [object] Client terminal (hterm) preferences
// preferences {
// [enum(null, "none", "ctrl-alt", "left-alt", "right-alt")]
// Select an AltGr detection hack^Wheuristic.
// null: Autodetect based on navigator.language: "en-us" => "none", else => "right-alt"
// "none": Disable any AltGr related munging.
// "ctrl-alt": Assume Ctrl+Alt means AltGr.
// "left-alt": Assume left Alt means AltGr.
// "right-alt": Assume right Alt means AltGr.
// alt_gr_mode = null
// [bool] If set, alt-backspace indeed is alt-backspace.
// alt_backspace_is_meta_backspace = false
// [bool] Set whether the alt key acts as a meta key or as a distinct alt key.
// alt_is_meta = false
// [enum("escape", "8-bit", "browser-key")]
// Controls how the alt key is handled.
// "escape"....... Send an ESC prefix.
// "8-bit"........ Add 128 to the unshifted character as in xterm.
// "browser-key".. Wait for the keypress event and see what the browser says.
// (This won't work well on platforms where the browser performs a default action for some alt sequences.)
// alt_sends_what = "escape"
// [string] URL of the terminal bell sound. Empty string for no audible bell.
// audible_bell_sound = "lib-resource:hterm/audio/bell"
// [bool] If true, terminal bells in the background will create a Web Notification. http://www.w3.org/TR/notifications/
// Displaying notifications requires permission from the user.
// When this option is set to true, hterm will attempt to ask the user for permission if necessary.
// Note browsers may not show this permission request
// if it did not originate from a user action.
// desktop_notification_bell = false
// [string] The background color for text with no other color attributes.
// background_color = "rgb(16, 16, 16)"
// [string] CSS value of the background image. Empty string for no image.
// For example:
// "url(https://goo.gl/anedTK) linear-gradient(top bottom, blue, red)"
// background_image = ""
// [string] CSS value of the background image size. Defaults to none.
// background_size = ""
// [string] CSS value of the background image position.
// For example:
// "10% 10% center"
// background_position = ""
// [bool] If true, the backspace should send BS ('\x08', aka ^H). Otherwise the backspace key should send '\x7f'.
// backspace_sends_backspace = false
// [map[string]map[string]string]
// A nested map where each property is the character set code and the value is a map that is a sparse array itself.
// In that sparse array, each property is the received character and the value is the displayed character.
// For example:
// {"0" = {"+" = "\u2192"
// "," = "\u2190"
// "-" = "\u2191"
// "." = "\u2193"
// "0" = "\u2588"}}
// character_map_overrides = null
// [bool] Whether or not to close the window when the command exits.
// close_on_exit = true
// [bool] Whether or not to blink the cursor by default.
// cursor_blink = false
// [2[int]] The cursor blink rate in milliseconds.
// A two element array, the first of which is how long the cursor should be on, second is how long it should be off.
// cursor_blink_cycle = [1000, 500]
// [string] The color of the visible cursor.
// cursor_color = "rgba(255, 0, 0, 0.5)"
// [[]string]
// Override colors in the default palette.
// This can be specified as an array or an object.
// Values can be specified as almost any css color value.
// This includes #RGB, #RRGGBB, rgb(...), rgba(...), and any color names that are also part of the stock X11 rgb.txt file.
// You can use 'null' to specify that the default value should be not be changed.
// This is useful for skipping a small number of indicies when the value is specified as an array.
// color_palette_overrides = null
// [bool] Automatically copy mouse selection to the clipboard.
copy_on_select = true
// [bool] Whether to use the default window copy behaviour
//use_default_window_copy = false
// [bool] Whether to clear the selection after copying.
clear_selection_after_copy = false
// [bool] If true, Ctrl-Plus/Minus/Zero controls zoom.
// If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_, Ctrl-Plus/Zero do nothing.
// ctrl_plus_minus_zero_zoom = true
// [bool] Ctrl+C copies if true, send ^C to host if false.
// Ctrl+Shift+C sends ^C to host if true, copies if false.
// ctrl_c_copy = true
// [bool] Ctrl+V pastes if true, send ^V to host if false.
// Ctrl+Shift+V sends ^V to host if true, pastes if false.
// ctrl_v_paste = true
// [bool] Set whether East Asian Ambiguous characters have two column width.
// east_asian_ambiguous_as_two_column = false
// [bool] True to enable 8-bit control characters, false to ignore them.
// We'll respect the two-byte versions of these control characters regardless of this setting.
// enable_8_bit_control = false
// [enum(null, true, false)]
// True if we should use bold weight font for text with the bold/bright attribute.
// False to use the normal weight font.
// Null to autodetect.
// enable_bold = null
// [bool] True if we should use bright colors (8-15 on a 16 color palette) for any text with the bold attribute.
// False otherwise.
// enable_bold_as_bright = true
// [bool] Show a message in the terminal when the host writes to the clipboard.
// enable_clipboard_notice = true
// [bool] Allow the host to write directly to the system clipboard.
// enable_clipboard_write = true
// [bool] Respect the host's attempt to change the cursor blink status using DEC Private Mode 12.
// enable_dec12 = false
// [map[string]string] The default environment variables, as an object.
// environment = {"TERM" = "xterm-256color"}
// [string] Default font family for the terminal text.
// font_family = "'DejaVu Sans Mono', 'Everson Mono', FreeMono, 'Menlo', 'Terminal', monospace"
// [int] The default font size in pixels.
// font_size = 15
// [string] CSS font-smoothing property.
// font_smoothing = "antialiased"
// [string] The foreground color for text with no other color attributes.
// foreground_color = "rgb(240, 240, 240)"
// [bool] If true, home/end will control the terminal scrollbar and shift home/end will send the VT keycodes.
// If false then home/end sends VT codes and shift home/end scrolls.
// home_keys_scroll = false
// [map[string]string]
// A map of key sequence to key actions.
// Key sequences include zero or more modifier keys followed by a key code.
// Key codes can be decimal or hexadecimal numbers, or a key identifier.
// Key actions can be specified a string to send to the host, or an action identifier.
// For a full list of key code and action identifiers, see https://goo.gl/8AoD09.
// Sample keybindings:
// {"Ctrl-Alt-K" = "clearScrollback"
// "Ctrl-Shift-L"= "PASS"
// "Ctrl-H" = "'HELLO\n'"}
// keybindings = null
// [int] Max length of a DCS, OSC, PM, or APS sequence before we give up and ignore the code.
// max_string_sequence = 100000
// [bool] If true, convert media keys to their Fkey equivalent.
// If false, let the browser handle the keys.
// media_keys_are_fkeys = false
// [bool] Set whether the meta key sends a leading escape or not.
// meta_sends_escape = true
// [enum(null, 0, 1, 2, 3, 4, 5, 6]
// Mouse paste button, or null to autodetect.
// For autodetect, we'll try to enable middle button paste for non-X11 platforms.
// On X11 we move it to button 3.
// mouse_paste_button = null
// [bool] If true, page up/down will control the terminal scrollbar and shift page up/down will send the VT keycodes.
// If false then page up/down sends VT codes and shift page up/down scrolls.
// page_keys_scroll = false
// [enum(null, true, false)]
// Set whether we should pass Alt-1..9 to the browser.
// This is handy when running hterm in a browser tab, so that you don't lose Chrome's "switch to tab" keyboard accelerators.
// When not running in a tab it's better to send these keys to the host so they can be used in vim or emacs.
// If true, Alt-1..9 will be handled by the browser.
// If false, Alt-1..9 will be sent to the host.
// If null, autodetect based on browser platform and window type.
// pass_alt_number = null
// [enum(null, true, false)]
// Set whether we should pass Ctrl-1..9 to the browser.
// This is handy when running hterm in a browser tab, so that you don't lose Chrome's "switch to tab" keyboard accelerators.
// When not running in a tab it's better to send these keys to the host so they can be used in vim or emacs.
// If true, Ctrl-1..9 will be handled by the browser.
// If false, Ctrl-1..9 will be sent to the host.
// If null, autodetect based on browser platform and window type.
// pass_ctrl_number = null
// [enum(null, true, false)]
// Set whether we should pass Meta-1..9 to the browser.
// This is handy when running hterm in a browser tab, so that you don't lose Chrome's "switch to tab" keyboard accelerators.
// When not running in a tab it's better to send these keys to the host so they can be used in vim or emacs.
// If true, Meta-1..9 will be handled by the browser.
// If false, Meta-1..9 will be sent to the host. If null, autodetect based on browser platform and window type.
// pass_meta_number = null
// [bool] Set whether meta-V gets passed to host.
// pass_meta_v = true
// [bool] If true, scroll to the bottom on any keystroke.
// scroll_on_keystroke = true
// [bool] If true, scroll to the bottom on terminal output.
// scroll_on_output = false
// [bool] The vertical scrollbar mode.
// scrollbar_visible = true
// [int] The multiplier for the pixel delta in mousewheel event caused by the scroll wheel. Alters how fast the page scrolls.
// scroll_wheel_move_multiplier = 1
// [bool] Shift + Insert pastes if true, sent to host if false.
// shift_insert_paste = true
// [string] URL of user stylesheet to include in the terminal document.
// user_css = ""
// }

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2017 Iwasaki Yudai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

222
src/mod/sshprox/sshprox.go Normal file
View File

@@ -0,0 +1,222 @@
package sshprox
import (
"embed"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/reverseproxy"
"imuslab.com/zoraxy/mod/utils"
"imuslab.com/zoraxy/mod/websocketproxy"
)
/*
SSH Proxy
This is a tool to bind gotty into Zoraxy
so that you can do something similar to
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
}
type Instance struct {
UUID string
ExecPath string
RemoteAddr string
RemotePort int
AssignedPort int
conn *reverseproxy.ReverseProxy //HTTP proxy
tty *exec.Cmd //SSH connection ported to web interface
Parent *Manager
}
func NewSSHProxyManager() *Manager {
return &Manager{
StartingPort: 14810,
Instances: []*Instance{},
}
}
//Get the next free port in the list
func (m *Manager) GetNextPort() int {
nextPort := m.StartingPort
occupiedPort := make(map[int]bool)
for _, instance := range m.Instances {
occupiedPort[instance.AssignedPort] = true
}
for {
if !occupiedPort[nextPort] {
return nextPort
}
nextPort++
}
}
func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) {
targetInstance, err := m.GetInstanceById(instanceId)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if targetInstance.tty == nil {
//Server side already closed
http.Error(w, "Connection already closed", http.StatusInternalServerError)
return
}
r.Header.Set("X-Forwarded-Host", r.Host)
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("A-Upgrade", "websocket")
requestURL = strings.TrimPrefix(requestURL, "/")
u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
wspHandler := websocketproxy.NewProxy(u)
wspHandler.ServeHTTP(w, r)
return
}
targetInstance.conn.ProxyHTTP(w, r)
}
func (m *Manager) GetInstanceById(instanceId string) (*Instance, error) {
for _, instance := range m.Instances {
if instance.UUID == instanceId {
return instance, nil
}
}
return nil, fmt.Errorf("instance not found: %s", instanceId)
}
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"
}
//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) {
//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
realpath, err := filepath.Abs(execPath)
if err != nil {
return nil, err
}
thisInstance := Instance{
UUID: uuid.New().String(),
ExecPath: realpath,
AssignedPort: -1,
Parent: m,
}
m.Instances = append(m.Instances, &thisInstance)
return &thisInstance, nil
}
//Create a new Connection to target address
func (i *Instance) CreateNewConnection(listenPort int, username string, remoteIpAddr string, remotePort int) error {
//Create a gotty instance
connAddr := remoteIpAddr
if username != "" {
connAddr = username + "@" + remoteIpAddr
}
configPath := filepath.Join(filepath.Dir(i.ExecPath), ".gotty")
title := username + "@" + remoteIpAddr
if remotePort != 22 {
title = title + ":" + strconv.Itoa(remotePort)
}
sshCommand := []string{"ssh", "-t", connAddr, "-p", strconv.Itoa(remotePort)}
cmd := exec.Command(i.ExecPath, "-w", "-p", strconv.Itoa(listenPort), "--once", "--config", configPath, "--title-format", title, "bash", "-c", strings.Join(sshCommand, " "))
cmd.Dir = filepath.Dir(i.ExecPath)
cmd.Env = append(os.Environ(), "TERM=xterm")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
go func() {
cmd.Run()
i.Destroy()
}()
i.tty = cmd
i.AssignedPort = listenPort
i.RemoteAddr = remoteIpAddr
i.RemotePort = remotePort
//Create a new proxy agent for this root
path, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(listenPort))
if err != nil {
return err
}
//Create new proxy objects to the proxy
proxy := reverseproxy.NewReverseProxy(path)
i.conn = proxy
return nil
}
func (i *Instance) Destroy() {
// Remove the instance from the Manager's Instances list
for idx, inst := range i.Parent.Instances {
if inst == i {
// Remove the instance from the slice by swapping it with the last instance and slicing the slice
i.Parent.Instances[len(i.Parent.Instances)-1], i.Parent.Instances[idx] = i.Parent.Instances[idx], i.Parent.Instances[len(i.Parent.Instances)-1]
i.Parent.Instances = i.Parent.Instances[:len(i.Parent.Instances)-1]
break
}
}
}

72
src/mod/sshprox/utils.go Normal file
View File

@@ -0,0 +1,72 @@
package sshprox
import (
"fmt"
"net"
"net/url"
"runtime"
"strings"
"time"
)
//Rewrite url based on proxy root
func RewriteURL(rooturl string, requestURL string) (*url.URL, error) {
rewrittenURL := strings.TrimPrefix(requestURL, rooturl)
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
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
}