123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- // +build !windows
- package main
- import (
- "encoding/json"
- "io"
- "net/http"
- "os"
- "os/exec"
- "syscall"
- "unsafe"
- "github.com/sirupsen/logrus"
- "github.com/gorilla/websocket"
- "github.com/kr/pty"
- )
- type windowSize struct {
- Rows uint16 `json:"rows"`
- Cols uint16 `json:"cols"`
- X uint16
- Y uint16
- }
- func lookShellPath() (string, error) {
- shPath, err := exec.LookPath("bash")
- if err == nil {
- return shPath, nil
- }
- return exec.LookPath("sh")
- }
- func handleTerminalWebsocket(w http.ResponseWriter, r *http.Request) {
- l := logrus.WithField("remoteaddr", r.RemoteAddr)
- conn, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- l.WithError(err).Error("Unable to upgrade connection")
- return
- }
- shPath, err := lookShellPath()
- if err != nil {
- l.WithError(err).Error("Unable find shell path")
- return
- }
- cmd := exec.Command(shPath, "-l")
- cmd.Env = append(os.Environ(), "TERM=xterm")
- tty, err := pty.Start(cmd)
- if err != nil {
- l.WithError(err).Error("Unable to start pty/cmd")
- conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
- return
- }
- defer func() {
- cmd.Process.Kill()
- cmd.Process.Wait()
- tty.Close()
- conn.Close()
- }()
- go func() {
- for {
- buf := make([]byte, 1024)
- read, err := tty.Read(buf)
- if err != nil {
- conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
- l.WithError(err).Error("Unable to read from pty/cmd")
- return
- }
- conn.WriteMessage(websocket.BinaryMessage, buf[:read])
- }
- }()
- for {
- messageType, reader, err := conn.NextReader()
- if err != nil {
- l.WithError(err).Error("Unable to grab next reader")
- return
- }
- if messageType == websocket.TextMessage {
- l.Warn("Unexpected text message")
- conn.WriteMessage(websocket.TextMessage, []byte("Unexpected text message"))
- continue
- }
- dataTypeBuf := make([]byte, 1)
- read, err := reader.Read(dataTypeBuf)
- if err != nil {
- l.WithError(err).Error("Unable to read message type from reader")
- conn.WriteMessage(websocket.TextMessage, []byte("Unable to read message type from reader"))
- return
- }
- if read != 1 {
- l.WithField("bytes", read).Error("Unexpected number of bytes read")
- return
- }
- switch dataTypeBuf[0] {
- case 0:
- copied, err := io.Copy(tty, reader)
- if err != nil {
- l.WithError(err).Errorf("Error after copying %d bytes", copied)
- }
- case 1:
- decoder := json.NewDecoder(reader)
- resizeMessage := windowSize{}
- err := decoder.Decode(&resizeMessage)
- if err != nil {
- conn.WriteMessage(websocket.TextMessage, []byte("Error decoding resize message: "+err.Error()))
- continue
- }
- l.WithField("resizeMessage", resizeMessage).Info("Resizing terminal")
- _, _, errno := syscall.Syscall(
- syscall.SYS_IOCTL,
- tty.Fd(),
- syscall.TIOCSWINSZ,
- uintptr(unsafe.Pointer(&resizeMessage)),
- )
- if errno != 0 {
- l.WithError(syscall.Errno(errno)).Error("Unable to resize terminal")
- }
- default:
- l.WithField("dataType", dataTypeBuf[0]).Error("Unknown data type")
- }
- }
- }
|