wsterminal.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package wsterminal
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "sync"
  6. "time"
  7. "github.com/gorilla/websocket"
  8. "imuslab.com/arozos/mod/auth"
  9. )
  10. /*
  11. WebSocket Terminal
  12. Author: tobychui
  13. This module is a remote support service that allow
  14. reverse ssh like connection using websocket.
  15. For normal weboscket based shell or WsTTY, see wstty module instead.
  16. */
  17. type Connection struct {
  18. RemoteName string
  19. RemoteUUID string
  20. RemoteIP string
  21. RemoteToken string
  22. ConnectionStartedTime int64
  23. LastOnline int64
  24. connection *websocket.Conn
  25. terminateTicker chan bool
  26. }
  27. type Server struct {
  28. connectionPool sync.Map
  29. upgrader websocket.Upgrader
  30. authAgent *auth.AuthAgent
  31. }
  32. type Client struct {
  33. }
  34. //Create a new Server
  35. func NewWsTerminalServer(authAgent *auth.AuthAgent) *Server {
  36. return &Server{
  37. connectionPool: sync.Map{},
  38. upgrader: websocket.Upgrader{},
  39. authAgent: authAgent,
  40. }
  41. }
  42. //List all the active connection that is current connected to this server
  43. func (s *Server) ListConnections(w http.ResponseWriter, r *http.Request) {
  44. activeConnections := []Connection{}
  45. s.connectionPool.Range(func(key, value interface{}) bool {
  46. activeConnections = append(activeConnections, *value.(*Connection))
  47. return true
  48. })
  49. js, _ := json.Marshal(activeConnections)
  50. sendJSONResponse(w, string(js))
  51. }
  52. //Handle new connections
  53. func (s *Server) HandleConnection(w http.ResponseWriter, r *http.Request) {
  54. //Get the token and validate it
  55. token, err := mv(r, "token", false)
  56. if err != nil {
  57. w.WriteHeader(http.StatusUnauthorized)
  58. w.Write([]byte(`401 Unauthorized - Invalid token given`))
  59. return
  60. }
  61. //Try to get the uuid from connectio
  62. uuid, err := mv(r, "uuid", false)
  63. if err != nil {
  64. uuid = "unknown"
  65. }
  66. //Valida te the token
  67. valid, username := s.authAgent.ValidateAutoLoginToken(token)
  68. if !valid {
  69. //Invalid token
  70. w.WriteHeader(http.StatusUnauthorized)
  71. w.Write([]byte(`401 Unauthorized - Invalid token given`))
  72. return
  73. }
  74. //Create a connection object
  75. thisConnection := Connection{
  76. RemoteName: username,
  77. RemoteUUID: uuid,
  78. RemoteIP: "",
  79. RemoteToken: "",
  80. ConnectionStartedTime: time.Now().Unix(),
  81. LastOnline: time.Now().Unix(),
  82. }
  83. //Check if the same connection already exists. If yes, disconnect the old one
  84. val, ok := s.connectionPool.Load(username)
  85. if ok {
  86. //Connection already exists. Disconenct the old one first
  87. previousConn := val.(*Connection).connection
  88. previousConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
  89. time.Sleep(1 * time.Second)
  90. previousConn.Close()
  91. }
  92. //Upgrade the current connection
  93. c, err := s.upgrader.Upgrade(w, r, nil)
  94. if err != nil {
  95. w.WriteHeader(http.StatusInternalServerError)
  96. w.Write([]byte(`500 Internal Server Error`))
  97. return
  98. }
  99. thisConnection.connection = c
  100. //Create a timer for poking the client and check if it is still online
  101. ticker := time.NewTicker(5 * time.Minute)
  102. done := make(chan bool)
  103. thisConnection.terminateTicker = done
  104. go func(connectionObject Connection) {
  105. for {
  106. select {
  107. case <-done:
  108. //Termination from another thread
  109. return
  110. case <-ticker.C:
  111. //Send a ping signal to the client
  112. if err := connectionObject.connection.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
  113. //Unable to send ping message. Assume closed. Remove from active connection pool
  114. s.connectionPool.Delete(thisConnection.RemoteName)
  115. return
  116. } else {
  117. connectionObject.LastOnline = time.Now().Unix()
  118. }
  119. }
  120. }
  121. }(thisConnection)
  122. //Store the connection object to the connection pool
  123. s.connectionPool.Store(username, &thisConnection)
  124. }