// Used https://github.com/miekg/exdns/reflect as a template
//
// This is an authoritative DNS server that behaves as follows.
// If the looked up host is v4-1-1-1-1.v6-2-2-2-2.crazysh.com:
// 
// a. 1.1.1.1 is always returned as the A record
// b. The IPv4 embedded IPv6 ::ffff:2.2.2.2 is returned as the AAAA record
//
// This strange behavior was required for an SSRF exploit to work and might
// be useful for testing
//
// You may find stuff like this useful!
//
// cruoho@omgwtfbbq ~ % host v4-1-1-1-1.v6-169-254-169-254.crazysh.com
// v4-1-1-1-1.v6-169-254-169-254.crazysh.com has address 1.1.1.1
// v4-1-1-1-1.v6-169-254-169-254.crazysh.com has IPv6 address ::ffff:169.254.169.254
//
// Multiple records can be returned, e.g.
//
// v4-1-1-1-1.v4-169-254-169-254.crazysh.com
// 
// if you're in the mood for other dns tricks
//
// You can also insert meaningless keys in a middle octet if you're watching your
// logs and want to tag a specific request, eg v4-1-1-1-1.foo.crazysh.com
//
// Contact: clint@wtfismyip.com

package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"runtime"
	"runtime/pprof"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/miekg/dns"
)

var (
	cpuprofile  = flag.String("cpuprofile", "", "write cpu profile to file")
	printf      = flag.Bool("print", false, "print replies")
	compress    = flag.Bool("compress", false, "compress replies")
	tsig        = flag.String("tsig", "", "use MD5 hmac tsig: keyname:base64")
	soreuseport = flag.Int("soreuseport", 0, "use SO_REUSE_PORT")
	cpu         = flag.Int("cpu", 0, "number of cpu to use")
)

const dom = "crazysh.com."
const ns1 = "arf1.crazysh.com."
const ns2 = "arf2.crazysh.com."

