package app import ( "fmt" "log" "net/http" "strconv" "strings" "time" ) // WebServiceHandler handles custom web honeypot services type WebServiceHandler struct { config WebServiceConfig templateManager *WebTemplateManager logFunc func(Record) } // NewWebServiceHandler creates a new web service handler func NewWebServiceHandler(config WebServiceConfig, templateManager *WebTemplateManager, logFunc func(Record)) *WebServiceHandler { return &WebServiceHandler{ config: config, templateManager: templateManager, logFunc: logFunc, } } // ServeHTTP implements the http.Handler interface func (wsh *WebServiceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Log all requests wsh.logRequest(r) // Check if request is for the configured path if r.URL.Path == wsh.config.Path { wsh.handleLoginPage(w, r) return } // For all other paths, return 403 Forbidden wsh.logEvent(r, "forbidden_access", map[string]string{ "requested_path": r.URL.Path, "configured_path": wsh.config.Path, }) http.Error(w, "Forbidden", http.StatusForbidden) } // handleLoginPage serves the login page and processes login attempts func (wsh *WebServiceHandler) handleLoginPage(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: wsh.serveLoginForm(w, r) case http.MethodPost: wsh.handleLoginAttempt(w, r) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } // serveLoginForm renders and serves the login form func (wsh *WebServiceHandler) serveLoginForm(w http.ResponseWriter, r *http.Request) { // Load template tmpl, err := wsh.templateManager.LoadTemplate(wsh.config.TemplateName) if err != nil { log.Printf("Failed to load template %s: %v", wsh.config.TemplateName, err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Prepare template data data := map[string]interface{}{ "ServiceName": wsh.config.Name, "LoginPath": wsh.config.Path, "UseHTTPS": wsh.config.UseHTTPS, } // Set content type w.Header().Set("Content-Type", "text/html; charset=utf-8") // Execute template if err := tmpl.Execute(w, data); err != nil { log.Printf("Failed to execute template: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Log page view wsh.logEvent(r, "login_page_view", map[string]string{ "template": wsh.config.TemplateName, }) } // handleLoginAttempt processes login form submissions func (wsh *WebServiceHandler) handleLoginAttempt(w http.ResponseWriter, r *http.Request) { // Parse form data if err := r.ParseForm(); err != nil { http.Error(w, "Bad Request", http.StatusBadRequest) return } username := strings.TrimSpace(r.FormValue("username")) password := r.FormValue("password") // Log the login attempt wsh.logEvent(r, "login_attempt", map[string]string{ "username": username, "password": password, "template": wsh.config.TemplateName, }) // Always reject login attempts (this is a honeypot) // Redirect back to login page with error parameter redirectURL := fmt.Sprintf("%s?error=invalid", wsh.config.Path) http.Redirect(w, r, redirectURL, http.StatusSeeOther) } // logRequest logs basic request information func (wsh *WebServiceHandler) logRequest(r *http.Request) { details := map[string]string{ "method": r.Method, "path": r.URL.Path, "query": r.URL.RawQuery, "user_agent": r.Header.Get("User-Agent"), "referer": r.Header.Get("Referer"), } // Add additional headers that might be interesting if auth := r.Header.Get("Authorization"); auth != "" { details["authorization"] = auth } if cookie := r.Header.Get("Cookie"); cookie != "" { details["cookie"] = cookie } wsh.logEvent(r, "http_request", details) } // logEvent logs an event with the web service context func (wsh *WebServiceHandler) logEvent(r *http.Request, eventType string, details map[string]string) { if details == nil { details = make(map[string]string) } // Add service context details["event"] = eventType details["service_name"] = wsh.config.Name details["service_port"] = strconv.Itoa(wsh.config.Port) details["service_path"] = wsh.config.Path // Create log record record := Record{ Timestamp: time.Now().UTC(), RemoteAddr: remoteIP(r.RemoteAddr), RemotePort: remotePort(r.RemoteAddr), Service: fmt.Sprintf("web-%s", wsh.config.Name), Details: details, RawPayload: fmt.Sprintf("%s %s", r.Method, r.URL.String()), } if wsh.logFunc != nil { wsh.logFunc(record) } } // WebServiceManager manages multiple web service instances type WebServiceManager struct { templateManager *WebTemplateManager logFunc func(Record) services map[string]*WebServiceHandler } // NewWebServiceManager creates a new web service manager func NewWebServiceManager(templateManager *WebTemplateManager, logFunc func(Record)) *WebServiceManager { return &WebServiceManager{ templateManager: templateManager, logFunc: logFunc, services: make(map[string]*WebServiceHandler), } } // StartWebService starts a web service on the specified configuration func (wsm *WebServiceManager) StartWebService(config WebServiceConfig) error { if !config.Enabled { return nil } // Create handler handler := NewWebServiceHandler(config, wsm.templateManager, wsm.logFunc) // Create HTTP server mux := http.NewServeMux() mux.Handle("/", handler) addr := fmt.Sprintf(":%d", config.Port) server := &http.Server{ Addr: addr, Handler: mux, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } // Store service serviceKey := fmt.Sprintf("%s:%d", config.Name, config.Port) wsm.services[serviceKey] = handler log.Printf("Starting web service '%s' on port %d at path %s", config.Name, config.Port, config.Path) // Start server go func() { var err error if config.UseHTTPS { // For HTTPS, we would need to load certificates; using HTTP placeholder log.Printf("HTTPS requested for %s but using HTTP for now. Configure TLS certificates for production.", config.Name) err = server.ListenAndServe() } else { err = server.ListenAndServe() } if err != nil && err != http.ErrServerClosed { log.Printf("Web service %s error: %v", config.Name, err) } }() return nil } // StopWebService stops a web service func (wsm *WebServiceManager) StopWebService(name string, port int) { serviceKey := fmt.Sprintf("%s:%d", name, port) delete(wsm.services, serviceKey) // Note: In a production system, you'd want to properly shutdown the HTTP server } // GetActiveServices returns a list of active web services func (wsm *WebServiceManager) GetActiveServices() []string { var services []string for key := range wsm.services { services = append(services, key) } return services }