Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
elisescu committed Apr 5, 2018
0 parents commit 9f0a147
Show file tree
Hide file tree
Showing 27 changed files with 7,868 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
**/node_modules/
.DS_Store
bundle.js
playground/
.vscode/
tty_sender
tty_server
tmp-*
33 changes: 33 additions & 0 deletions Makefile
@@ -0,0 +1,33 @@
TTY_SERVER=tty_server
TTY_SENDER=tty_sender

TTY_SERVER_SRC=$(wildcard ./tty-server/*.go)
TTY_SENDER_SRC=$(wildcard ./tty-sender/*.go)
EXTRA_BUILD_DEPS=$(wildcard ./common/*go)

all: $(TTY_SERVER) $(TTY_SENDER)
@echo "All done"

$(TTY_SERVER): $(TTY_SERVER_SRC) $(EXTRA_BUILD_DEPS)
go build -o $@ $(TTY_SERVER_SRC)

$(TTY_SENDER): $(TTY_SENDER_SRC) $(EXTRA_BUILD_DEPS)
go build -o $@ $(TTY_SENDER_SRC)

frontend: FORCE
cd frontend && npm run build && cd -

clean:
@rm -f $(TTY_SERVER) $(TTY_SENDER)
@echo "Cleaned"

runs: $(TTY_SERVER)
@./$(TTY_SERVER) --url http://localhost:9090 --web_address :9090 --sender_address :7654

runc: $(TTY_SENDER)
@./$(TTY_SENDER) --logfile output.log

test:
@go test github.com/elisescu/tty-share/testing -v

FORCE:
24 changes: 24 additions & 0 deletions README.md
@@ -0,0 +1,24 @@
TTY Share
========

A small tool to allow sharing a terminal command with others via Internet.
Shortly, the user can start a command in the terminal and then others can watch that command via
Internet in the browser.
More info to come.


Run the code
===========

* Build the frontend
```
cd tty-share/frontend
npm install
npm run build
```

* Run the server
```
cd tty-share
make run
```
132 changes: 132 additions & 0 deletions common/protocol.go
@@ -0,0 +1,132 @@
package common

import (
"encoding/json"
"errors"
"fmt"
"io"
)

type ProtocolMessageIDType string

const (
MsgIDSenderInitRequest = "SenderInitRequest"
MsgIDSenderInitReply = "SenderInitReply"
MsgIDReceiverInitRequest = "ReceiverInitRequest"
MsgIDReceiverInitReply = "ReceiverInitReply"
MsgIDWrite = "Write"
MsgIDWinSize = "WinSize"
)

type MsgAll struct {
Type ProtocolMessageIDType
Data []byte
}

type MsgTTYSenderInitRequest struct {
Salt string
PasswordVerifierA string
}

type MsgTTYSenderInitReply struct {
ReceiverURLWebReadWrite string
}

type MsgTTYReceiverInitRequest struct {
ChallengeReply string
}

type MsgTTYReceiverInitReply struct {
}

type MsgTTYWrite struct {
Data []byte
Size int
}

type MsgTTYWinSize struct {
Cols int
Rows int
}

func ReadAndUnmarshalMsg(reader io.Reader, aMessage interface{}) (err error) {
var wrapperMsg MsgAll
// Wait here for the right message to come
dec := json.NewDecoder(reader)
err = dec.Decode(&wrapperMsg)

if err != nil {
return errors.New("Cannot decode message: " + err.Error())
}

err = json.Unmarshal(wrapperMsg.Data, aMessage)

if err != nil {
return errors.New("Cannot decode message: " + err.Error())
}
return
}

func MarshalMsg(aMessage interface{}) (_ []byte, err error) {
var msg MsgAll

if initRequestMsg, ok := aMessage.(MsgTTYSenderInitRequest); ok {
msg.Type = MsgIDSenderInitRequest
msg.Data, err = json.Marshal(initRequestMsg)
if err != nil {
return
}
return json.Marshal(msg)
}

if initReplyMsg, ok := aMessage.(MsgTTYSenderInitReply); ok {
msg.Type = MsgIDSenderInitReply
msg.Data, err = json.Marshal(initReplyMsg)
if err != nil {
return
}
return json.Marshal(msg)
}

if writeMsg, ok := aMessage.(MsgTTYWrite); ok {
msg.Type = MsgIDWrite
msg.Data, err = json.Marshal(writeMsg)
//fmt.Printf("Sent write message %s\n", string(writeMsg.Data))
if err != nil {
return
}
return json.Marshal(msg)
}

if winChangedMsg, ok := aMessage.(MsgTTYWinSize); ok {
msg.Type = MsgIDWinSize
msg.Data, err = json.Marshal(winChangedMsg)
if err != nil {
return
}
return json.Marshal(msg)
}

return nil, nil
}

func MarshalAndWriteMsg(writer io.Writer, aMessage interface{}) (err error) {
b, err := MarshalMsg(aMessage)

if err != nil {
return
}

n, err := writer.Write(b)

if n != len(b) {
err = fmt.Errorf("Unable to write : wrote %d out of %d bytes", n, len(b))
return
}

if err != nil {
return
}

return
}
118 changes: 118 additions & 0 deletions common/tty_protocol_conn.go
@@ -0,0 +1,118 @@
package common

import (
"encoding/json"
"io"
)

type ServerSessionInfo struct {
URLWebReadWrite string
}

type ReceiverSessionInfo struct {
}

type SenderSessionInfo struct {
Salt string
PasswordVerifierA string
}

// TTYProtocolConn is the interface used to communicate with the sending (master) side of the TTY session
type TTYProtocolConn struct {
netConnection io.ReadWriteCloser
jsonDecoder *json.Decoder
}

func NewTTYProtocolConn(conn io.ReadWriteCloser) *TTYProtocolConn {
return &TTYProtocolConn{
netConnection: conn,
jsonDecoder: json.NewDecoder(conn),
}
}

func (protoConn *TTYProtocolConn) ReadMessage() (msg MsgAll, err error) {
// TODO: perhaps read here the error, and transform it to something that's understandable
// from the outside in the context of this object
err = protoConn.jsonDecoder.Decode(&msg)
return
}

func (protoConn *TTYProtocolConn) SetWinSize(cols, rows int) error {
msgWinChanged := MsgTTYWinSize{
Cols: cols,
Rows: rows,
}
return MarshalAndWriteMsg(protoConn.netConnection, msgWinChanged)
}

func (protoConn *TTYProtocolConn) Close() error {
return protoConn.netConnection.Close()
}

// Function to send data from one the sender to the server and the other way around.
func (protoConn *TTYProtocolConn) Write(buff []byte) (int, error) {
msgWrite := MsgTTYWrite{
Data: buff,
Size: len(buff),
}
return len(buff), MarshalAndWriteMsg(protoConn.netConnection, msgWrite)
}

func (protoConn *TTYProtocolConn) WriteRawData(buff []byte) (int, error) {
return protoConn.netConnection.Write(buff)
}

// Function to be called on the sender side, and which blocks until the protocol has been
// initialised
func (protoConn *TTYProtocolConn) InitSender(senderInfo SenderSessionInfo) (serverInfo ServerSessionInfo, err error) {
var replyMsg MsgTTYSenderInitReply

msgInitReq := MsgTTYSenderInitRequest{
Salt: senderInfo.Salt,
PasswordVerifierA: senderInfo.PasswordVerifierA,
}

// Send the InitRequest message
if err = MarshalAndWriteMsg(protoConn.netConnection, msgInitReq); err != nil {
return
}

// Wait here for the InitReply message
if err = ReadAndUnmarshalMsg(protoConn.netConnection, &replyMsg); err != nil {
return
}

serverInfo = ServerSessionInfo{
URLWebReadWrite: replyMsg.ReceiverURLWebReadWrite,
}
return
}

func (protoConn *TTYProtocolConn) InitServer(serverInfo ServerSessionInfo) (senderInfo SenderSessionInfo, err error) {
var requestMsg MsgTTYSenderInitRequest

// Wait here and expect a InitRequest message
if err = ReadAndUnmarshalMsg(protoConn.netConnection, &requestMsg); err != nil {
return
}

// Send back a InitReply message
if err = MarshalAndWriteMsg(protoConn.netConnection, MsgTTYSenderInitReply{
ReceiverURLWebReadWrite: serverInfo.URLWebReadWrite}); err != nil {
return
}

senderInfo = SenderSessionInfo{
Salt: requestMsg.Salt,
PasswordVerifierA: requestMsg.PasswordVerifierA,
}
return
}

func (protoConn *TTYProtocolConn) InitServerReceiverConn(serverInfo ServerSessionInfo) (receiverInfo ReceiverSessionInfo, err error) {
return
}

func (protoConn *TTYProtocolConn) InitReceiverServerConn(receiverInfo ReceiverSessionInfo) (serverInfo ServerSessionInfo, err error) {
return
}
58 changes: 58 additions & 0 deletions doc/brainstorm.md
@@ -0,0 +1,58 @@
## Overview of the architecture

```
B
A +-------------+
+-----------------+ | | C
| TTYSender(cmd) | <+-> | TTYProxy | +-------------------+
+-----------------+ | Server | <-+->| TTYReceiver(web) |
| | +-------------------+
| |
| | D
| | +-------------------+
| | <-+->| TTYReceiver(ssh) |
| | +-------------------+
| |
M | | N
+-----------------+ | | +-------------------+
| TTYSender(cmd) | <+-> | | <-+->| TTYREceiver(web) |
+-----------------+ +-------------+ +-------------------+
```
##
```
A <-> C, D
M <-> N
```

### A
Where A is the TTYSender, which will be used by the user Alice to share her terminal session. She will start it in the command line, with something like:
```
tty-share bash
```
If everything is successful, A will output to stdout 3 URLs, which, something like:
```
1. read-only: https://tty-share.io/s/0ENHQGjqaB
2. write: https://tty-share.io/s/4HGFN8jahg
3. terminal: ssh://0ENHQGjqaB@tty-share.io.com -p1234
4. admin: http://localhost:5456/admin
```
Url number 1. will provide read-only access to the command shared. Which means the user will not be able to interact with the terminal.
Url number 2. will allow the user to interact with the terminale.
Url number 3. ssh access, to follow the remote command from a remote terminal.
Url number 4. provides an interface to control various options related to sharing.
### B
B is the TTYProxyServer, which will be publicly accessible and to which the TTYSender will connect to. On the TTYProxyServer will be created te sessions (read-only and write), and URLs will be returned back to A. Whent the command that A started exits, the session will end, so C should know.
### C
C is the browser via which user Chris will receive the terminal which Alice has shared.

### Corner cases
Corner cases to test for:
* AB connection cannot be done
* AB is established, but CB can't be done
* AB connection can go down
* CB connection can go down:
- The websocket connection can go down
- The browser refreshed. Command is still running, so the session is still valid
* All users from the C side close their connection
* The commmand finishes
2 changes: 2 additions & 0 deletions doc/journal.md
@@ -0,0 +1,2 @@
# Sat Oct 28 16:42:04 CEST 2017
Got the first end-to-end communication between the tty-share command and the

0 comments on commit 9f0a147

Please sign in to comment.