func handleReflect(w dns.ResponseWriter, r *dns.Msg) {
	var (
		rr       dns.RR
		rr2      dns.RR
		str      string
		remoteIP string
	)
	m := new(dns.Msg)

	// Get the hostname in the DNS request
	Question := r.Question[0]
	Name := strings.ToLower(Question.Name)

	// nuke uvpn.me and trailing dot

	Host := strings.TrimSuffix(strings.Split(Name, dom)[0], ".")
	Host = strings.ToLower(Host)
	Hosts := strings.Split(Host, ".")

	host4 := make([]string, 0, 20)
	host6 := make([]string, 0, 20)
	rr4 := make([]dns.RR, 0, 20)
	rr6 := make([]dns.RR, 0, 20)
	var ttl uint32 = 1

	for i := 0; i < len(Hosts); i++ {
		if strings.HasPrefix(Hosts[i], "v4-") || (strings.HasPrefix(Hosts[i], "V4-")) {
			host4 = append(host4, strings.ReplaceAll(strings.TrimPrefix(strings.TrimPrefix(Hosts[i], "v4-"), "V4-"), "-", "."))
		} else if strings.HasPrefix(Hosts[i], "v6-") || (strings.HasPrefix(Hosts[i], "V6-")) {
			host6 = append(host6, ("::ffff:" + strings.ReplaceAll(strings.TrimPrefix(strings.TrimPrefix(Hosts[i], "v6-"), "V6-"), "-", ".")))
		} else if Host == "arf1" {
			host4 = append(host4, "54.39.106.25")
			host6 = append(host6, "2607:5300:203:3c19::2")
			ttl = 3600
		} else if Host == "arf2" {
			host4 = append(host4, "54.39.106.25")
			host6 = append(host6, "2607:5300:203:3c19::2")
			ttl = 3600
		} else if Host == "" {
			host4 = append(host4, "51.222.11.82")
			ttl = 3600
		}
	}

	m.SetReply(r)
	m.Authoritative = true
	m.Compress = *compress

	if ip, ok := w.RemoteAddr().(*net.UDPAddr); ok {
		str = "Port: " + strconv.Itoa(ip.Port) + " (udp)"
		remoteIP, _, _ = net.SplitHostPort(ip.String())
	}
	if ip, ok := w.RemoteAddr().(*net.TCPAddr); ok {
		str = "Port: " + strconv.Itoa(ip.Port) + " (tcp)"
		remoteIP, _, _ = net.SplitHostPort(ip.String())
	}

	now := time.Now()
	fmt.Printf("%s %s: %s\n", remoteIP, now, Name)

	// create the A and AAAA records
	for i := 0; i < len(host6); i++ {
		rr = &dns.AAAA{
			Hdr:  dns.RR_Header{Name: Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl},
			AAAA: net.ParseIP(host6[i]),
		}
		rr6 = append(rr6, rr)
	}

	for j := 0; j < len(host4); j++ {
		rr2 = &dns.A{
			Hdr: dns.RR_Header{Name: Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl},
			A:   net.ParseIP(host4[j]),
		}
		rr4 = append(rr4, rr2)
	}

	t := &dns.TXT{
		Hdr: dns.RR_Header{Name: Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: ttl},
		Txt: []string{str},
	}

	ns1 := &dns.NS{
		Hdr: dns.RR_Header{Name: Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86400},
		Ns:  dns.Fqdn(ns1),
	}

	ns2 := &dns.NS{
		Hdr: dns.RR_Header{Name: Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86400},
		Ns:  dns.Fqdn(ns2),
	}

	soa := &dns.SOA{
		Hdr:     dns.RR_Header{Name: Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 86400},
		Ns:      "arf1.crazysh.com.",
		Mbox:    "admin.crazysh.com.",
		Serial:  1,
		Refresh: 1200,
		Retry:   600,
		Expire:  1209600,
		Minttl:  3600,
	}

	switch r.Question[0].Qtype {

	// serve responses
	case dns.TypeTXT:
		m.Answer = append(m.Answer, t)
		//m.Extra = append(m.Extra, rr)
	default:
		fallthrough
	case dns.TypeA:
		for i := 0; i < len(rr4); i++ {
			m.Answer = append(m.Answer, rr4[i])
		}
		//m.Extra = append(m.Extra, t)
	case dns.TypeAAAA:
		for i := 0; i < len(rr6); i++ {
			m.Answer = append(m.Answer, rr6[i])
		}
		//m.Extra = append(m.Extra, t)
	case dns.TypeNS:
		m.Answer = append(m.Answer, ns1)
		m.Answer = append(m.Answer, ns2)
	case dns.TypeSOA:
		m.Answer = append(m.Answer, soa)
	}

	if r.IsTsig() != nil {
		if w.TsigStatus() == nil {
			m.SetTsig(r.Extra[len(r.Extra)-1].(*dns.TSIG).Hdr.Name, dns.HmacMD5, 300, time.Now().Unix())
		} else {
			println("Status", w.TsigStatus().Error())
		}
	}

	if *printf {
		fmt.Printf("%v\n", m.String())
	}

	w.WriteMsg(m)
}

func serve(net, name, secret string, soreuseport bool) {
	switch name {
	case "":
		server := &dns.Server{Addr: ":53", Net: net, TsigSecret: nil, ReusePort: soreuseport}
		if err := server.ListenAndServe(); err != nil {
			fmt.Printf("Failed to setup the "+net+" server: %s\n", err.Error())

		}
	default:
		server := &dns.Server{Addr: ":8053", Net: net, TsigSecret: map[string]string{name: secret}, ReusePort: soreuseport}
		if err := server.ListenAndServe(); err != nil {
			fmt.Printf("Failed to setup the "+net+" server: %s\n", err.Error())
		}
	}
}

func main() {
	var name, secret string
	flag.Usage = func() {
		flag.PrintDefaults()
	}
	flag.Parse()
	if *tsig != "" {
		a := strings.SplitN(*tsig, ":", 2)
		name, secret = dns.Fqdn(a[0]), a[1] // fqdn the name, which everybody forgets...
	}
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	if *cpu != 0 {
		runtime.GOMAXPROCS(*cpu)
	}
	dns.HandleFunc(".", handleReflect)
	if *soreuseport > 0 {
		for i := 0; i < *soreuseport; i++ {
			go serve("tcp", name, secret, true)
			go serve("udp", name, secret, true)
		}
	} else {
		go serve("tcp", name, secret, false)
		go serve("udp", name, secret, false)
	}
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	s := <-sig
	fmt.Printf("Signal (%s) received, stopping\n", s)
}