Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Shannon Wynter committed May 18, 2016
0 parents commit 8a7f999
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 0 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# TestTerminal

Proof of concept terminal.

This is a small component of a much larger system including authentication, access control and security - do not use it.

## Getting

```
go get github.com/freman/goterm
```

## Running

Not included is https://github.com/sourcelair/xterm.js

```
cd src/github.com/freman/goterm/assets
git clone https://github.com/sourcelair/xterm.js
$GOPATH/bin/goterm -assets `pwd`
```
66 changes: 66 additions & 0 deletions assets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Xterm</title>
<link rel="stylesheet" href="/main.css" />
<link rel="stylesheet" href="/xterm.js/src/xterm.css" />
<link rel="stylesheet" href="/xterm.js/addons/fullscreen/fullscreen.css" />
</head>
<body>
<div id="xterm"></div>

<script src="/xterm.js/src/xterm.js" ></script>
<script src="/xterm.js/addons/fit/fit.js" ></script>
<script src="/xterm.js/addons/fullscreen/fullscreen.js" ></script>
<script>
var term;
var websocket = new WebSocket("ws://" + window.location.hostname + ":" + window.location.port + "/term");
websocket.binaryType = "arraybuffer";

function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}

websocket.onopen = function(evt) {
term = new Terminal({
screenKeys: true,
useStyle: true,
cursorBlink: true,
});

term.on('data', function(data) {
websocket.send(new TextEncoder().encode("\x00" + data));
});

term.on('resize', function(evt) {
websocket.send(new TextEncoder().encode("\x01" + JSON.stringify({cols: evt.cols, rows: evt.rows})))
});

term.on('title', function(title) {
document.title = title;
});

term.open(document.getElementById('xterm'));
term.fit();
websocket.onmessage = function(evt) {
if (evt.data instanceof ArrayBuffer) {
term.write(ab2str(evt.data));
} else {
alert(evt.data)
}
}

websocket.onclose = function(evt) {
term.write("Session terminated");
term.destroy();
}

websocket.onerror = function(evt) {
if (typeof console.log == "function") {
console.log(evt)
}
}
}
</script>
</body>
</html>
29 changes: 29 additions & 0 deletions assets/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
body {
font-family: helvetica, sans-serif, arial;
font-size: 1em;
color: #111;
}

#terminal-container {
width: 960px;
height: 600px;
margin: 0 auto;
padding: 2px;
}

#terminal-container .terminal {
background-color: #111;
color: #fafafa;
padding: 2px;
}

#terminal-container .terminal .terminal-cursor {
background-color: #fafafa;
}

/*
.xterm-rows div {
height: 16px;
}
*/
143 changes: 143 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package main

import (
"encoding/json"
"flag"
"io"
"net/http"
"os"
"os/exec"
"strings"
"syscall"
"unsafe"

log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/kr/pty"
)

type windowSize struct {
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
X uint16
Y uint16
}

var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}

func handleWebsocket(w http.ResponseWriter, r *http.Request) {
l := log.WithField("remoteaddr", r.RemoteAddr)
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
l.WithError(err).Error("Unable to upgrade connection")
return
}

cmd := exec.Command("/bin/bash", "-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 tty.Close()

go func() {
defer conn.Close()
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")
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
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
}
log.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")
}
}
}

func main() {
var listen = flag.String("listen", "127.0.0.1:3000", "Host:port to listen on")
var assetsPath = flag.String("assets", "./assets", "Path to assets")

flag.Parse()

r := mux.NewRouter()

r.HandleFunc("/term", handleWebsocket)
r.PathPrefix("/").Handler(http.FileServer(http.Dir(*assetsPath)))

log.Info("Demo Websocket/Xterm terminal")
log.Warn("Warning, this is a completely insecure daemon that permits anyone to connect and control your computer, please don't run this anywhere")

if !(strings.HasPrefix(*listen, "127.0.0.1") || strings.HasPrefix(*listen, "localhost")) {
log.Warn("Danger Will Robinson - This program has no security built in and should not be exposed beyond localhost, you've been warned")
}

if err := http.ListenAndServe(*listen, r); err != nil {
log.WithError(err).Fatal("Something went wrong with the webserver")
}
}

0 comments on commit 8a7f999

Please sign in to comment.