term_posix.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. // +build !windows
  2. package main
  3. import (
  4. "encoding/json"
  5. "io"
  6. "net/http"
  7. "os"
  8. "os/exec"
  9. "syscall"
  10. "unsafe"
  11. "github.com/sirupsen/logrus"
  12. "github.com/gorilla/websocket"
  13. "github.com/kr/pty"
  14. )
  15. type windowSize struct {
  16. Rows uint16 `json:"rows"`
  17. Cols uint16 `json:"cols"`
  18. X uint16
  19. Y uint16
  20. }
  21. func lookShellPath() (string, error) {
  22. shPath, err := exec.LookPath("bash")
  23. if err == nil {
  24. return shPath, nil
  25. }
  26. return exec.LookPath("sh")
  27. }
  28. func handleTerminalWebsocket(w http.ResponseWriter, r *http.Request) {
  29. l := logrus.WithField("remoteaddr", r.RemoteAddr)
  30. conn, err := upgrader.Upgrade(w, r, nil)
  31. if err != nil {
  32. l.WithError(err).Error("Unable to upgrade connection")
  33. return
  34. }
  35. shPath, err := lookShellPath()
  36. if err != nil {
  37. l.WithError(err).Error("Unable find shell path")
  38. return
  39. }
  40. cmd := exec.Command(shPath, "-l")
  41. cmd.Env = append(os.Environ(), "TERM=xterm")
  42. tty, err := pty.Start(cmd)
  43. if err != nil {
  44. l.WithError(err).Error("Unable to start pty/cmd")
  45. conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
  46. return
  47. }
  48. defer func() {
  49. cmd.Process.Kill()
  50. cmd.Process.Wait()
  51. tty.Close()
  52. conn.Close()
  53. }()
  54. go func() {
  55. for {
  56. buf := make([]byte, 1024)
  57. read, err := tty.Read(buf)
  58. if err != nil {
  59. conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
  60. l.WithError(err).Error("Unable to read from pty/cmd")
  61. return
  62. }
  63. conn.WriteMessage(websocket.BinaryMessage, buf[:read])
  64. }
  65. }()
  66. for {
  67. messageType, reader, err := conn.NextReader()
  68. if err != nil {
  69. l.WithError(err).Error("Unable to grab next reader")
  70. return
  71. }
  72. if messageType == websocket.TextMessage {
  73. l.Warn("Unexpected text message")
  74. conn.WriteMessage(websocket.TextMessage, []byte("Unexpected text message"))
  75. continue
  76. }
  77. dataTypeBuf := make([]byte, 1)
  78. read, err := reader.Read(dataTypeBuf)
  79. if err != nil {
  80. l.WithError(err).Error("Unable to read message type from reader")
  81. conn.WriteMessage(websocket.TextMessage, []byte("Unable to read message type from reader"))
  82. return
  83. }
  84. if read != 1 {
  85. l.WithField("bytes", read).Error("Unexpected number of bytes read")
  86. return
  87. }
  88. switch dataTypeBuf[0] {
  89. case 0:
  90. copied, err := io.Copy(tty, reader)
  91. if err != nil {
  92. l.WithError(err).Errorf("Error after copying %d bytes", copied)
  93. }
  94. case 1:
  95. decoder := json.NewDecoder(reader)
  96. resizeMessage := windowSize{}
  97. err := decoder.Decode(&resizeMessage)
  98. if err != nil {
  99. conn.WriteMessage(websocket.TextMessage, []byte("Error decoding resize message: "+err.Error()))
  100. continue
  101. }
  102. l.WithField("resizeMessage", resizeMessage).Info("Resizing terminal")
  103. _, _, errno := syscall.Syscall(
  104. syscall.SYS_IOCTL,
  105. tty.Fd(),
  106. syscall.TIOCSWINSZ,
  107. uintptr(unsafe.Pointer(&resizeMessage)),
  108. )
  109. if errno != 0 {
  110. l.WithError(syscall.Errno(errno)).Error("Unable to resize terminal")
  111. }
  112. default:
  113. l.WithField("dataType", dataTypeBuf[0]).Error("Unknown data type")
  114. }
  115. }
  116. }