package api import ( "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "log" "strings" "net/http" "github.com/cynfo/rmm-backend/ws" ) // POST /api/v1/firmware/upload — Neues Agent-Binary hochladen func (h *Handler) uploadFirmware(w http.ResponseWriter, r *http.Request) { version := r.URL.Query().Get("version") if version == "" { writeError(w, http.StatusBadRequest, "version Parameter erforderlich") return } platform := r.URL.Query().Get("platform") if platform == "" { platform = "freebsd" // Default fuer OPNsense } // Validieren validPlatforms := map[string]bool{"freebsd": true, "linux": true, "windows": true} if !validPlatforms[platform] { writeError(w, http.StatusBadRequest, "Ungueltige Plattform (freebsd, linux, windows)") return } // Binary lesen — Multipart (curl -F) oder raw Body (Content-Type: application/octet-stream) r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024) var binary []byte if ct := r.Header.Get("Content-Type"); strings.HasPrefix(ct, "multipart/") { if err := r.ParseMultipartForm(50 << 20); err != nil { writeError(w, http.StatusBadRequest, "Multipart-Parse fehlgeschlagen") return } file, _, err := r.FormFile("file") if err != nil { writeError(w, http.StatusBadRequest, "Feld 'file' fehlt im Multipart-Form") return } defer file.Close() binary, err = io.ReadAll(file) if err != nil { writeError(w, http.StatusBadRequest, "Binary lesen fehlgeschlagen") return } } else { var err error binary, err = io.ReadAll(r.Body) if err != nil { writeError(w, http.StatusBadRequest, "Binary lesen fehlgeschlagen") return } } if len(binary) == 0 { writeError(w, http.StatusBadRequest, "Leeres Binary") return } hash := sha256.Sum256(binary) hashStr := hex.EncodeToString(hash[:]) if err := h.db.SaveAgentFirmware(version, platform, hashStr, binary); err != nil { log.Printf("Firmware speichern fehlgeschlagen: %v", err) writeError(w, http.StatusInternalServerError, "Speichern fehlgeschlagen") return } h.auditLog(r, "firmware.upload", "firmware", version, platform, fmt.Sprintf("%d bytes", len(binary))) log.Printf("Agent-Firmware v%s (%s) hochgeladen (%d bytes, Hash: %s)", version, platform, len(binary), hashStr[:16]) writeJSON(w, http.StatusOK, map[string]interface{}{ "version": version, "platform": platform, "hash": hashStr, "size": len(binary), "message": "Firmware hochgeladen", }) } // GET /api/v1/firmware — Firmware-Info (alle Plattformen oder spezifisch) func (h *Handler) getFirmwareInfo(w http.ResponseWriter, r *http.Request) { platform := r.URL.Query().Get("platform") if platform == "" { // Alle Plattformen auflisten all, err := h.db.GetAllFirmwareInfo() if err != nil || len(all) == 0 { writeJSON(w, http.StatusOK, map[string]interface{}{ "available": false, "message": "Keine Firmware hochgeladen", "platforms": []interface{}{}, }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "available": true, "platforms": all, }) return } version, hash, size, err := h.db.GetFirmwareInfo(platform) if err != nil { writeJSON(w, http.StatusOK, map[string]interface{}{ "available": false, "platform": platform, "message": "Keine Firmware fuer diese Plattform", }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "available": true, "platform": platform, "version": version, "hash": hash, "size": size, }) } // GET /api/v1/firmware/download — Binary herunterladen (fuer Updater) func (h *Handler) downloadFirmware(w http.ResponseWriter, r *http.Request) { platform := r.URL.Query().Get("platform") if platform == "" { platform = "freebsd" } version, hash, binary, err := h.db.GetLatestFirmware(platform) if err != nil { writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar") return } w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", "attachment; filename=rmm-agent") w.Header().Set("X-Firmware-Version", version) w.Header().Set("X-Firmware-Hash", hash) w.Header().Set("Content-Length", fmt.Sprintf("%d", len(binary))) w.Write(binary) } // POST /api/v1/agents/{id}/request-update — Update-Flag setzen func (h *Handler) requestUpdate(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") if err := h.db.SetUpdateRequest(agentID, true); err != nil { writeError(w, http.StatusInternalServerError, "Fehler beim Setzen des Update-Flags") return } h.auditLog(r, "firmware.update_request", "agent", agentID, "", "") log.Printf("Update angefordert fuer Agent %s", agentID) writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update angefordert", "agent_id": agentID}) } // DELETE /api/v1/agents/{id}/request-update — Update-Flag loeschen func (h *Handler) cancelUpdate(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") h.db.ClearUpdateRequest(agentID) writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update-Anforderung geloescht", "agent_id": agentID}) } // POST /api/v1/agents/request-update-all — Update-Flag fuer alle setzen func (h *Handler) requestUpdateAll(w http.ResponseWriter, r *http.Request) { count, err := h.db.SetUpdateRequestAll(true) if err != nil { writeError(w, http.StatusInternalServerError, "Fehler") return } h.auditLog(r, "firmware.update_all", "", "", "", fmt.Sprintf("%d Agents", count)) log.Printf("Update angefordert fuer %d Agents", count) writeJSON(w, http.StatusOK, map[string]interface{}{"message": fmt.Sprintf("Update fuer %d Agents angefordert", count), "count": count}) } // POST /api/v1/agents/{id}/agent-update — Update an einzelnen Agent pushen func (h *Handler) pushAgentUpdate(hub *ws.Hub) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") // TODO: Agent-Plattform aus DB lesen; fuer jetzt default freebsd version, hash, binary, err := h.db.GetLatestFirmware("freebsd") if err != nil { writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar") return } // Agent-Version pruefen agent, _, err := h.db.GetAgent(agentID) if err != nil || agent == nil { writeError(w, http.StatusNotFound, "Agent nicht gefunden") return } if agent.AgentVersion == version { writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "Agent ist bereits auf dem neuesten Stand", "current_version": agent.AgentVersion, "target_version": version, "updated": false, }) return } // Binary als Base64 an Agent senden binaryB64 := base64.StdEncoding.EncodeToString(binary) cmdID := fmt.Sprintf("agent-update-%s", agentID[:8]) cmd := map[string]interface{}{ "type": "command", "id": cmdID, "command": "agent_update", "data": map[string]interface{}{ "binary": binaryB64, "hash": hash, "version": version, "old_version": agent.AgentVersion, }, } cmdJSON, _ := json.Marshal(cmd) if err := hub.SendToAgent(agentID, cmdJSON); err != nil { writeError(w, http.StatusBadGateway, fmt.Sprintf("Agent nicht erreichbar: %v", err)) return } log.Printf("Agent-Update v%s an %s gesendet (%d bytes)", version, agent.Name, len(binary)) writeJSON(w, http.StatusOK, map[string]interface{}{ "message": fmt.Sprintf("Update v%s an %s gesendet", version, agent.Name), "current_version": agent.AgentVersion, "target_version": version, "updated": true, "size": len(binary), }) } }