Add WiFi Audit, API Fuzzer, Cloud Scanner, Threat Intel, Log Correlator, Steganography, Anti-Forensics, BLE Scanner, Forensics, RFID/NFC, Malware Sandbox, Password Toolkit, Web Scanner, Report Engine, Net Mapper, and C2 Framework. Each module includes CLI interface, Flask routes, and web UI template. Also includes Go DNS server source + binary, IP Capture service, SYN Flood, Gone Fishing mail server, and hack hijack modules from v2.0 work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
350 lines
7.2 KiB
Go
350 lines
7.2 KiB
Go
package server
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// HostEntry represents a single hosts file entry.
|
|
type HostEntry struct {
|
|
IP string `json:"ip"`
|
|
Hostname string `json:"hostname"`
|
|
Aliases []string `json:"aliases,omitempty"`
|
|
Comment string `json:"comment,omitempty"`
|
|
}
|
|
|
|
// HostsStore manages a hosts-file-like database.
|
|
type HostsStore struct {
|
|
mu sync.RWMutex
|
|
entries []HostEntry
|
|
path string // path to hosts file on disk (if loaded from file)
|
|
}
|
|
|
|
// NewHostsStore creates a new hosts store.
|
|
func NewHostsStore() *HostsStore {
|
|
return &HostsStore{
|
|
entries: make([]HostEntry, 0),
|
|
}
|
|
}
|
|
|
|
// LoadFile parses a hosts file from disk.
|
|
func (h *HostsStore) LoadFile(path string) error {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
h.path = path
|
|
h.entries = h.entries[:0]
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
// Strip inline comments
|
|
comment := ""
|
|
if idx := strings.Index(line, "#"); idx >= 0 {
|
|
comment = strings.TrimSpace(line[idx+1:])
|
|
line = strings.TrimSpace(line[:idx])
|
|
}
|
|
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
|
|
ip := fields[0]
|
|
if net.ParseIP(ip) == nil {
|
|
continue // invalid IP
|
|
}
|
|
|
|
entry := HostEntry{
|
|
IP: ip,
|
|
Hostname: strings.ToLower(fields[1]),
|
|
Comment: comment,
|
|
}
|
|
if len(fields) > 2 {
|
|
aliases := make([]string, len(fields)-2)
|
|
for i, a := range fields[2:] {
|
|
aliases[i] = strings.ToLower(a)
|
|
}
|
|
entry.Aliases = aliases
|
|
}
|
|
h.entries = append(h.entries, entry)
|
|
}
|
|
|
|
log.Printf("[hosts] Loaded %d entries from %s", len(h.entries), path)
|
|
return scanner.Err()
|
|
}
|
|
|
|
// LoadFromText parses hosts-format text (like pasting /etc/hosts content).
|
|
func (h *HostsStore) LoadFromText(content string) int {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
count := 0
|
|
scanner := bufio.NewScanner(strings.NewReader(content))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
comment := ""
|
|
if idx := strings.Index(line, "#"); idx >= 0 {
|
|
comment = strings.TrimSpace(line[idx+1:])
|
|
line = strings.TrimSpace(line[:idx])
|
|
}
|
|
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
|
|
ip := fields[0]
|
|
if net.ParseIP(ip) == nil {
|
|
continue
|
|
}
|
|
|
|
entry := HostEntry{
|
|
IP: ip,
|
|
Hostname: strings.ToLower(fields[1]),
|
|
Comment: comment,
|
|
}
|
|
if len(fields) > 2 {
|
|
aliases := make([]string, len(fields)-2)
|
|
for i, a := range fields[2:] {
|
|
aliases[i] = strings.ToLower(a)
|
|
}
|
|
entry.Aliases = aliases
|
|
}
|
|
|
|
// Dedup by hostname
|
|
found := false
|
|
for i, e := range h.entries {
|
|
if e.Hostname == entry.Hostname {
|
|
h.entries[i] = entry
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
h.entries = append(h.entries, entry)
|
|
}
|
|
count++
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// Add adds a single host entry.
|
|
func (h *HostsStore) Add(ip, hostname string, aliases []string, comment string) error {
|
|
if net.ParseIP(ip) == nil {
|
|
return fmt.Errorf("invalid IP: %s", ip)
|
|
}
|
|
hostname = strings.ToLower(strings.TrimSpace(hostname))
|
|
if hostname == "" {
|
|
return fmt.Errorf("hostname required")
|
|
}
|
|
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
// Check for duplicate
|
|
for i, e := range h.entries {
|
|
if e.Hostname == hostname {
|
|
h.entries[i].IP = ip
|
|
h.entries[i].Aliases = aliases
|
|
h.entries[i].Comment = comment
|
|
return nil
|
|
}
|
|
}
|
|
|
|
h.entries = append(h.entries, HostEntry{
|
|
IP: ip,
|
|
Hostname: hostname,
|
|
Aliases: aliases,
|
|
Comment: comment,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// Remove removes a host entry by hostname.
|
|
func (h *HostsStore) Remove(hostname string) bool {
|
|
hostname = strings.ToLower(strings.TrimSpace(hostname))
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
for i, e := range h.entries {
|
|
if e.Hostname == hostname {
|
|
h.entries = append(h.entries[:i], h.entries[i+1:]...)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Clear removes all entries.
|
|
func (h *HostsStore) Clear() int {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
n := len(h.entries)
|
|
h.entries = h.entries[:0]
|
|
return n
|
|
}
|
|
|
|
// List returns all entries.
|
|
func (h *HostsStore) List() []HostEntry {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
result := make([]HostEntry, len(h.entries))
|
|
copy(result, h.entries)
|
|
return result
|
|
}
|
|
|
|
// Count returns the number of entries.
|
|
func (h *HostsStore) Count() int {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
return len(h.entries)
|
|
}
|
|
|
|
// Lookup resolves a hostname from the hosts store.
|
|
// Returns DNS RRs matching the query name and type.
|
|
func (h *HostsStore) Lookup(name string, qtype uint16) []dns.RR {
|
|
if qtype != dns.TypeA && qtype != dns.TypeAAAA && qtype != dns.TypePTR {
|
|
return nil
|
|
}
|
|
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
|
|
fqdn := dns.Fqdn(strings.ToLower(name))
|
|
baseName := strings.TrimSuffix(fqdn, ".")
|
|
|
|
// PTR lookup (reverse DNS)
|
|
if qtype == dns.TypePTR {
|
|
// Convert in-addr.arpa name to IP
|
|
ip := ptrToIP(fqdn)
|
|
if ip == "" {
|
|
return nil
|
|
}
|
|
for _, e := range h.entries {
|
|
if e.IP == ip {
|
|
rr := &dns.PTR{
|
|
Hdr: dns.RR_Header{
|
|
Name: fqdn,
|
|
Rrtype: dns.TypePTR,
|
|
Class: dns.ClassINET,
|
|
Ttl: 60,
|
|
},
|
|
Ptr: dns.Fqdn(e.Hostname),
|
|
}
|
|
return []dns.RR{rr}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Forward lookup (A / AAAA)
|
|
var results []dns.RR
|
|
for _, e := range h.entries {
|
|
// Match hostname or aliases
|
|
match := strings.EqualFold(e.Hostname, baseName) || strings.EqualFold(dns.Fqdn(e.Hostname), fqdn)
|
|
if !match {
|
|
for _, a := range e.Aliases {
|
|
if strings.EqualFold(a, baseName) || strings.EqualFold(dns.Fqdn(a), fqdn) {
|
|
match = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
ip := net.ParseIP(e.IP)
|
|
if ip == nil {
|
|
continue
|
|
}
|
|
|
|
if qtype == dns.TypeA && ip.To4() != nil {
|
|
rr := &dns.A{
|
|
Hdr: dns.RR_Header{
|
|
Name: fqdn,
|
|
Rrtype: dns.TypeA,
|
|
Class: dns.ClassINET,
|
|
Ttl: 60,
|
|
},
|
|
A: ip.To4(),
|
|
}
|
|
results = append(results, rr)
|
|
} else if qtype == dns.TypeAAAA && ip.To4() == nil {
|
|
rr := &dns.AAAA{
|
|
Hdr: dns.RR_Header{
|
|
Name: fqdn,
|
|
Rrtype: dns.TypeAAAA,
|
|
Class: dns.ClassINET,
|
|
Ttl: 60,
|
|
},
|
|
AAAA: ip,
|
|
}
|
|
results = append(results, rr)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
// Export returns hosts file format text.
|
|
func (h *HostsStore) Export() string {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString("# AUTARCH DNS hosts file\n")
|
|
sb.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().UTC().Format(time.RFC3339)))
|
|
sb.WriteString("# Entries: " + fmt.Sprintf("%d", len(h.entries)) + "\n\n")
|
|
|
|
for _, e := range h.entries {
|
|
line := e.IP + "\t" + e.Hostname
|
|
for _, a := range e.Aliases {
|
|
line += "\t" + a
|
|
}
|
|
if e.Comment != "" {
|
|
line += "\t# " + e.Comment
|
|
}
|
|
sb.WriteString(line + "\n")
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// ptrToIP converts a PTR domain name (in-addr.arpa) to an IP string.
|
|
func ptrToIP(name string) string {
|
|
name = strings.TrimSuffix(strings.ToLower(name), ".")
|
|
if !strings.HasSuffix(name, ".in-addr.arpa") {
|
|
return ""
|
|
}
|
|
name = strings.TrimSuffix(name, ".in-addr.arpa")
|
|
parts := strings.Split(name, ".")
|
|
if len(parts) != 4 {
|
|
return ""
|
|
}
|
|
// Reverse the octets
|
|
return parts[3] + "." + parts[2] + "." + parts[1] + "." + parts[0]
|
|
}
|