From afc909a3588f2b5cfdbf376da41c32ebb5b91eaf Mon Sep 17 00:00:00 2001 From: Vasile Popescu Date: Thu, 1 Oct 2020 23:18:39 +0200 Subject: [PATCH] Add support for joining a session from command line --- client.go | 118 ++++++++++++++++++++++++++++++++++++++++ go.mod | 3 + go.sum | 6 ++ main.go | 15 ++++- server/assets_bundle.go | 14 ++--- server/server.go | 6 +- 6 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 client.go diff --git a/client.go b/client.go new file mode 100644 index 0000000..10c030c --- /dev/null +++ b/client.go @@ -0,0 +1,118 @@ +package main + +import ( + "encoding/json" + "io" + "net/http" + "net/url" + "os" + + ttyServer "github.com/elisescu/tty-share/server" + "github.com/gorilla/websocket" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh/terminal" +) + +type ttyShareClient struct { + url string + connection *websocket.Conn +} + +func newTtyShareClient(url string) *ttyShareClient { + return &ttyShareClient{ + url: url, + connection: nil, + } +} + +type wsTextWriter struct { + conn *websocket.Conn +} + +func (w *wsTextWriter) Write(data []byte) (n int, err error) { + err = w.conn.WriteMessage(websocket.TextMessage, data) + return len(data), err +} + +func (c *ttyShareClient) Run() (err error) { + log.Printf("Starting tty-share client on %s", c.url) + + resp, err := http.Get(c.url) + + if err != nil { + return + } + + // Get the path of the websockts route from the header + wsPath := resp.Header.Get("TTYSHARE-WSPATH") + + // Build the WS URL from the host part of the given http URL and the wsPath + httpURL, err := url.Parse(c.url) + if err != nil { + return + } + wsScheme := "ws" + if httpURL.Scheme == "https" { + wsScheme = "wss" + } + wsURL := wsScheme + "://" + httpURL.Host + wsPath + + log.Printf("Connecting to WS URL: %s", wsURL) + + conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + if err != nil { + log.Fatal("Cannot create the websocket connection:", err) + } + + state, err := terminal.MakeRaw(0) + defer terminal.Restore(0, state) + + c.connection = conn + readLoop := func() { + for { + var msg ttyServer.MsgAll + _, r, err := conn.NextReader() + if err != nil { + log.Infof("Connection closed: %s", err.Error()) + return + } + err = json.NewDecoder(r).Decode(&msg) + if err != nil { + log.Errorf("Cannot read JSON: %s", err.Error()) + } + + switch msg.Type { + case ttyServer.MsgIDWrite: + var msgWrite ttyServer.MsgTTYWrite + err := json.Unmarshal(msg.Data, &msgWrite) + + if err != nil { + log.Errorf("Cannot read JSON: %s", err.Error()) + } + + os.Stdout.Write(msgWrite.Data) + case ttyServer.MsgIDWinSize: + log.Debugf("Got Win Size msg") + } + } + } + + writeLoop := func() { + ww := &wsTextWriter{ + conn: conn, + } + + _, err := io.Copy(ttyServer.NewTTYProtocolWriter(ww), os.Stdin) + + if err != nil { + log.Errorf("Finished io.Copy with %s", err.Error()) + } + } + go writeLoop() + readLoop() + return +} + +func (c *ttyShareClient) Stop() { + c.connection.Close() +} diff --git a/go.mod b/go.mod index 5de6b76..e222ca0 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,10 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce + github.com/jroimartin/gocui v0.4.0 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect github.com/sirupsen/logrus v1.4.2 golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7 // indirect diff --git a/go.sum b/go.sum index 84952c1..ec57f0a 100644 --- a/go.sum +++ b/go.sum @@ -11,9 +11,15 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce h1:7UnVY3T/ZnHUrfviiAgIUjg2PXxsQfs5bphsG8F7Keo= github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8= +github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag= +github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= diff --git a/main.go b/main.go index 6ba69f0..93ed854 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,7 @@ func main() { proxyServerAddress := flag.String("proxy_address", "localhost:9000", "Address of the proxy for public facing connections") readOnly := flag.Bool("readonly", false, "Start a read only session") publicSession := flag.Bool("public", false, "Create a public session") + connectURL := flag.String("connect", "", "Use as client to connect to a remote tty-share, instead of using the browser") flag.Parse() if *versionFlag { @@ -55,7 +56,7 @@ func main() { return } - log.SetLevel(log.ErrorLevel) + log.SetLevel(log.InfoLevel) if *logFileName != "-" { fmt.Printf("Writing logs to: %s\n", *logFileName) logFile, err := os.Create(*logFileName) @@ -71,6 +72,18 @@ func main() { os.Exit(1) } + + if *connectURL != "" { + client := newTtyShareClient(*connectURL) + + err := client.Run() + + if err != nil { + panic(err.Error()) + } + return + } + sessionID := "local" if *publicSession { proxy, err := proxy.NewProxyConnection(*listenAddress, *proxyServerAddress) diff --git a/server/assets_bundle.go b/server/assets_bundle.go index f321ed3..abe6d9b 100644 --- a/server/assets_bundle.go +++ b/server/assets_bundle.go @@ -97,7 +97,7 @@ func _404Css() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "404.css", size: 3393, mode: os.FileMode(420), modTime: time.Unix(1601412768, 0)} + info := bindataFileInfo{name: "404.css", size: 3393, mode: os.FileMode(420), modTime: time.Unix(1601583738, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -117,7 +117,7 @@ func _404Html() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "404.html", size: 601, mode: os.FileMode(420), modTime: time.Unix(1601412768, 0)} + info := bindataFileInfo{name: "404.html", size: 601, mode: os.FileMode(420), modTime: time.Unix(1601583738, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -137,7 +137,7 @@ func _404InHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "404.in.html", size: 616, mode: os.FileMode(420), modTime: time.Unix(1601412768, 0)} + info := bindataFileInfo{name: "404.in.html", size: 616, mode: os.FileMode(420), modTime: time.Unix(1601583738, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -157,12 +157,12 @@ func bootstrapMinCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "bootstrap.min.css", size: 140930, mode: os.FileMode(420), modTime: time.Unix(1601412768, 0)} + info := bindataFileInfo{name: "bootstrap.min.css", size: 140930, mode: os.FileMode(420), modTime: time.Unix(1601583738, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _ttyShareInHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x91\x4d\x6b\xc3\x30\x0c\x86\xef\xfd\x15\x5a\xee\x8d\xae\x63\x38\x39\x8d\xc1\x6e\x85\x6d\xec\xac\xc5\xde\xa2\x92\x8f\x62\xbf\xa4\x0d\x26\xff\x7d\x38\x86\x35\xa5\xf3\xc5\x92\xfc\xe8\xb5\x3e\xcc\x83\x1d\x1b\xcc\x27\x47\x2d\xfa\xae\xde\x99\x7c\x11\x11\x99\xd6\x89\xcd\xe6\xea\xf6\x0e\x42\x4d\x2b\x3e\x38\x54\xc5\xc7\xfb\xcb\xfe\xb1\xd8\x3c\x43\xd1\xb9\x1a\x98\xf7\xa1\x15\xef\x0c\xe7\x40\x96\xe2\xab\x96\xf9\x1a\xed\xbc\xc9\xb3\x3a\x91\xda\xaa\x80\xf3\xbd\x0e\xd2\x15\xb5\x61\xab\xd3\x3f\x44\x70\x80\x0e\x3f\xe1\x9e\x08\x8d\xd7\x13\x28\xf5\x91\x84\x2e\xe0\xa3\x4c\x92\xa3\x9b\x1a\xd3\x39\xeb\x60\xc7\x73\x09\xcc\xaf\x83\x42\xa5\x7b\x16\x08\x55\x14\x6f\xa8\x95\x0c\x07\x41\xfb\x44\x31\x96\x9f\x6f\xc9\x5c\x96\x1b\xe6\xea\x19\xce\x5f\xdd\x57\x14\x7c\x53\x15\x1c\x63\x99\xf2\x0f\xde\x7d\xeb\x65\x59\x38\x40\xa0\x0d\xff\xcd\xaa\x3c\xae\x3d\x6d\x45\x0c\xe7\x31\x19\xce\x0b\xd9\xfd\x06\x00\x00\xff\xff\x8c\xe3\x5d\x6b\xa9\x01\x00\x00") +var _ttyShareInHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x92\xcf\xaf\xdb\x20\x0c\xc7\xef\xef\xaf\xf0\xe3\x4e\xd0\x6e\xd3\x44\x7a\x9a\x26\xed\x56\x69\x9d\x76\x76\x82\x37\x5c\x25\x10\x81\x97\x34\x8a\xf2\xbf\x4f\x09\x52\x9b\xaa\x7b\x5c\xc0\xe6\xeb\x8f\x7f\x80\x7d\x77\xb1\x95\x79\x20\xf0\xd2\x77\xa7\x37\x5b\x36\x00\x00\xeb\x09\x5d\x39\xee\x66\x4f\x82\xd0\x7a\x4c\x99\xa4\x56\x3f\x2f\xdf\xf4\x67\x75\xb8\x16\x96\x8e\x4e\x22\xb3\xce\x1e\x13\x59\x53\x1c\x05\x65\x1e\x2c\xdb\x44\x37\x1f\xe2\xde\xb5\x86\x8b\xe7\x0c\x8e\x47\x40\x91\xc4\xcd\x5f\xa1\x0c\x13\x77\x1d\x34\x04\x89\xd0\x41\x33\x83\x78\x82\x3b\x1d\xda\xd8\xf7\x18\x1c\x4c\x9e\x02\x24\x0c\x10\xc3\xae\x48\xd4\x47\x21\xc8\xec\x08\xb4\x3e\xa4\xd9\xe8\xec\x6a\x75\x47\xe8\xad\x1f\x05\x43\x8a\x12\xf5\x48\x29\x73\x0c\xb5\xfa\xa4\x60\xca\x7a\x40\xf1\xb5\x5a\x96\xea\xd7\x8f\x33\x8a\x5f\x57\x75\xb2\xc6\xf1\xf8\x3f\x1e\xa5\x9e\x03\x76\x1f\x2b\x32\x89\x70\xf8\x93\x5f\x15\xb9\x4d\x3c\x08\x6c\xe3\xdf\x40\x37\x31\x57\x1c\xb1\x78\x0f\xa3\xdd\xd6\xc4\xc1\xc5\xa9\x12\x99\xbf\x07\x16\xc6\xee\x2b\x0a\x42\x0d\xcb\x93\x6a\x57\xe6\xad\xe4\x2f\x70\xa8\xfe\x49\xf3\xb0\xac\x29\xa9\x5e\x2b\xca\xa9\xad\x95\x59\x96\x6a\x8b\x3f\x27\xfa\xcd\xb7\x75\x35\x59\x50\xb8\x35\xf7\x09\x56\xd7\xbd\xa7\x23\xc4\x9a\xf2\xba\xd6\x94\x7f\xf4\xf6\x2f\x00\x00\xff\xff\x73\xbb\x5a\x79\x60\x02\x00\x00") func ttyShareInHtmlBytes() ([]byte, error) { return bindataRead( @@ -177,7 +177,7 @@ func ttyShareInHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "tty-share.in.html", size: 425, mode: os.FileMode(420), modTime: time.Unix(1601412768, 0)} + info := bindataFileInfo{name: "tty-share.in.html", size: 608, mode: os.FileMode(420), modTime: time.Unix(1601583738, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -197,7 +197,7 @@ func ttyShareJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "tty-share.js", size: 404641, mode: os.FileMode(420), modTime: time.Unix(1601412768, 0)} + info := bindataFileInfo{name: "tty-share.js", size: 404641, mode: os.FileMode(420), modTime: time.Unix(1601583738, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/server/server.go b/server/server.go index 9ff72b1..0b23330 100644 --- a/server/server.go +++ b/server/server.go @@ -95,10 +95,14 @@ func NewTTYServer(config TTYServerConfig) (server *TTYServer) { }))) routesHandler.HandleFunc(fmt.Sprintf("/%s/", session), func(w http.ResponseWriter, r *http.Request) { + wsPath := "/" + session + "/ws" templateModel := struct { PathPrefix string WSPath string - }{session, "/" + session + "/ws"} + }{session, wsPath} + // Extract these in constants + w.Header().Add("TTYSHARE-VERSION", "1") + w.Header().Add("TTYSHARE-WSPATH", wsPath) server.handleWithTemplateHtml(w, r, "tty-share.in.html", templateModel) })