wsshell.go 5.2 KB

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