From db2741e1be131f997aaf052ec4768b3d07e7cf7a Mon Sep 17 00:00:00 2001 From: Zhi Wang Date: Wed, 5 Jan 2022 12:38:52 -0500 Subject: [PATCH] has a websocket shell now! --- assets/main.js | 2 +- go.mod | 4 ++- go.sum | 6 ++++ main.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/assets/main.js b/assets/main.js index c7a8cad..0e737a7 100644 --- a/assets/main.js +++ b/assets/main.js @@ -31,7 +31,7 @@ function createTerminal() { }); term.open(document.getElementById('terminal_view')); - term.resize(120, 36); + term.resize(122, 37); const weblinksAddon = new WebLinksAddon.WebLinksAddon(); term.loadAddon(weblinksAddon); diff --git a/go.mod b/go.mod index 5e1c0e7..12838c7 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/syssecfsu/wsterm go 1.17 require ( + github.com/creack/pty v1.1.17 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.7.7 // indirect github.com/go-playground/locales v0.13.0 // indirect @@ -17,6 +18,7 @@ require ( github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/ugorji/go/codec v1.1.7 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect + golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index 6a84dbf..eeab74b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -42,6 +44,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go index f204fbe..7e2bb4a 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,45 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" + + "os" + "os/exec" + + "github.com/creack/pty" + "golang.org/x/term" ) +func createPty(cmdline string) (*os.File, *term.State, error) { + // Create a shell command. + cmd := exec.Command(cmdline) + + // Start the command with a pty. + ptmx, err := pty.Start(cmd) + + if err != nil { + return nil, nil, err + } + + // Use fixed size, the xterm is initalized as 122x37, + // But we set pty to 120x36. Using fullsize will lead + // some program to misbehaive. + pty.Setsize(ptmx, &pty.Winsize{ + Cols: 120, + Rows: 36, + }) + + // Set stdin in raw mode. This might cause problems in ssh. + // ignore the error if it so happens + termState, err := term.MakeRaw(int(os.Stdin.Fd())) + + if err != nil { + fmt.Println(err) + return ptmx, nil, err + } + + return ptmx, termState, nil +} + var host *string = nil var upgrader = websocket.Upgrader{ @@ -42,15 +79,56 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("Created the websocket") + ptmx, termState, err := createPty("bash") + + defer func() { + //close the terminal and restore the terminal state + if termState != nil { + term.Restore(int(os.Stdin.Fd()), termState) + } + }() + + if err != nil { + fmt.Println("failed to create PTY", err) + return + } + + // pipe the msgs from WS to pty, we need to use goroutine here + go func() { + for { + _, buf, err := conn.ReadMessage() + + if err != nil { + fmt.Println(err) + // We need to close pty so the goroutine and this one can end + // using defer will cause problems + ptmx.Close() + return + } + + _, err = ptmx.Write(buf) + + if err != nil { + fmt.Println(err) + ptmx.Close() + return + } + } + }() + + readBuf := make([]byte, 4096) + for { - msgType, p, err := conn.ReadMessage() + n, err := ptmx.Read(readBuf) if err != nil { fmt.Println(err) + ptmx.Close() return } - if err := conn.WriteMessage(msgType, p); err != nil { + if err = conn.WriteMessage(websocket.BinaryMessage, readBuf[:n]); err != nil { + ptmx.Close() fmt.Println(err) return }