sshprox.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package sshprox
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "runtime"
  11. "strconv"
  12. "strings"
  13. "github.com/google/uuid"
  14. "imuslab.com/zoraxy/mod/reverseproxy"
  15. "imuslab.com/zoraxy/mod/utils"
  16. "imuslab.com/zoraxy/mod/websocketproxy"
  17. )
  18. /*
  19. SSH Proxy
  20. This is a tool to bind gotty into Zoraxy
  21. so that you can do something similar to
  22. online ssh terminal
  23. */
  24. type Manager struct {
  25. StartingPort int
  26. Instances []*Instance
  27. }
  28. type Instance struct {
  29. UUID string
  30. ExecPath string
  31. RemoteAddr string
  32. RemotePort int
  33. AssignedPort int
  34. conn *reverseproxy.ReverseProxy //HTTP proxy
  35. tty *exec.Cmd //SSH connection ported to web interface
  36. Parent *Manager
  37. }
  38. func NewSSHProxyManager() *Manager {
  39. return &Manager{
  40. StartingPort: 14810,
  41. Instances: []*Instance{},
  42. }
  43. }
  44. //Get the next free port in the list
  45. func (m *Manager) GetNextPort() int {
  46. nextPort := m.StartingPort
  47. occupiedPort := make(map[int]bool)
  48. for _, instance := range m.Instances {
  49. occupiedPort[instance.AssignedPort] = true
  50. }
  51. for {
  52. if !occupiedPort[nextPort] {
  53. return nextPort
  54. }
  55. nextPort++
  56. }
  57. }
  58. func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) {
  59. targetInstance, err := m.GetInstanceById(instanceId)
  60. if err != nil {
  61. http.Error(w, err.Error(), http.StatusNotFound)
  62. return
  63. }
  64. if targetInstance.tty == nil {
  65. //Server side already closed
  66. http.Error(w, "Connection already closed", http.StatusInternalServerError)
  67. return
  68. }
  69. r.Header.Set("X-Forwarded-Host", r.Host)
  70. requestURL := r.URL.String()
  71. if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
  72. //Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
  73. r.Header.Set("A-Upgrade", "websocket")
  74. requestURL = strings.TrimPrefix(requestURL, "/")
  75. u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
  76. wspHandler := websocketproxy.NewProxy(u)
  77. wspHandler.ServeHTTP(w, r)
  78. return
  79. }
  80. targetInstance.conn.ProxyHTTP(w, r)
  81. }
  82. func (m *Manager) GetInstanceById(instanceId string) (*Instance, error) {
  83. for _, instance := range m.Instances {
  84. if instance.UUID == instanceId {
  85. return instance, nil
  86. }
  87. }
  88. return nil, fmt.Errorf("instance not found: %s", instanceId)
  89. }
  90. func (m *Manager) NewSSHProxy(binaryRoot string) (*Instance, error) {
  91. //Check if the binary exists in system/gotty/
  92. binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH
  93. if runtime.GOOS == "windows" {
  94. binary = binary + ".exe"
  95. }
  96. execPath := filepath.Join(binaryRoot, binary)
  97. if !utils.FileExists(execPath) {
  98. //Binary not found
  99. return nil, errors.New("binary not found at " + execPath)
  100. }
  101. //Convert the binary path to realpath
  102. realpath, err := filepath.Abs(execPath)
  103. if err != nil {
  104. return nil, err
  105. }
  106. thisInstance := Instance{
  107. UUID: uuid.New().String(),
  108. ExecPath: realpath,
  109. AssignedPort: -1,
  110. Parent: m,
  111. }
  112. m.Instances = append(m.Instances, &thisInstance)
  113. return &thisInstance, nil
  114. }
  115. //Create a new Connection to target address
  116. func (i *Instance) CreateNewConnection(listenPort int, username string, remoteIpAddr string, remotePort int) error {
  117. //Create a gotty instance
  118. connAddr := remoteIpAddr
  119. if username != "" {
  120. connAddr = username + "@" + remoteIpAddr
  121. }
  122. cmd := exec.Command(i.ExecPath, "-w", "-p", strconv.Itoa(listenPort), "--once", "ssh", connAddr, "-p", strconv.Itoa(remotePort))
  123. cmd.Stdout = os.Stdout
  124. cmd.Stderr = os.Stderr
  125. go func() {
  126. cmd.Run()
  127. i.Destroy()
  128. }()
  129. i.tty = cmd
  130. i.AssignedPort = listenPort
  131. i.RemoteAddr = remoteIpAddr
  132. i.RemotePort = remotePort
  133. //Create a new proxy agent for this root
  134. path, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(listenPort))
  135. if err != nil {
  136. return err
  137. }
  138. //Create new proxy objects to the proxy
  139. proxy := reverseproxy.NewReverseProxy(path)
  140. i.conn = proxy
  141. return nil
  142. }
  143. func (i *Instance) Destroy() {
  144. // Remove the instance from the Manager's Instances list
  145. for idx, inst := range i.Parent.Instances {
  146. if inst == i {
  147. // Remove the instance from the slice by swapping it with the last instance and slicing the slice
  148. i.Parent.Instances[len(i.Parent.Instances)-1], i.Parent.Instances[idx] = i.Parent.Instances[idx], i.Parent.Instances[len(i.Parent.Instances)-1]
  149. i.Parent.Instances = i.Parent.Instances[:len(i.Parent.Instances)-1]
  150. break
  151. }
  152. }
  153. }