147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/cynfo/rmm-backend/ws"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var termUpgrader = websocket.Upgrader{
|
|
ReadBufferSize: 4096,
|
|
WriteBufferSize: 4096,
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
}
|
|
|
|
// terminalHandler bridget Browser-WebSocket mit Agent-PTY
|
|
func (h *Handler) terminalHandler(hub *ws.Hub) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
agentID := r.PathValue("id")
|
|
if agentID == "" {
|
|
http.Error(w, "agent_id erforderlich", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Cols/Rows aus Query
|
|
cols := uint16(220)
|
|
rows := uint16(50)
|
|
if c := r.URL.Query().Get("cols"); c != "" {
|
|
var v uint16
|
|
fmt.Sscanf(c, "%d", &v)
|
|
if v > 0 {
|
|
cols = v
|
|
}
|
|
}
|
|
if r := r.URL.Query().Get("rows"); r != "" {
|
|
var v uint16
|
|
fmt.Sscanf(r, "%d", &v)
|
|
if v > 0 {
|
|
rows = v
|
|
}
|
|
}
|
|
|
|
// WebSocket-Upgrade
|
|
browserConn, err := termUpgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Printf("Terminal WS Upgrade fehlgeschlagen: %v", err)
|
|
return
|
|
}
|
|
defer browserConn.Close()
|
|
|
|
// PTY-Session-ID generieren
|
|
sessionID := fmt.Sprintf("pty-%s-%d", agentID[:8], time.Now().UnixNano())
|
|
|
|
// Output-Channel registrieren
|
|
outputCh := hub.RegisterPTYSession(sessionID)
|
|
defer hub.UnregisterPTYSession(sessionID)
|
|
|
|
// pty_start an Agent senden
|
|
err = h.sendCommandAndWaitPTY(hub, agentID, sessionID, cols, rows)
|
|
if err != nil {
|
|
log.Printf("PTY-Start fehlgeschlagen fuer %s: %v", agentID, err)
|
|
browserConn.WriteMessage(websocket.TextMessage, []byte("\r\nFehler: Agent nicht erreichbar oder PTY-Start fehlgeschlagen\r\n"))
|
|
return
|
|
}
|
|
|
|
log.Printf("PTY-Session gestartet: %s fuer Agent %s", sessionID, agentID)
|
|
|
|
// Goroutine: Agent-Ausgabe -> Browser
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
select {
|
|
case data, ok := <-outputCh:
|
|
if !ok {
|
|
return
|
|
}
|
|
browserConn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
|
if err := browserConn.WriteMessage(websocket.BinaryMessage, data); err != nil {
|
|
return
|
|
}
|
|
case <-time.After(30 * time.Second):
|
|
// Keep-alive ping
|
|
if err := browserConn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Browser-Input -> Agent
|
|
browserConn.SetReadDeadline(time.Time{}) // kein Timeout fuer interaktive Sessions
|
|
for {
|
|
messageType, data, err := browserConn.ReadMessage()
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
if messageType == websocket.BinaryMessage {
|
|
// Eingabe an Agent-PTY weiterleiten
|
|
idBytes := []byte(sessionID)
|
|
msg := make([]byte, 1+len(idBytes)+len(data))
|
|
msg[0] = byte(len(idBytes))
|
|
copy(msg[1:], idBytes)
|
|
copy(msg[1+len(idBytes):], data)
|
|
hub.SendBinaryToAgent(agentID, msg)
|
|
|
|
} else if messageType == websocket.TextMessage {
|
|
// JSON-Control-Messages (resize)
|
|
var ctrl struct {
|
|
Type string `json:"type"`
|
|
Cols uint16 `json:"cols"`
|
|
Rows uint16 `json:"rows"`
|
|
}
|
|
if err := json.Unmarshal(data, &ctrl); err == nil && ctrl.Type == "resize" {
|
|
SendCommandToAgent(hub, agentID, "pty_resize", map[string]interface{}{
|
|
"session_id": sessionID,
|
|
"cols": ctrl.Cols,
|
|
"rows": ctrl.Rows,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Browser disconnected -> PTY stoppen
|
|
SendCommandToAgent(hub, agentID, "pty_stop", map[string]interface{}{
|
|
"session_id": sessionID,
|
|
})
|
|
log.Printf("PTY-Session beendet: %s", sessionID)
|
|
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func (h *Handler) sendCommandAndWaitPTY(hub *ws.Hub, agentID, sessionID string, cols, rows uint16) error {
|
|
_, err := h.sendCommandAndWaitResult(hub, agentID, "pty_start", map[string]interface{}{
|
|
"session_id": sessionID,
|
|
"cols": cols,
|
|
"rows": rows,
|
|
}, 10)
|
|
return err
|
|
}
|