2015-08-21 09:51:43 +00:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2015-08-23 11:40:18 +00:00
|
|
|
"strings"
|
2015-08-21 09:51:43 +00:00
|
|
|
"syscall"
|
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
"github.com/yudai/utf8reader"
|
|
|
|
)
|
|
|
|
|
|
|
|
type clientContext struct {
|
|
|
|
app *App
|
|
|
|
request *http.Request
|
|
|
|
connection *websocket.Conn
|
|
|
|
command *exec.Cmd
|
|
|
|
pty *os.File
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
Input = '0'
|
|
|
|
ResizeTerminal = '1'
|
|
|
|
)
|
|
|
|
|
2015-08-23 11:40:18 +00:00
|
|
|
const (
|
2015-08-27 06:23:54 +00:00
|
|
|
Output = '0'
|
|
|
|
SetWindowTitle = '1'
|
|
|
|
SetPreferences = '2'
|
|
|
|
SetReconnect = '3'
|
2015-08-23 11:40:18 +00:00
|
|
|
)
|
|
|
|
|
2015-08-21 09:51:43 +00:00
|
|
|
type argResizeTerminal struct {
|
|
|
|
Columns float64
|
|
|
|
Rows float64
|
|
|
|
}
|
|
|
|
|
2015-08-23 11:40:18 +00:00
|
|
|
type ContextVars struct {
|
|
|
|
Command string
|
|
|
|
Pid int
|
|
|
|
Hostname string
|
|
|
|
RemoteAddr string
|
|
|
|
}
|
|
|
|
|
2015-08-21 09:51:43 +00:00
|
|
|
func (context *clientContext) goHandleClient() {
|
|
|
|
exit := make(chan bool, 2)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer func() { exit <- true }()
|
|
|
|
|
|
|
|
context.processSend()
|
|
|
|
}()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer func() { exit <- true }()
|
|
|
|
|
|
|
|
context.processReceive()
|
|
|
|
}()
|
|
|
|
|
2015-08-24 10:22:25 +00:00
|
|
|
context.app.server.StartRoutine()
|
2015-08-24 19:15:24 +00:00
|
|
|
|
|
|
|
if context.app.options.Once {
|
|
|
|
log.Printf("Last client accepted, closing the listener.")
|
|
|
|
context.app.server.Close()
|
|
|
|
}
|
|
|
|
|
2015-08-21 09:51:43 +00:00
|
|
|
go func() {
|
2015-08-24 10:22:25 +00:00
|
|
|
defer context.app.server.FinishRoutine()
|
|
|
|
|
2015-08-21 09:51:43 +00:00
|
|
|
<-exit
|
2015-08-21 10:08:51 +00:00
|
|
|
context.pty.Close()
|
2015-08-28 08:22:42 +00:00
|
|
|
|
|
|
|
// Even if the PTY has been closed,
|
|
|
|
// Read(0 in processSend() keeps blocking and the process doen't exit
|
|
|
|
context.command.Process.Signal(syscall.SIGHUP)
|
|
|
|
|
2015-08-21 09:51:43 +00:00
|
|
|
context.command.Wait()
|
|
|
|
context.connection.Close()
|
|
|
|
log.Printf("Connection closed: %s", context.request.RemoteAddr)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (context *clientContext) processSend() {
|
2015-08-23 11:40:18 +00:00
|
|
|
if err := context.sendInitialize(); err != nil {
|
|
|
|
log.Printf(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-08-21 09:51:43 +00:00
|
|
|
buf := make([]byte, 1024)
|
|
|
|
utf8f := utf8reader.New(context.pty)
|
|
|
|
|
|
|
|
for {
|
|
|
|
size, err := utf8f.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Command exited for: %s", context.request.RemoteAddr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-08-28 08:22:42 +00:00
|
|
|
err = context.connection.WriteMessage(websocket.TextMessage, append([]byte{Output}, buf[:size]...))
|
2015-08-21 09:51:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-23 11:40:18 +00:00
|
|
|
func (context *clientContext) sendInitialize() error {
|
|
|
|
hostname, _ := os.Hostname()
|
|
|
|
titleVars := ContextVars{
|
2015-08-27 06:23:54 +00:00
|
|
|
Command: strings.Join(context.app.command, " "),
|
2015-08-23 11:40:18 +00:00
|
|
|
Pid: context.command.Process.Pid,
|
|
|
|
Hostname: hostname,
|
|
|
|
RemoteAddr: context.request.RemoteAddr,
|
|
|
|
}
|
|
|
|
|
|
|
|
writer, err := context.connection.NextWriter(websocket.TextMessage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
writer.Write([]byte{SetWindowTitle})
|
|
|
|
if err = context.app.titleTemplate.Execute(writer, titleVars); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
writer.Close()
|
|
|
|
|
2015-08-29 04:12:04 +00:00
|
|
|
prefs, _ := json.Marshal(context.app.options.Preferences)
|
2015-08-28 08:49:25 +00:00
|
|
|
context.connection.WriteMessage(
|
|
|
|
websocket.TextMessage,
|
|
|
|
append([]byte{SetPreferences}, prefs...),
|
|
|
|
)
|
2015-08-23 13:55:06 +00:00
|
|
|
|
2015-08-27 06:23:54 +00:00
|
|
|
if context.app.options.EnableReconnect {
|
|
|
|
reconnect, _ := json.Marshal(context.app.options.ReconnectTime)
|
2015-08-28 08:49:25 +00:00
|
|
|
context.connection.WriteMessage(
|
|
|
|
websocket.TextMessage,
|
|
|
|
append([]byte{SetReconnect}, reconnect...),
|
|
|
|
)
|
2015-08-23 22:14:24 +00:00
|
|
|
}
|
|
|
|
|
2015-08-23 11:40:18 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-21 09:51:43 +00:00
|
|
|
func (context *clientContext) processReceive() {
|
|
|
|
for {
|
|
|
|
_, data, err := context.connection.ReadMessage()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch data[0] {
|
|
|
|
case Input:
|
|
|
|
if !context.app.options.PermitWrite {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := context.pty.Write(data[1:])
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
case ResizeTerminal:
|
|
|
|
var args argResizeTerminal
|
|
|
|
err = json.Unmarshal(data[1:], &args)
|
|
|
|
if err != nil {
|
|
|
|
log.Print("Malformed remote command")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
window := struct {
|
|
|
|
row uint16
|
|
|
|
col uint16
|
|
|
|
x uint16
|
|
|
|
y uint16
|
|
|
|
}{
|
|
|
|
uint16(args.Rows),
|
|
|
|
uint16(args.Columns),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
}
|
|
|
|
syscall.Syscall(
|
|
|
|
syscall.SYS_IOCTL,
|
|
|
|
context.pty.Fd(),
|
|
|
|
syscall.TIOCSWINSZ,
|
|
|
|
uintptr(unsafe.Pointer(&window)),
|
|
|
|
)
|
|
|
|
|
|
|
|
default:
|
|
|
|
log.Print("Unknown message type")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|