110 lines
3.8 KiB
Go
110 lines
3.8 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/x-grk/longpolling-remote-access/internal/proto"
|
|
)
|
|
|
|
// Files on an SSH host through a machine, for the panel's file panes: the
|
|
// machine's agent keeps the SSH connection (open gives its id) and runs the
|
|
// operations there. Downloads and uploads go in the same pieces as a
|
|
// machine's own files.
|
|
|
|
// POST /api/panel/agents/{id}/sshfs/open - {"ssh": {...}} → {conn, home}.
|
|
func (s *Server) handleSSHFSOpen(w http.ResponseWriter, r *http.Request) {
|
|
agentID := r.PathValue("id")
|
|
var in struct {
|
|
SSH *proto.SSHTarget `json:"ssh"`
|
|
}
|
|
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&in); err != nil || in.SSH == nil {
|
|
writeJSON(w, proto.TaskResult{Error: "не указан хост"})
|
|
return
|
|
}
|
|
if err := s.fillSaved(s.currentAdmin(r).ID, in.SSH); err != nil {
|
|
writeJSON(w, proto.TaskResult{Error: err.Error()})
|
|
return
|
|
}
|
|
t := in.SSH
|
|
t.Host, t.User = strings.TrimSpace(t.Host), strings.TrimSpace(t.User)
|
|
if t.Port == 0 {
|
|
t.Port = 22
|
|
}
|
|
if t.Host == "" || t.User == "" || t.Port < 1 || t.Port > 65535 {
|
|
writeJSON(w, proto.TaskResult{Error: "нужны хост, порт и пользователь"})
|
|
return
|
|
}
|
|
if !s.fsAgent(w, agentID, true) {
|
|
return
|
|
}
|
|
res, err := s.waitTask(r.Context(), agentID, proto.TaskSSHFSOpen, proto.SSHFSArgs{SSH: t}, fsTaskTimeout)
|
|
if err != nil {
|
|
res = proto.TaskResult{Error: err.Error()}
|
|
}
|
|
if unknownOp(res) {
|
|
res = proto.TaskResult{Error: "агент не умеет файлы по SSH - обновите его"}
|
|
}
|
|
if res.OK {
|
|
if admin := s.currentAdmin(r); admin != nil {
|
|
s.db.Audit(admin.ID, agentID, "sshfs.open:"+t.User+"@"+t.Host)
|
|
}
|
|
}
|
|
writeJSON(w, res)
|
|
}
|
|
|
|
// sshfsChanges are the operations that change files, for the audit log.
|
|
var sshfsChanges = map[string]bool{"write": true, "mkdir": true, "delete": true, "deltree": true, "move": true, "copy": true, "chmod": true, "zip": true, "unzip": true}
|
|
|
|
// POST /api/panel/agents/{id}/sshfs/{conn}/{op} - an operation on the host;
|
|
// the body is in the shape of the machine's own fs.{op}. "close" drops the
|
|
// connection.
|
|
func (s *Server) handleSSHFSOp(w http.ResponseWriter, r *http.Request) {
|
|
agentID, conn, op := r.PathValue("id"), r.PathValue("conn"), r.PathValue("op")
|
|
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 64<<20))
|
|
if err != nil {
|
|
http.Error(w, "bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if !s.fsAgent(w, agentID, true) {
|
|
return
|
|
}
|
|
task, args := proto.TaskSSHFSOp, proto.SSHFSArgs{Conn: conn, Op: op, Args: json.RawMessage(body)}
|
|
if op == "close" {
|
|
task, args = proto.TaskSSHFSClose, proto.SSHFSArgs{Conn: conn}
|
|
}
|
|
if len(args.Args) == 0 {
|
|
args.Args = json.RawMessage("{}")
|
|
}
|
|
if sshfsChanges[op] {
|
|
var d struct{ Path, Src, Dst string }
|
|
_ = json.Unmarshal(body, &d)
|
|
if admin := s.currentAdmin(r); admin != nil {
|
|
s.db.Audit(admin.ID, agentID, "sshfs."+op+":"+strings.TrimSpace(d.Path+" "+d.Src+" "+d.Dst))
|
|
}
|
|
}
|
|
res, err := s.waitTask(r.Context(), agentID, task, args, fsTaskTimeout)
|
|
if err != nil {
|
|
res = proto.TaskResult{Error: err.Error()}
|
|
}
|
|
if unknownOp(res) && op != "close" {
|
|
res = proto.TaskResult{Error: "агент не умеет файлы по SSH - обновите его"}
|
|
}
|
|
writeJSON(w, res)
|
|
}
|
|
|
|
// GET /api/panel/agents/{id}/sshfs/{conn}/download?path=...[&inline=1]
|
|
func (s *Server) handleSSHFSDownload(w http.ResponseWriter, r *http.Request) {
|
|
s.download(w, r, proto.TaskSSHFSSend, r.PathValue("conn"))
|
|
}
|
|
|
|
// POST /api/panel/agents/{id}/sshfs/{conn}/upload?path=...&size=N
|
|
func (s *Server) handleSSHFSUpload(w http.ResponseWriter, r *http.Request) {
|
|
if admin := s.currentAdmin(r); admin != nil {
|
|
s.db.Audit(admin.ID, r.PathValue("id"), "sshfs.upload:"+r.URL.Query().Get("path"))
|
|
}
|
|
s.uploadStart(w, r, proto.TaskSSHFSPut, r.PathValue("conn"))
|
|
}
|