DigiJ 2322f69516 v2.2.0 — Full arsenal expansion: 16 new security modules
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>
2026-03-03 05:20:39 -08:00

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]
}