sshprox.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. ReservedPorts map[string]int
  27. Instances []*Instance
  28. }
  29. type Instance struct {
  30. UUID string
  31. ExecPath string
  32. RemoteAddr string
  33. RemotePort int
  34. AssignedPort int
  35. conn *reverseproxy.ReverseProxy //HTTP proxy
  36. tty *exec.Cmd //SSH connection ported to web interface
  37. }
  38. func NewSSHProxyManager() *Manager {
  39. return &Manager{
  40. StartingPort: 14810,
  41. ReservedPorts: map[string]int{},
  42. Instances: []*Instance{},
  43. }
  44. }
  45. //Get the next free port in the list
  46. func (m *Manager) GetNextPort() int {
  47. nextPort := m.StartingPort
  48. for {
  49. if _, exists := m.ReservedPorts[strconv.Itoa(nextPort)]; !exists {
  50. if !isPortInUse(nextPort) {
  51. return nextPort
  52. }
  53. }
  54. if nextPort == 65534 {
  55. return -1
  56. }
  57. nextPort++
  58. }
  59. }
  60. func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWriter, r *http.Request) {
  61. targetInstance, err := m.GetInstanceById(instanceId)
  62. if err != nil {
  63. http.Error(w, err.Error(), http.StatusNotFound)
  64. return
  65. }
  66. if targetInstance.tty == nil {
  67. //Server side already closed
  68. http.Error(w, "Connection already closed", http.StatusInternalServerError)
  69. return
  70. }
  71. r.Header.Set("X-Forwarded-Host", r.Host)
  72. requestURL := r.URL.String()
  73. if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
  74. //Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
  75. r.Header.Set("A-Upgrade", "websocket")
  76. requestURL = strings.TrimPrefix(requestURL, "/")
  77. u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
  78. wspHandler := websocketproxy.NewProxy(u)
  79. wspHandler.ServeHTTP(w, r)
  80. return
  81. }
  82. targetInstance.conn.ProxyHTTP(w, r)
  83. }
  84. func (m *Manager) GetInstanceById(instanceId string) (*Instance, error) {
  85. for _, instance := range m.Instances {
  86. if instance.UUID == instanceId {
  87. return instance, nil
  88. }
  89. }
  90. return nil, fmt.Errorf("instance not found: %s", instanceId)
  91. }
  92. func (m *Manager) NewSSHProxy(binaryRoot string) (*Instance, error) {
  93. //Check if the binary exists in system/gotty/
  94. binary := "gotty_" + runtime.GOOS + "_" + runtime.GOARCH
  95. if runtime.GOOS == "windows" {
  96. binary = binary + ".exe"
  97. }
  98. execPath := filepath.Join(binaryRoot, binary)
  99. if !utils.FileExists(execPath) {
  100. //Binary not found
  101. return nil, errors.New("binary not found at " + execPath)
  102. }
  103. //Convert the binary path to realpath
  104. realpath, err := filepath.Abs(execPath)
  105. if err != nil {
  106. return nil, err
  107. }
  108. thisInstance := Instance{
  109. UUID: uuid.New().String(),
  110. ExecPath: realpath,
  111. AssignedPort: -1,
  112. }
  113. m.Instances = append(m.Instances, &thisInstance)
  114. return &thisInstance, nil
  115. }
  116. //Create a new Connection to target address
  117. func (i *Instance) CreateNewConnection(listenPort int, remoteIpAddr string, remotePort int) error {
  118. //Create a gotty instance
  119. cmd := exec.Command(i.ExecPath, "-w", "-p", strconv.Itoa(listenPort), "--once", "ssh", remoteIpAddr, "-p", strconv.Itoa(remotePort))
  120. cmd.Stdout = os.Stdout
  121. cmd.Stderr = os.Stderr
  122. go func() {
  123. cmd.Run()
  124. }()
  125. i.tty = cmd
  126. i.AssignedPort = listenPort
  127. i.RemoteAddr = remoteIpAddr
  128. i.RemotePort = remotePort
  129. //Create a new proxy agent for this root
  130. path, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(listenPort))
  131. if err != nil {
  132. return err
  133. }
  134. //Create new proxy objects to the proxy
  135. proxy := reverseproxy.NewReverseProxy(path)
  136. i.conn = proxy
  137. return nil
  138. }