package sipguardian import ( "encoding/json" "net/http" "strings" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) func init() { caddy.RegisterModule(AdminHandler{}) } // AdminHandler provides HTTP endpoints to manage SIP Guardian type AdminHandler struct { guardian *SIPGuardian } func (AdminHandler) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ ID: "http.handlers.sip_guardian_admin", New: func() caddy.Module { return new(AdminHandler) }, } } func (h *AdminHandler) Provision(ctx caddy.Context) error { // Get the shared guardian instance // In production, this would use proper module loading h.guardian = &SIPGuardian{} return h.guardian.Provision(ctx) } // ServeHTTP handles admin API requests func (h *AdminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { // Route based on path path := r.URL.Path switch { case strings.HasSuffix(path, "/bans"): return h.handleBans(w, r) case strings.HasSuffix(path, "/stats"): return h.handleStats(w, r) case strings.Contains(path, "/unban/"): return h.handleUnban(w, r, path) case strings.Contains(path, "/ban/"): return h.handleBan(w, r, path) default: return next.ServeHTTP(w, r) } } // handleBans lists all banned IPs func (h *AdminHandler) handleBans(w http.ResponseWriter, r *http.Request) error { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return nil } bans := h.guardian.GetBannedIPs() w.Header().Set("Content-Type", "application/json") return json.NewEncoder(w).Encode(map[string]interface{}{ "bans": bans, "count": len(bans), }) } // handleStats returns current statistics func (h *AdminHandler) handleStats(w http.ResponseWriter, r *http.Request) error { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return nil } stats := h.guardian.GetStats() w.Header().Set("Content-Type", "application/json") return json.NewEncoder(w).Encode(stats) } // handleUnban removes an IP from the ban list func (h *AdminHandler) handleUnban(w http.ResponseWriter, r *http.Request, path string) error { if r.Method != http.MethodPost && r.Method != http.MethodDelete { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return nil } // Extract IP from path: /admin/sip-guardian/unban/{ip} parts := strings.Split(path, "/unban/") if len(parts) != 2 || parts[1] == "" { http.Error(w, "IP address required", http.StatusBadRequest) return nil } ip := strings.TrimSuffix(parts[1], "/") if h.guardian.UnbanIP(ip) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "message": "IP unbanned", "ip": ip, }) } else { w.WriteHeader(http.StatusNotFound) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "message": "IP not found in ban list", "ip": ip, }) } return nil } // handleBan manually adds an IP to the ban list func (h *AdminHandler) handleBan(w http.ResponseWriter, r *http.Request, path string) error { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return nil } // Extract IP from path: /admin/sip-guardian/ban/{ip} parts := strings.Split(path, "/ban/") if len(parts) != 2 || parts[1] == "" { http.Error(w, "IP address required", http.StatusBadRequest) return nil } ip := strings.TrimSuffix(parts[1], "/") // Parse optional reason from body var body struct { Reason string `json:"reason"` } if r.Body != nil { json.NewDecoder(r.Body).Decode(&body) } if body.Reason == "" { body.Reason = "manual_ban" } // Force a ban by recording max failures h.guardian.mu.Lock() h.guardian.failureCounts[ip] = &failureTracker{ count: h.guardian.MaxFailures, } h.guardian.banIP(ip, body.Reason) h.guardian.mu.Unlock() w.Header().Set("Content-Type", "application/json") return json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "message": "IP banned", "ip": ip, "reason": body.Reason, }) } // Interface guards var ( _ caddyhttp.MiddlewareHandler = (*AdminHandler)(nil) _ caddy.Provisioner = (*AdminHandler)(nil) )