package ws import ( "context" "fmt" "log" "os/exec" "strconv" "time" ) type Handler struct { client *Client } func NewHandler(client *Client) *Handler { return &Handler{client: client} } func (h *Handler) HandleCommand(msg Message) { log.Printf("Command erhalten: %s (ID: %s)", msg.Command, msg.ID) var response Message response.Type = "response" response.ID = msg.ID switch msg.Command { case "exec": response = h.handleExec(msg) case "tunnel_open": response = h.handleTunnelOpen(msg) case "tunnel_close": response = h.handleTunnelClose(msg) case "tunnel_data": // Tunnel-Daten werden als Binary-Messages gesendet, nicht als JSON h.handleTunnelData(msg) return default: response.Status = "error" response.Error = fmt.Sprintf("Unbekanntes Command: %s", msg.Command) } if err := h.client.SendMessage(response); err != nil { log.Printf("Response senden fehlgeschlagen: %v", err) } } func (h *Handler) handleExec(msg Message) Message { response := Message{ Type: "response", ID: msg.ID, } // Parameter extrahieren command, ok := msg.Params["command"].(string) if !ok || command == "" { response.Status = "error" response.Error = "Parameter 'command' erforderlich" return response } timeoutSec := 30 // Default if t, ok := msg.Params["timeout"]; ok { if ts, ok := t.(float64); ok { timeoutSec = int(ts) } else if ts, ok := t.(string); ok { if parsed, err := strconv.Atoi(ts); err == nil { timeoutSec = parsed } } } // Command ausführen mit Timeout ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSec)*time.Second) defer cancel() // FreeBSD/OPNsense nutzt /bin/sh cmd := exec.CommandContext(ctx, "/bin/sh", "-c", command) output, err := cmd.CombinedOutput() if err != nil { response.Status = "error" response.Error = err.Error() response.Data = map[string]interface{}{ "output": string(output), } } else { response.Status = "ok" response.Data = map[string]interface{}{ "output": string(output), } } return response } func (h *Handler) handleTunnelOpen(msg Message) Message { response := Message{ Type: "response", ID: msg.ID, } // Parameter extrahieren targetHost, ok := msg.Params["target_host"].(string) if !ok || targetHost == "" { response.Status = "error" response.Error = "Parameter 'target_host' erforderlich" return response } targetPortFloat, ok := msg.Params["target_port"].(float64) if !ok { response.Status = "error" response.Error = "Parameter 'target_port' erforderlich" return response } targetPort := int(targetPortFloat) // Tunnel-ID vom Backend uebernehmen (falls vorhanden) backendTunnelID, _ := msg.Params["tunnel_id"].(string) // Tunnel oeffnen tunnelID, err := h.client.tunnelManager.OpenTunnel(targetHost, targetPort, backendTunnelID) if err != nil { response.Status = "error" response.Error = fmt.Sprintf("Tunnel öffnen fehlgeschlagen: %v", err) return response } response.Status = "ok" response.Data = map[string]interface{}{ "tunnel_id": tunnelID, } log.Printf("Tunnel geöffnet: %s -> %s:%d", tunnelID, targetHost, targetPort) return response } func (h *Handler) handleTunnelClose(msg Message) Message { response := Message{ Type: "response", ID: msg.ID, } // Tunnel-ID extrahieren tunnelID, ok := msg.Params["tunnel_id"].(string) if !ok || tunnelID == "" { response.Status = "error" response.Error = "Parameter 'tunnel_id' erforderlich" return response } // Tunnel schließen if err := h.client.tunnelManager.CloseTunnel(tunnelID); err != nil { response.Status = "error" response.Error = fmt.Sprintf("Tunnel schließen fehlgeschlagen: %v", err) return response } response.Status = "ok" log.Printf("Tunnel geschlossen: %s", tunnelID) return response } func (h *Handler) handleTunnelData(msg Message) { // Diese Funktion wird für JSON-basierte Tunnel-Daten verwendet // In der Praxis verwenden wir Binary WebSocket Messages für bessere Performance tunnelID, ok := msg.Params["tunnel_id"].(string) if !ok { log.Printf("Tunnel-Daten ohne tunnel_id erhalten") return } dataStr, ok := msg.Params["data"].(string) if !ok { log.Printf("Tunnel-Daten ohne data erhalten") return } // Base64-Dekodierung würde hier stattfinden // Aber wir verwenden Binary Messages, daher ist das nur ein Fallback log.Printf("Tunnel-Daten via JSON erhalten für %s (Länge: %d)", tunnelID, len(dataStr)) } // SendTunnelData sendet Tunnel-Daten als Binary-Message zurück zum Backend func (h *Handler) SendTunnelData(tunnelID string, data []byte) error { // Format: [tunnel_id_length:1][tunnel_id][data] tunnelIDBytes := []byte(tunnelID) if len(tunnelIDBytes) > 255 { return fmt.Errorf("tunnel_id zu lang") } message := make([]byte, 1+len(tunnelIDBytes)+len(data)) message[0] = byte(len(tunnelIDBytes)) copy(message[1:], tunnelIDBytes) copy(message[1+len(tunnelIDBytes):], data) return h.client.SendBinaryData(message) } // ParseTunnelData parst eingehende Binary-Messages mit Tunnel-Daten func ParseTunnelData(data []byte) (tunnelID string, payload []byte, err error) { if len(data) < 1 { return "", nil, fmt.Errorf("Daten zu kurz") } tunnelIDLen := int(data[0]) if len(data) < 1+tunnelIDLen { return "", nil, fmt.Errorf("Tunnel-ID zu kurz") } tunnelID = string(data[1 : 1+tunnelIDLen]) payload = data[1+tunnelIDLen:] return tunnelID, payload, nil }