package mailer
import (
"bytes"
"fmt"
"html"
"log"
"net/smtp"
"os"
"sort"
"nucleus/internal/models"
)
func SendReport(target models.Target, findings []models.Finding) {
host := os.Getenv("SMTP_HOST")
port := os.Getenv("SMTP_PORT")
if host == "" || port == "" {
log.Println("SMTP_HOST or SMTP_PORT not set, skipping email report.")
return
}
user := os.Getenv("SMTP_USER")
pass := os.Getenv("SMTP_PASS")
from := os.Getenv("SMTP_FROM")
to := os.Getenv("SMTP_TO")
if from == "" || to == "" {
log.Println("SMTP_FROM or SMTP_TO not set, skipping email report.")
return
}
var auth smtp.Auth
if user != "" && pass != "" {
auth = smtp.PlainAuth("", user, pass, host)
}
subject := fmt.Sprintf("Nucleus Scan Report: %s", target.Name)
var body bytes.Buffer
body.WriteString(fmt.Sprintf("To: %s\r\n", to))
body.WriteString(fmt.Sprintf("From: %s\r\n", from))
body.WriteString(fmt.Sprintf("Subject: %s\r\n", subject))
body.WriteString("Content-Type: text/html; charset=UTF-8\r\n\r\n")
body.WriteString("
")
body.WriteString(fmt.Sprintf("Scan Report for %s (%s)
", target.Name, target.Address))
body.WriteString("The scheduled Nuclei scan has completed.
")
if len(findings) == 0 {
body.WriteString("No findings were detected.
")
} else {
// Calculate summary
counts := map[string]int{"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
for _, f := range findings {
counts[f.Severity]++
}
body.WriteString("Summary
")
if counts["critical"] > 0 { body.WriteString(fmt.Sprintf("- Critical: %d
", counts["critical"])) }
if counts["high"] > 0 { body.WriteString(fmt.Sprintf("- High: %d
", counts["high"])) }
if counts["medium"] > 0 { body.WriteString(fmt.Sprintf("- Medium: %d
", counts["medium"])) }
if counts["low"] > 0 { body.WriteString(fmt.Sprintf("- Low: %d
", counts["low"])) }
if counts["info"] > 0 { body.WriteString(fmt.Sprintf("- Info: %d
", counts["info"])) }
body.WriteString("
")
// Sort findings by severity
severityScore := map[string]int{"critical": 5, "high": 4, "medium": 3, "low": 2, "info": 1}
sort.Slice(findings, func(i, j int) bool {
return severityScore[findings[i].Severity] > severityScore[findings[j].Severity]
})
body.WriteString("Details
")
body.WriteString("")
body.WriteString("| Severity | Name | Host | Template |
")
for _, f := range findings {
color := "#ffffff"
switch f.Severity {
case "critical": color = "#ffcccc"
case "high": color = "#ffe6cc"
case "medium": color = "#ffffcc"
case "low": color = "#e6f2ff"
case "info": color = "#f2f2f2"
}
body.WriteString(fmt.Sprintf("| %s | %s | %s | %s |
",
color,
html.EscapeString(f.Severity),
html.EscapeString(f.Name),
html.EscapeString(f.Host),
html.EscapeString(f.TemplateID)))
}
body.WriteString("
")
}
body.WriteString("")
addr := fmt.Sprintf("%s:%s", host, port)
err := smtp.SendMail(addr, auth, from, []string{to}, body.Bytes())
if err != nil {
log.Printf("Failed to send email report: %v", err)
} else {
log.Println("Successfully sent email report for target", target.Name)
}
}