update layout, pdf export for headeranalyzer needs fixing layout
This commit is contained in:
428
main.go
428
main.go
@@ -3,11 +3,8 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -21,9 +18,9 @@ import (
|
||||
|
||||
"headeranalyzer/parser"
|
||||
"headeranalyzer/passwordgenerator"
|
||||
"headeranalyzer/resolver"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -130,14 +127,10 @@ func main() {
|
||||
// Initialize password generator word list
|
||||
passwordgenerator.InitWordList()
|
||||
|
||||
tmpl := template.Must(template.New("index.html").Funcs(template.FuncMap{
|
||||
"splitString": func(s, delimiter string) []string {
|
||||
return strings.Split(s, delimiter)
|
||||
},
|
||||
"contains": func(s, substr string) bool {
|
||||
return strings.Contains(s, substr)
|
||||
},
|
||||
}).ParseFS(embeddedFiles, "web/index.html"))
|
||||
// Create handlers with separate template sets
|
||||
indexHandler := parser.NewHandler(embeddedFiles)
|
||||
dnsHandler := resolver.NewHandler(embeddedFiles)
|
||||
passwordHandler := passwordgenerator.NewHandler(embeddedFiles)
|
||||
|
||||
// Use embedded static files for web assets
|
||||
staticFS, err := fs.Sub(embeddedFiles, "web")
|
||||
@@ -158,416 +151,17 @@ func main() {
|
||||
w.Write(data)
|
||||
})
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
headers := r.FormValue("headers")
|
||||
report := parser.Analyze(headers)
|
||||
tmpl.Execute(w, report)
|
||||
return
|
||||
}
|
||||
tmpl.Execute(w, nil)
|
||||
})
|
||||
http.Handle("/", indexHandler)
|
||||
|
||||
http.HandleFunc("/dns", func(w http.ResponseWriter, r *http.Request) {
|
||||
dnsTmpl := template.Must(template.ParseFS(embeddedFiles, "web/dns.html"))
|
||||
dnsTmpl.Execute(w, nil)
|
||||
})
|
||||
http.Handle("/dns", dnsHandler)
|
||||
|
||||
http.HandleFunc("/api/dns", func(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("query")
|
||||
typeq := r.URL.Query().Get("type")
|
||||
if query == "" || typeq == "" {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Missing query or type"))
|
||||
return
|
||||
}
|
||||
dnsServer := r.URL.Query().Get("server")
|
||||
var result string
|
||||
switch typeq {
|
||||
case "WHOIS":
|
||||
resp, err := http.Get("https://rdap.org/domain/" + query)
|
||||
if err != nil {
|
||||
result = "WHOIS lookup failed: " + err.Error()
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
// Try to parse JSON and extract key info
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(body, &data); err == nil {
|
||||
var lines []string
|
||||
if v, ok := data["ldhName"]; ok {
|
||||
lines = append(lines, fmt.Sprintf("Domain: %v", v))
|
||||
}
|
||||
if v, ok := data["status"]; ok {
|
||||
if arr, ok := v.([]interface{}); ok {
|
||||
lines = append(lines, fmt.Sprintf("Status: %v", strings.Join(func() []string {
|
||||
s := make([]string, len(arr))
|
||||
for i, x := range arr {
|
||||
s[i] = fmt.Sprintf("%v", x)
|
||||
}
|
||||
return s
|
||||
}(), ", ")))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf("Status: %v", v))
|
||||
}
|
||||
}
|
||||
var registrar, registrarIANA string
|
||||
var registrant, registrantEmail, registrantPhone, registrantOrg, registrantCountry string
|
||||
if v, ok := data["entities"]; ok {
|
||||
if ents, ok := v.([]interface{}); ok {
|
||||
for _, ent := range ents {
|
||||
if entmap, ok := ent.(map[string]interface{}); ok {
|
||||
var rolestr string
|
||||
if roles, ok := entmap["roles"]; ok {
|
||||
if rlist, ok := roles.([]interface{}); ok {
|
||||
for _, r := range rlist {
|
||||
rolestr = fmt.Sprintf("%v", r)
|
||||
if rolestr == "registrar" {
|
||||
if v, ok := entmap["vcardArray"]; ok {
|
||||
if vcard, ok := v.([]interface{}); ok && len(vcard) > 1 {
|
||||
if props, ok := vcard[1].([]interface{}); ok {
|
||||
for _, prop := range props {
|
||||
if arr, ok := prop.([]interface{}); ok && len(arr) > 3 {
|
||||
if arr[0] == "fn" {
|
||||
registrar = fmt.Sprintf("%v", arr[3])
|
||||
}
|
||||
if arr[0] == "org" {
|
||||
registrar = fmt.Sprintf("%v", arr[3])
|
||||
}
|
||||
if arr[0] == "ianaRegistrarId" {
|
||||
registrarIANA = fmt.Sprintf("%v", arr[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if rolestr == "registrant" {
|
||||
if v, ok := entmap["vcardArray"]; ok {
|
||||
if vcard, ok := v.([]interface{}); ok && len(vcard) > 1 {
|
||||
if props, ok := vcard[1].([]interface{}); ok {
|
||||
for _, prop := range props {
|
||||
if arr, ok := prop.([]interface{}); ok && len(arr) > 3 {
|
||||
if arr[0] == "fn" {
|
||||
registrant = fmt.Sprintf("%v", arr[3])
|
||||
}
|
||||
if arr[0] == "email" {
|
||||
registrantEmail = fmt.Sprintf("%v", arr[3])
|
||||
}
|
||||
if arr[0] == "tel" {
|
||||
registrantPhone = fmt.Sprintf("%v", arr[3])
|
||||
}
|
||||
if arr[0] == "org" {
|
||||
registrantOrg = fmt.Sprintf("%v", arr[3])
|
||||
}
|
||||
if arr[0] == "adr" {
|
||||
if adr, ok := arr[3].([]interface{}); ok && len(adr) > 6 {
|
||||
registrantCountry = fmt.Sprintf("%v", adr[6])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if registrar != "" {
|
||||
lines = append(lines, "Registrar: "+registrar)
|
||||
}
|
||||
if registrarIANA != "" {
|
||||
lines = append(lines, "Registrar IANA ID: "+registrarIANA)
|
||||
}
|
||||
if registrant != "" {
|
||||
lines = append(lines, "Registrant: "+registrant)
|
||||
}
|
||||
if registrantOrg != "" {
|
||||
lines = append(lines, "Registrant Org: "+registrantOrg)
|
||||
}
|
||||
if registrantEmail != "" {
|
||||
lines = append(lines, "Registrant Email: "+registrantEmail)
|
||||
}
|
||||
if registrantPhone != "" {
|
||||
lines = append(lines, "Registrant Phone: "+registrantPhone)
|
||||
}
|
||||
if registrantCountry != "" {
|
||||
lines = append(lines, "Registrant Country: "+registrantCountry)
|
||||
}
|
||||
if v, ok := data["nameservers"]; ok {
|
||||
if nsarr, ok := v.([]interface{}); ok && len(nsarr) > 0 {
|
||||
nslist := make([]string, 0, len(nsarr))
|
||||
for _, ns := range nsarr {
|
||||
if nsmap, ok := ns.(map[string]interface{}); ok {
|
||||
if ldh, ok := nsmap["ldhName"]; ok {
|
||||
nslist = append(nslist, fmt.Sprintf("%v", ldh))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(nslist) > 0 {
|
||||
lines = append(lines, "Nameservers: "+strings.Join(nslist, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := data["secureDNS"]; ok {
|
||||
if sec, ok := v.(map[string]interface{}); ok {
|
||||
if ds, ok := sec["delegationSigned"]; ok {
|
||||
lines = append(lines, fmt.Sprintf("DNSSEC: %v", ds))
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := data["events"]; ok {
|
||||
if evs, ok := v.([]interface{}); ok {
|
||||
for _, ev := range evs {
|
||||
if evm, ok := ev.(map[string]interface{}); ok {
|
||||
if action, ok := evm["eventAction"]; ok {
|
||||
if date, ok := evm["eventDate"]; ok {
|
||||
lines = append(lines, fmt.Sprintf("%v: %v", action, date))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := data["remarks"]; ok {
|
||||
if rems, ok := v.([]interface{}); ok {
|
||||
for _, rem := range rems {
|
||||
if remm, ok := rem.(map[string]interface{}); ok {
|
||||
if desc, ok := remm["description"]; ok {
|
||||
if descarr, ok := desc.([]interface{}); ok && len(descarr) > 0 {
|
||||
lines = append(lines, fmt.Sprintf("Remark: %v", descarr[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result = strings.Join(lines, "\n")
|
||||
} else {
|
||||
result = string(body)
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Special handling for SPF, DKIM, DMARC
|
||||
var answers []string
|
||||
switch strings.ToUpper(typeq) {
|
||||
case "SPF":
|
||||
// Query TXT records and filter for SPF
|
||||
target := dns.Fqdn(query)
|
||||
resolvers := []string{"1.1.1.1:53", "8.8.8.8:53"}
|
||||
if dnsServer != "" {
|
||||
if !strings.Contains(dnsServer, ":") {
|
||||
dnsServer = dnsServer + ":53"
|
||||
}
|
||||
resolvers = []string{dnsServer}
|
||||
}
|
||||
for _, resolverAddr := range resolvers {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(target, dns.TypeTXT)
|
||||
resp, err := dns.Exchange(m, resolverAddr)
|
||||
if err == nil && resp != nil && len(resp.Answer) > 0 {
|
||||
for _, ans := range resp.Answer {
|
||||
if t, ok := ans.(*dns.TXT); ok {
|
||||
for _, txt := range t.Txt {
|
||||
if strings.HasPrefix(txt, "v=spf1") {
|
||||
answers = append(answers, txt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(answers) == 0 {
|
||||
result = "No SPF record found."
|
||||
} else {
|
||||
result = "SPF record(s):\n" + strings.Join(answers, "\n")
|
||||
}
|
||||
case "DMARC":
|
||||
// Query _dmarc.<domain> TXT
|
||||
dmarc := "_dmarc." + query
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(dmarc), dns.TypeTXT)
|
||||
resolvers := []string{"1.1.1.1:53", "8.8.8.8:53"}
|
||||
if dnsServer != "" {
|
||||
if !strings.Contains(dnsServer, ":") {
|
||||
dnsServer = dnsServer + ":53"
|
||||
}
|
||||
resolvers = []string{dnsServer}
|
||||
}
|
||||
for _, resolverAddr := range resolvers {
|
||||
resp, err := dns.Exchange(m, resolverAddr)
|
||||
if err == nil && resp != nil && len(resp.Answer) > 0 {
|
||||
for _, ans := range resp.Answer {
|
||||
if t, ok := ans.(*dns.TXT); ok {
|
||||
answers = append(answers, strings.Join(t.Txt, ""))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(answers) == 0 {
|
||||
result = "No DMARC record found."
|
||||
} else {
|
||||
result = "DMARC record(s):\n" + strings.Join(answers, "\n")
|
||||
}
|
||||
case "DKIM":
|
||||
// Query <selector>._domainkey.<domain> TXT, if not found, check CNAME and then TXT at CNAME target
|
||||
domain := query
|
||||
selector := r.URL.Query().Get("selector")
|
||||
if strings.Contains(query, ":") {
|
||||
parts := strings.SplitN(query, ":", 2)
|
||||
domain = parts[0]
|
||||
selector = parts[1]
|
||||
}
|
||||
if selector == "" {
|
||||
selector = "default"
|
||||
}
|
||||
if selector == "default" && !strings.Contains(query, ":") {
|
||||
answers = append(answers, "Tip: For DKIM, specify selector as domain:selector (e.g. example.com:selector1)")
|
||||
}
|
||||
dkim := selector + "._domainkey." + domain
|
||||
resolvers := []string{"1.1.1.1:53", "8.8.8.8:53"}
|
||||
if dnsServer != "" {
|
||||
if !strings.Contains(dnsServer, ":") {
|
||||
dnsServer = dnsServer + ":53"
|
||||
}
|
||||
resolvers = []string{dnsServer}
|
||||
}
|
||||
foundTXT := false
|
||||
var cnameTarget string
|
||||
for _, resolverAddr := range resolvers {
|
||||
// 1. Query TXT at DKIM name
|
||||
mTXT := new(dns.Msg)
|
||||
mTXT.SetQuestion(dns.Fqdn(dkim), dns.TypeTXT)
|
||||
txtResp, txtErr := dns.Exchange(mTXT, resolverAddr)
|
||||
if txtErr == nil && txtResp != nil && len(txtResp.Answer) > 0 {
|
||||
for _, ans := range txtResp.Answer {
|
||||
if t, ok := ans.(*dns.TXT); ok {
|
||||
answers = append(answers, strings.Join(t.Txt, ""))
|
||||
foundTXT = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if foundTXT {
|
||||
break
|
||||
}
|
||||
// 2. If no TXT, query CNAME at DKIM name
|
||||
mCNAME := new(dns.Msg)
|
||||
mCNAME.SetQuestion(dns.Fqdn(dkim), dns.TypeCNAME)
|
||||
cnameResp, cnameErr := dns.Exchange(mCNAME, resolverAddr)
|
||||
if cnameErr == nil && cnameResp != nil && len(cnameResp.Answer) > 0 {
|
||||
for _, ans := range cnameResp.Answer {
|
||||
if c, ok := ans.(*dns.CNAME); ok {
|
||||
cnameTarget = c.Target
|
||||
}
|
||||
}
|
||||
}
|
||||
if cnameTarget != "" {
|
||||
// 3. Query TXT at CNAME target
|
||||
m2 := new(dns.Msg)
|
||||
m2.SetQuestion(cnameTarget, dns.TypeTXT)
|
||||
resp2, err2 := dns.Exchange(m2, resolverAddr)
|
||||
if err2 == nil && resp2 != nil && len(resp2.Answer) > 0 {
|
||||
for _, ans2 := range resp2.Answer {
|
||||
if t2, ok := ans2.(*dns.TXT); ok {
|
||||
answers = append(answers, strings.Join(t2.Txt, ""))
|
||||
foundTXT = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if foundTXT {
|
||||
answers = append(answers, "(via CNAME: "+cnameTarget+")")
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundTXT {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(answers) == 0 || (len(answers) == 1 && strings.HasPrefix(answers[0], "Tip:")) {
|
||||
result = "No DKIM record found for selector '" + selector + "'."
|
||||
if len(answers) > 0 {
|
||||
result += "\n" + answers[0]
|
||||
}
|
||||
} else {
|
||||
result = "DKIM record(s) for selector '" + selector + "':\n" + strings.Join(answers, "\n")
|
||||
}
|
||||
default:
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(dns.Fqdn(query), dns.StringToType[typeq])
|
||||
resolvers := []string{"1.1.1.1:53", "8.8.8.8:53"}
|
||||
if dnsServer != "" {
|
||||
if !strings.Contains(dnsServer, ":") {
|
||||
dnsServer = dnsServer + ":53"
|
||||
}
|
||||
resolvers = []string{dnsServer}
|
||||
}
|
||||
for _, resolverAddr := range resolvers {
|
||||
resp, err := dns.Exchange(m, resolverAddr)
|
||||
if err == nil && resp != nil && len(resp.Answer) > 0 {
|
||||
for _, ans := range resp.Answer {
|
||||
result += ans.String() + "\n"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if result == "" {
|
||||
result = "No result found."
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write([]byte(result))
|
||||
})
|
||||
http.HandleFunc("/api/dns", resolver.DNSAPIHandler)
|
||||
|
||||
http.HandleFunc("/password", func(w http.ResponseWriter, r *http.Request) {
|
||||
passwordTmpl := template.Must(template.ParseFS(embeddedFiles, "web/password.html"))
|
||||
passwordTmpl.Execute(w, nil)
|
||||
})
|
||||
http.Handle("/password", passwordHandler)
|
||||
|
||||
http.HandleFunc("/api/password", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
http.HandleFunc("/api/password", passwordgenerator.PasswordAPIHandler)
|
||||
|
||||
var config passwordgenerator.Config
|
||||
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Invalid JSON"))
|
||||
return
|
||||
}
|
||||
|
||||
password, err := passwordgenerator.GeneratePassword(config)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write([]byte(password))
|
||||
})
|
||||
|
||||
http.HandleFunc("/api/password/info", func(w http.ResponseWriter, r *http.Request) {
|
||||
count, source, lastUpdate := passwordgenerator.GetWordListInfo()
|
||||
|
||||
info := map[string]interface{}{
|
||||
"wordCount": count,
|
||||
"source": source,
|
||||
"lastUpdate": lastUpdate.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(info)
|
||||
})
|
||||
http.HandleFunc("/api/password/info", passwordgenerator.PasswordInfoAPIHandler)
|
||||
|
||||
addrPort := fmt.Sprintf("%s:%d", *addr, *port)
|
||||
fmt.Printf("Listening on http://%s\n", addrPort)
|
||||
|
||||
Reference in New Issue
Block a user