87 lines
2.6 KiB
Go
87 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/x-grk/longpolling-remote-access/internal/proto"
|
|
)
|
|
|
|
// The browser terminal talks to the server over plain HTTP long-polling (the
|
|
// same transport the agent uses), so it works through any reverse proxy without
|
|
// WebSocket upgrade support.
|
|
|
|
// POST /api/panel/term/{agentID}/open - create a terminal session.
|
|
func (s *Server) handlePanelOpen(w http.ResponseWriter, r *http.Request) {
|
|
agentID := r.PathValue("agentID")
|
|
agent, err := s.db.GetAgent(agentID)
|
|
if err != nil || agent == nil {
|
|
log.Printf("panel open: agent %q not found (err=%v)", agentID, err)
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
sess := s.hub.CreateSession(agentID, agent.LoginUser)
|
|
if admin := s.currentAdmin(r); admin != nil {
|
|
s.db.Audit(admin.ID, agentID, "open_terminal:"+agent.TerminalUser())
|
|
}
|
|
writeJSON(w, map[string]string{"session_id": sess.ID})
|
|
}
|
|
|
|
// GET /api/panel/term/{sid}/out - long-poll for PTY output.
|
|
func (s *Server) handlePanelOut(w http.ResponseWriter, r *http.Request) {
|
|
sess := s.hub.Session(r.PathValue("sid"))
|
|
if sess == nil {
|
|
writeJSON(w, map[string]any{"closed": true})
|
|
return
|
|
}
|
|
msgs, alive := sess.PollOut(pollHold)
|
|
if !alive {
|
|
writeJSON(w, map[string]any{"closed": true})
|
|
return
|
|
}
|
|
writeJSON(w, map[string]any{"frames": msgs})
|
|
}
|
|
|
|
// panelInMsg is what the browser posts for keystrokes/resize.
|
|
type panelInMsg struct {
|
|
T string `json:"t"` // "i" input, "r" resize
|
|
D string `json:"d,omitempty"`
|
|
Cols uint16 `json:"cols,omitempty"`
|
|
Rows uint16 `json:"rows,omitempty"`
|
|
// Seq numbers the messages of a session from 1; 0 from a page that
|
|
// predates it.
|
|
Seq uint64 `json:"seq,omitempty"`
|
|
}
|
|
|
|
// POST /api/panel/term/{sid}/in - send keystrokes/resize to the agent PTY.
|
|
func (s *Server) handlePanelIn(w http.ResponseWriter, r *http.Request) {
|
|
sess := s.hub.Session(r.PathValue("sid"))
|
|
if sess == nil {
|
|
http.Error(w, "closed", http.StatusGone)
|
|
return
|
|
}
|
|
var m panelInMsg
|
|
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&m); err != nil {
|
|
http.Error(w, "bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if m.Seq > 0 && !sess.TakeSeq(m.Seq) {
|
|
w.WriteHeader(http.StatusNoContent) // a repeat of one already taken
|
|
return
|
|
}
|
|
switch m.T {
|
|
case "i":
|
|
sess.PushIn(proto.InFrame{Type: proto.FrameStdin, Data: m.D})
|
|
case "r":
|
|
sess.PushIn(proto.InFrame{Type: proto.FrameResize, Cols: m.Cols, Rows: m.Rows})
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// POST /api/panel/term/{sid}/close - tear the session down.
|
|
func (s *Server) handlePanelClose(w http.ResponseWriter, r *http.Request) {
|
|
s.hub.CloseSession(r.PathValue("sid"))
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|