wsshell.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. package wsshell
  2. import (
  3. "bufio"
  4. "io"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "regexp"
  12. "runtime"
  13. "strings"
  14. "time"
  15. "github.com/gorilla/websocket"
  16. )
  17. /*
  18. Bash Module
  19. author: tobychui
  20. This module handles the connection of bash terminal to websocket interface
  21. */
  22. var upgrader = websocket.Upgrader{
  23. ReadBufferSize: 1024,
  24. WriteBufferSize: 1024,
  25. }
  26. type Terminal struct {
  27. cwd string //Current Working Directory
  28. }
  29. func NewWebSocketShellTerminal() *Terminal {
  30. baseCWD, _ := filepath.Abs("./")
  31. return &Terminal{
  32. cwd: baseCWD,
  33. }
  34. }
  35. func (t *Terminal) HandleOpen(w http.ResponseWriter, r *http.Request) {
  36. //Upgrade the connection to WebSocket connection
  37. c, err := upgrader.Upgrade(w, r, nil)
  38. if err != nil {
  39. log.Println(err)
  40. w.WriteHeader(http.StatusInternalServerError)
  41. w.Write([]byte("500 - Websocket Upgrade Failed"))
  42. return
  43. }
  44. //Check if the system is running on windows or linux. Use cmd and bash
  45. var cmd *exec.Cmd
  46. if runtime.GOOS == "windows" {
  47. cmd = exec.Command("cmd")
  48. } else if runtime.GOOS == "linux" {
  49. cmd = exec.Command("/bin/bash")
  50. } else {
  51. //Currently not supported.
  52. c.WriteMessage(1, []byte("[ERROR] Host Platform not supported: " + runtime.GOOS))
  53. w.WriteHeader(http.StatusInternalServerError)
  54. w.Write([]byte("500 - Host OS Not supported"))
  55. return
  56. }
  57. if cmd == nil {
  58. w.WriteHeader(http.StatusInternalServerError)
  59. w.Write([]byte("500 -Internal Server Error"))
  60. return
  61. }
  62. //Create pipe to all interfaces: STDIN, OUT AND ERR
  63. stdout, err := cmd.StdoutPipe()
  64. if err != nil {
  65. log.Println(err)
  66. return
  67. }
  68. //Pipe stderr to stdout
  69. cmd.Stderr = cmd.Stdout
  70. /*
  71. stderr, err := cmd.StderrPipe()
  72. if err != nil {
  73. log.Println(err)
  74. return
  75. }
  76. */
  77. stdin, err := cmd.StdinPipe()
  78. if err != nil {
  79. log.Println(err)
  80. return
  81. }
  82. //Start the shell
  83. if err := cmd.Start(); err != nil {
  84. log.Println(err)
  85. return
  86. }
  87. //Start listening
  88. go func() {
  89. //s := bufio.NewScanner(io.MultiReader(stdout, stderr))
  90. s := bufio.NewScanner(io.MultiReader(stdout))
  91. s.Split(customSplitter)
  92. for s.Scan() {
  93. resp := s.Bytes()
  94. respstring := string(resp)
  95. if runtime.GOOS == "windows" {
  96. //Strip out all non ASCII characters
  97. re := regexp.MustCompile("[[:^ascii:]]")
  98. respstring = re.ReplaceAllLiteralString(string(resp), "?")
  99. } else if runtime.GOOS == "linux" {
  100. //Linux. Check if this is an internal test command.
  101. if len(respstring) > 12 && respstring[:12] == "<arozos_pwd>" {
  102. //This is an internal pwd update command
  103. t.cwd = strings.TrimSpace(respstring[12:])
  104. log.Println("Updating cwd: ", t.cwd)
  105. continue
  106. }
  107. }
  108. err := c.WriteMessage(1, []byte(respstring))
  109. //log.Println(string(resp))
  110. if err != nil {
  111. //Fail to write websocket. (Already disconencted?) Terminate the bash
  112. //log.Println(err.Error())
  113. cmd.Process.Kill()
  114. }
  115. }
  116. }()
  117. //Do platform depending stuffs
  118. if runtime.GOOS == "windows" {
  119. //Force codepage to be english
  120. io.WriteString(stdin, "chcp 65001\n")
  121. } else if runtime.GOOS == "linux" {
  122. //Send message of the day
  123. content, err := ioutil.ReadFile("/etc/motd")
  124. if err != nil {
  125. //Unable to read the motd, use the arozos default one
  126. c.WriteMessage(1, []byte("Terminal Connected. Start type something!"))
  127. } else {
  128. c.WriteMessage(1, content)
  129. }
  130. }
  131. //Start looping for inputs
  132. for {
  133. _, message, err := c.ReadMessage()
  134. if err != nil {
  135. //Something went wrong. Close the socket and kill the process
  136. cmd.Process.Kill()
  137. c.Close()
  138. return
  139. }
  140. //Check if the message is exit. If yes, terminate the section
  141. if strings.TrimSpace(string(message)) == "exit" {
  142. //Terminate the execution
  143. cmd.Process.Kill()
  144. //Exit listening loop
  145. break
  146. } else if strings.TrimSpace(string(message)) == "\x003" {
  147. log.Println("WSSHELL SIGKILL RECEIVED")
  148. if runtime.GOOS == "windows" {
  149. //Send kill signal, see if it kill itself
  150. _ = cmd.Process.Signal(os.Kill)
  151. //Nope, just forcefully kill it
  152. err := cmd.Process.Kill()
  153. if err != nil {
  154. c.WriteMessage(1, []byte("[Error] "+err.Error()))
  155. }
  156. } else if runtime.GOOS == "linux" {
  157. //Do it nicely
  158. go func() {
  159. time.Sleep(2 * time.Second)
  160. _ = cmd.Process.Signal(os.Kill)
  161. }()
  162. cmd.Process.Signal(os.Interrupt)
  163. }
  164. } else {
  165. //Push the input valie into the shell with a newline at the last position of the line
  166. if len(string(message)) > 0 && string(message)[len(message)-1:] != "\n" {
  167. message = []byte(string(message) + "\n")
  168. } else if len(string(message)) == 0 {
  169. continue
  170. }
  171. //Write to STDIN
  172. io.WriteString(stdin, string(message)+"\n")
  173. if runtime.GOOS == "linux" {
  174. //Reply what user has typed in on linux
  175. hostname, err := os.Hostname()
  176. if err != nil {
  177. hostname = "arozos"
  178. }
  179. if len(string(message)) > 2 && string(message)[:2] == "cd" {
  180. //Request an update to the pwd
  181. time.Sleep(300 * time.Millisecond)
  182. io.WriteString(stdin, `echo "<arozos_pwd>$PWD"`+"\n")
  183. time.Sleep(300 * time.Millisecond)
  184. }
  185. c.WriteMessage(1, []byte(hostname+":"+t.cwd+" & "+strings.TrimSpace(string(message))))
  186. }
  187. }
  188. }
  189. c.WriteMessage(1, []byte("Exiting session"))
  190. c.Close()
  191. }
  192. func (t *Terminal) Close() {
  193. //Nothing needed to be done
  194. }