Lots of fixes and different setup
This commit is contained in:
parent
c61fe58508
commit
9c178a49c3
|
@ -3,6 +3,6 @@
|
|||
# license that can be found in the LICENSE file.
|
||||
include $(GOROOT)/src/Make.inc
|
||||
TARG=fp
|
||||
GOFILES=q.go fp.go lex.go dns.go
|
||||
GOFILES=q.go fp.go
|
||||
DEPS=../../
|
||||
include $(GOROOT)/src/Make.cmd
|
||||
|
|
|
@ -1,292 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"dns"
|
||||
)
|
||||
|
||||
const (
|
||||
QUERY_NOERROR string = "QUERY,NOERROR,qr,aa,tc,rd,ra,ad,cd,z,0,0,0,0,do,0,nsid"
|
||||
QUERY_NOTIFY string = "NOTIFY,NOERROR,qr,AA,tc,RD,ra,ad,cd,Z,0,0,0,0,do,0,nsid"
|
||||
QUERY_ALL string = "QUERY,NOERROR,QR,AA,TC,RD,RA,AD,CD,Z,0,0,0,0,DO,0,nsid"
|
||||
)
|
||||
|
||||
// Check if the server responds at all
|
||||
func dnsAlive(l *lexer) stateFn {
|
||||
l.verbose("Alive")
|
||||
l.setString(".,IN,NS," + QUERY_NOERROR)
|
||||
f := l.probe()
|
||||
if f.ok() {
|
||||
return dnsServer
|
||||
}
|
||||
l.emit(&item{itemError, f.error()})
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is the starting test. Perform a bunch of queries, get the
|
||||
// fingerprint a go into a general direction. NsdLike, BindLike, WindowsLike, MaraLike
|
||||
func dnsServer(l *lexer) stateFn {
|
||||
l.verbose("Server")
|
||||
|
||||
// Set the DO bit
|
||||
l.setString(".,CH,TXT,QUERY,NOERROR,qr,aa,tc,RD,ra,ad,cd,z,0,0,0,0,DO,4097,NSID")
|
||||
f := l.probe()
|
||||
switch {
|
||||
case !f.Do && f.UDPSize == 4096 && f.Rcode == dns.RcodeSuccess:
|
||||
// NSD clears DO bit, but sets UDPSize to 4096. NOERROR.
|
||||
l.emit(&item{itemVendor, NLNETLABS})
|
||||
return dnsNsdLike
|
||||
case !f.Do && f.UDPSize == 0 && f.Rcode == dns.RcodeRefused:
|
||||
// MaraDNS clears DO BIT, UDPSize to 0. REFUSED
|
||||
l.emit(&item{itemVendor, MARA})
|
||||
return dnsMaraLike
|
||||
case f.Do && f.UDPSize == 2800 && f.Rcode == dns.RcodeSuccess:
|
||||
// PowerDNS(SEC) set UDP bufsize to 2800, resets UDPSize. NOERROR
|
||||
fallthrough
|
||||
case !f.Do && f.UDPSize == 0 && f.Rcode == dns.RcodeSuccess:
|
||||
// PowerDNS(SEC) clears DO bit, resets UDPSize. NOERROR
|
||||
l.emit(&item{itemVendor, POWER})
|
||||
return dnsPowerdnsLike
|
||||
case !f.Do && f.UDPSize == 0 && f.Rcode == dns.RcodeServerFailure:
|
||||
// Neustar
|
||||
l.emit(&item{itemVendor, NEUSTAR})
|
||||
return dnsNeustarLike
|
||||
case !f.Do && f.UDPSize == 0 && f.Rcode == dns.RcodeNotImplemented:
|
||||
// Altas?
|
||||
l.emit(&item{itemVendor, VERISIGN})
|
||||
return dnsAtlasLike
|
||||
case !f.Do && f.UDPSize == 4096 && f.Rcode == dns.RcodeServerFailure:
|
||||
// BIND8
|
||||
fallthrough
|
||||
case f.Do && f.UDPSize == 4096 && f.Rcode == dns.RcodeServerFailure:
|
||||
// BIND9 OLD
|
||||
fallthrough
|
||||
case f.Do && f.UDPSize == 4096 && f.Rcode == dns.RcodeRefused:
|
||||
// BIND9 leaves DO bit, but sets UDPSize to 4096. REFUSED.
|
||||
l.emit(&item{itemVendor, ISC})
|
||||
return dnsBindLike
|
||||
case f.Do && f.UDPSize == 4097 && f.Rcode == dns.RcodeFormatError:
|
||||
// Microsoft leaves DO bit, but echo's the UDPSize. FORMERR.
|
||||
l.emit(&item{itemVendor, MICROSOFT})
|
||||
return dnsWindowsLike
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
panic("not reached")
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsNsdLike(l *lexer) stateFn {
|
||||
l.verbose("NsdLike")
|
||||
l.setString("auThoRs.bInD.,CH,TXT," + QUERY_NOERROR)
|
||||
l.probe()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsBindLike(l *lexer) stateFn {
|
||||
l.verbose("BindLike")
|
||||
|
||||
l.emit(&item{itemSoftware, BIND})
|
||||
|
||||
// Repeat the query, as we get a lot of information from it
|
||||
l.setString(".,CH,TXT,QUERY,NOERROR,qr,aa,tc,RD,ra,ad,cd,z,0,0,0,0,DO,4097,nsid")
|
||||
f := l.probe()
|
||||
switch {
|
||||
case !f.Do && f.UDPSize == 4096 && f.Rcode == dns.RcodeServerFailure:
|
||||
l.emit(&item{itemVersionMajor, "8"})
|
||||
case f.Do && f.UDPSize == 4096 && f.Rcode == dns.RcodeServerFailure:
|
||||
l.emit(&item{itemVersionMajor, "9"})
|
||||
l.emit(&item{itemVersionMinor, "3"})
|
||||
case f.Do && f.UDPSize == 4096 && f.Rcode == dns.RcodeRefused:
|
||||
// BIND9 leaves DO bit, but sets UDPSize to 4096. REFUSED.
|
||||
l.emit(&item{itemVersionMajor, "9"})
|
||||
l.emit(&item{itemVersionMinor, "[7..]"})
|
||||
}
|
||||
|
||||
// Try authors.bind
|
||||
l.setString("auThoRs.bInD.,CH,TXT," + QUERY_NOERROR)
|
||||
f = l.probe()
|
||||
switch f.Rcode {
|
||||
case dns.RcodeServerFailure:
|
||||
// No authors.bind < 9
|
||||
l.emit(&item{itemVersionMajor, "8"})
|
||||
case dns.RcodeSuccess, dns.RcodeRefused:
|
||||
// BIND 9 or BIND 10
|
||||
l.emit(&item{itemVersionMajor, "[9..10]"})
|
||||
}
|
||||
// The three BIND (8, 9 and 10) behave differently when
|
||||
// receiving a notify query
|
||||
l.setString("bind.,NONE,SOA," + QUERY_NOTIFY)
|
||||
f = l.probe()
|
||||
switch {
|
||||
case f.Opcode == dns.OpcodeNotify:
|
||||
if f.Rcode == dns.RcodeRefused {
|
||||
l.emit(&item{itemVersionMajor, "9"})
|
||||
}
|
||||
if f.Rcode == dns.RcodeServerFailure {
|
||||
l.emit(&item{itemVersionMajor, "8"})
|
||||
}
|
||||
case f.Opcode == dns.OpcodeQuery && f.Rcode == dns.RcodeSuccess:
|
||||
l.emit(&item{itemVersionMajor, "10"})
|
||||
if !f.Response {
|
||||
// Cardinal sin
|
||||
l.emit(&item{itemVersionMinor, "-devel"})
|
||||
l.emit(&item{itemVersionPatch, "20110809"})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsWindowsLike(l *lexer) stateFn {
|
||||
l.verbose("WindowsLike")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsMaraLike(l *lexer) stateFn {
|
||||
l.verbose("MaraLike")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsPowerdnsLike(l *lexer) stateFn {
|
||||
l.verbose("PowerdnsLike")
|
||||
l.setString(".,CH,TXT,QUERY,NOERROR,qr,aa,tc,RD,ra,ad,cd,z,0,0,0,0,DO,4097,NSID")
|
||||
f := l.probe()
|
||||
if !f.Response && f.Query.Qclass == 0 && f.Query.Qtype == 0 && f.Rcode == dns.RcodeSuccess {
|
||||
// Yadifa does not set the QR bit on this
|
||||
l.emit(&item{itemVendor, EURID})
|
||||
return dnsYadifaLike
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsYadifaLike(l *lexer) stateFn {
|
||||
l.verbose("YadifaLike")
|
||||
l.setString(".,CLASS0,TYPE0,QUERY,NOERROR,QR,aa,tc,rd,ra,ad,cd,z,0,0,0,0,do,0,nsid")
|
||||
l.probe()
|
||||
l.setString(".,CLASS42,TXT,QUERY,NOERROR,qr,aa,tc,rd,ra,ad,cd,z,0,0,0,0,do,0,nsid")
|
||||
l.probe()
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsNeustarLike(l *lexer) stateFn {
|
||||
l.verbose("NeustarLike")
|
||||
return nil
|
||||
}
|
||||
|
||||
func dnsAtlasLike(l *lexer) stateFn {
|
||||
l.verbose("AtlasLike")
|
||||
|
||||
l.setString("MieK.NL.,IN,TXT," + QUERY_NOERROR)
|
||||
l.probe()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Check if the server returns the DO-bit when set in the request.
|
||||
func dnsDoBitMirror(l *lexer) stateFn {
|
||||
l.verbose("DoBitMirror")
|
||||
l.setString(".,IN,NS,QUERY,NOERROR,qr,aa,tc,RD,ra,ad,cd,z,0,0,0,0,DO,0,NSID")
|
||||
f := l.probe()
|
||||
// NSD doesn't set the DO bit, but does set the UDPMsg size to 4096.
|
||||
if !f.Do && f.UDPSize == 4096 {
|
||||
l.emit(&item{itemSoftware, NSD})
|
||||
return dnsEDNS0Mangler
|
||||
}
|
||||
return dnsEDNS0Mangler
|
||||
}
|
||||
|
||||
func dnsEDNS0Mangler(l *lexer) stateFn {
|
||||
l.verbose("EDNS0Mangler")
|
||||
l.setString("NOTIFY,NOERROR,qr,aa,tc,RD,ra,ad,cd,z,0,0,0,0,do,0,nsid")
|
||||
// l.setQuestion("012345678901234567890123456789012345678901234567890123456789012.012345678901234567890123456789012345678901234567890123456789012.012345678901234567890123456789012345678901234567890123456789012.0123456789012345678901234567890123456789012345678901234567890.", dns.TypeA, dns.ClassINET)
|
||||
f := l.probe()
|
||||
// MaraDNS does not set the QR bit in the reply... but only with this question is seems
|
||||
// QUERY,NOERROR,qr,aa,t
|
||||
if !f.Response && f.Opcode == dns.OpcodeQuery && f.Rcode == dns.RcodeSuccess {
|
||||
l.emit(&item{itemSoftware, MARADNS})
|
||||
}
|
||||
return dnsTcEnable
|
||||
}
|
||||
|
||||
func dnsTcEnable(l *lexer) stateFn {
|
||||
l.verbose("TcEnable")
|
||||
l.setString(".,IN,NS,QUERY,NOERROR,qr,aa,TC,rd,ra,ad,cd,z,0,0,0,0,do,0,nsid")
|
||||
l.probe()
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func dnsUDPSize(l *lexer) stateFn {
|
||||
l.verbose("UDPSize")
|
||||
l.setString("QUERY,NOERROR,qr,aa,tc,rd,ra,ad,cd,z,0,0,0,0,DO,4097,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return dnsZero
|
||||
}
|
||||
|
||||
func dnsZero(l *lexer) stateFn {
|
||||
l.verbose("Zero")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.setString("QUERY,NOERROR,qr,aa,tc,rd,ra,ad,cd,Z,0,0,0,0,do,0,nsid")
|
||||
l.probe()
|
||||
return dnsAll
|
||||
}
|
||||
|
||||
func dnsAll(l *lexer) stateFn {
|
||||
l.verbose("All")
|
||||
l.setString("QUERY,NOERROR,qr,AA,TC,RD,RA,AD,CD,Z,0,0,0,0,DO,8192,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return dnsIquery
|
||||
}
|
||||
|
||||
func dnsIquery(l *lexer) stateFn {
|
||||
l.verbose("Iquery")
|
||||
l.setString("IQUERY,NOERROR,qr,aa,tc,rd,ra,ad,cd,Z,0,0,0,0,do,0,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return dnsUpdate
|
||||
}
|
||||
|
||||
func dnsUpdate(l *lexer) stateFn {
|
||||
l.verbose("Update")
|
||||
l.setString("UPDATE,NOERROR,qr,aa,tc,rd,ra,ad,cd,Z,0,0,0,0,do,0,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return dnsStatus
|
||||
}
|
||||
|
||||
func dnsStatus(l *lexer) stateFn {
|
||||
l.verbose("Status")
|
||||
l.setString("STATUS,NOERROR,qr,aa,tc,rd,ra,ad,cd,Z,0,0,0,0,do,0,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return dnsOpcodeWhacky
|
||||
}
|
||||
|
||||
func dnsOpcodeWhacky(l *lexer) stateFn {
|
||||
l.verbose("OpcodeWhacky")
|
||||
l.setString("12,NOERROR,qr,aa,tc,rd,ra,ad,cd,Z,0,0,0,0,do,0,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return dnsRcodeWhacky
|
||||
}
|
||||
|
||||
func dnsRcodeWhacky(l *lexer) stateFn {
|
||||
l.verbose("RcodeWhacky")
|
||||
l.setString("QUERY,31,qr,aa,tc,rd,ra,ad,cd,Z,0,0,0,0,do,0,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return dnsRcodeNotZone
|
||||
}
|
||||
|
||||
func dnsRcodeNotZone(l *lexer) stateFn {
|
||||
l.verbose("RcodeNotZone")
|
||||
l.setString("QUERY,NOTZONE,qr,aa,tc,rd,ra,ad,cd,z,0,0,0,0,do,0,nsid")
|
||||
l.setQuestion(".", dns.TypeNS, dns.ClassINET)
|
||||
l.probe()
|
||||
return nil
|
||||
}
|
||||
*/
|
|
@ -3,7 +3,6 @@ package main
|
|||
|
||||
import (
|
||||
"dns"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
@ -32,37 +31,15 @@ const (
|
|||
EURID = "EurID"
|
||||
)
|
||||
|
||||
func startParse(addr string) {
|
||||
l := &lexer{
|
||||
addr: addr,
|
||||
client: dns.NewClient(),
|
||||
fp: new(fingerprint),
|
||||
items: make(chan item),
|
||||
state: dnsAlive,
|
||||
debug: true,
|
||||
}
|
||||
|
||||
l.run()
|
||||
|
||||
// Not completely sure about this code..
|
||||
for {
|
||||
item := <-l.items
|
||||
fmt.Printf("{%s %s}\n", itemString[item.typ], item.val)
|
||||
if l.state == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendProbe creates a packet and sends it to the nameserver. It
|
||||
// probe creates a packet and sends it to the nameserver. It
|
||||
// returns a fingerprint.
|
||||
func sendProbe(c *dns.Client, addr string, f *fingerprint) *fingerprint {
|
||||
m := f.toProbe()
|
||||
func probe(c *dns.Client, addr string, f *fingerprint) *fingerprint {
|
||||
m := f.msg()
|
||||
r, err := c.Exchange(m, addr)
|
||||
if err != nil {
|
||||
return errorToFingerprint(err)
|
||||
}
|
||||
return msgToFingerprint(r)
|
||||
return toFingerprint(r)
|
||||
}
|
||||
|
||||
// This leads to strings like: "miek.nl.,IN,A,QUERY,NOERROR,qr,aa,tc,RD,ad,cd,z,1,0,0,1,DO,4096,NSID"
|
||||
|
@ -90,7 +67,7 @@ type fingerprint struct {
|
|||
|
||||
// String creates a (short) string representation of a dns message.
|
||||
// If a bit is set we uppercase the name 'AD' otherwise it's lowercase 'ad'.
|
||||
// This leads to strings like: "QUERY,NOERROR,qr,aa,tc,RD,ad,cd,z,1,0,0,1,DO,4096,NSID" // TODO fix doc
|
||||
// This leads to strings like: ".,IN,NS,QUERY,NOERROR,qr,aa,tc,RD,ad,cd,z,1,0,0,1,DO,4096,NSID"
|
||||
func (f *fingerprint) String() string {
|
||||
if f == nil {
|
||||
return "<nil>"
|
||||
|
@ -155,7 +132,7 @@ func (f *fingerprint) StringNoSections() string {
|
|||
return strings.Join(s[2:13], ",")
|
||||
}
|
||||
|
||||
// SetString set the string to fp.. todo
|
||||
// SetString sets the strings str to the fingerprint *f.
|
||||
func (f *fingerprint) setString(str string) {
|
||||
for i, s := range strings.Split(str, ",") {
|
||||
switch i {
|
||||
|
@ -208,7 +185,7 @@ func (f *fingerprint) setString(str string) {
|
|||
case 19:
|
||||
f.Nsid = s == strings.ToUpper("nsid")
|
||||
default:
|
||||
panic("unhandled fingerprint")
|
||||
panic("unhandled fingerprint field")
|
||||
}
|
||||
}
|
||||
return
|
||||
|
@ -231,7 +208,8 @@ func errorToFingerprint(e error) *fingerprint {
|
|||
return f
|
||||
}
|
||||
|
||||
func msgToFingerprint(m *dns.Msg) *fingerprint {
|
||||
// Convert a Msg to a fingerprint
|
||||
func toFingerprint(m *dns.Msg) *fingerprint {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -279,8 +257,8 @@ func msgToFingerprint(m *dns.Msg) *fingerprint {
|
|||
|
||||
// Create a dns message from a fingerprint string and
|
||||
// a DNS question. The order of a string is always the same.
|
||||
// QUERY,NOERROR,qr,aa,tc,RD,ad,ad,z,1,0,0,1,DO,4096,nsid
|
||||
func (f *fingerprint) toProbe() *dns.Msg {
|
||||
// .,IN,NS,QUERY,NOERROR,qr,aa,tc,RD,ad,ad,z,1,0,0,1,DO,4096,nsid
|
||||
func (f *fingerprint) msg() *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.MsgHdr.Id = dns.Id()
|
||||
m.Question = make([]dns.Question, 1)
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
// We handle the checking as a lexing program.
|
||||
// We use the lexer like Rob Pike lectures about in this
|
||||
// clip: http://www.youtube.com/watch?v=HxaD_trXwRE
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"dns"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type itemType int
|
||||
|
||||
type item struct {
|
||||
typ itemType
|
||||
val string
|
||||
}
|
||||
|
||||
const (
|
||||
itemError itemType = iota
|
||||
itemSoftware // the name of the DNS server software
|
||||
itemVendor // vendor of the DNS software
|
||||
itemVersionMajor // the major version of the software (empty if not determined)
|
||||
itemVersionMinor // the minor version of the software (empty if not determined)
|
||||
itemVersionPatch // the patch level of the software (empty if not determined)
|
||||
)
|
||||
|
||||
var itemString = map[itemType]string{
|
||||
itemError: "error",
|
||||
itemSoftware: "software",
|
||||
itemVendor: "vendor",
|
||||
itemVersionMajor: "major",
|
||||
itemVersionMinor: "minor",
|
||||
itemVersionPatch: "patch",
|
||||
}
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
type lexer struct {
|
||||
client *dns.Client // client used.
|
||||
addr string // addr of the server being scanned.
|
||||
fp *fingerprint // fingerprint to test.
|
||||
items chan item // channel of scanned items.
|
||||
state stateFn // the next function to enter.
|
||||
debug bool // if true, the fingerprints are printed.
|
||||
}
|
||||
|
||||
func (l *lexer) probe() *fingerprint {
|
||||
f := sendProbe(l.client, l.addr, l.fp)
|
||||
if l.debug {
|
||||
fmt.Printf(" QR fp: %s\n", f)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (l *lexer) emit(i *item) {
|
||||
l.items <- *i
|
||||
}
|
||||
|
||||
func (l *lexer) setString(s string) {
|
||||
l.fp.setString(s)
|
||||
if l.debug {
|
||||
fmt.Printf(" Q fp: %s\n", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) run() {
|
||||
go func() {
|
||||
for l.state != nil {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
close(l.items)
|
||||
}()
|
||||
}
|
||||
|
||||
func (l *lexer) verbose(s string) {
|
||||
if l.debug {
|
||||
fmt.Printf(" dns%s\n", s)
|
||||
}
|
||||
}
|
259
examples/fp/q.go
259
examples/fp/q.go
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func q(w dns.RequestWriter, m *dns.Msg) {
|
||||
|
@ -19,271 +18,39 @@ func q(w dns.RequestWriter, m *dns.Msg) {
|
|||
}
|
||||
|
||||
func main() {
|
||||
dnssec := flag.Bool("dnssec", false, "request DNSSEC records")
|
||||
query := flag.Bool("question", false, "show question")
|
||||
short := flag.Bool("short", false, "abbreviate long DNSKEY and RRSIG RRs")
|
||||
check := flag.Bool("check", false, "check internal DNSSEC consistency")
|
||||
port := flag.Int("port", 53, "port number to use")
|
||||
aa := flag.Bool("aa", false, "set AA flag in query")
|
||||
ad := flag.Bool("ad", false, "set AD flag in query")
|
||||
cd := flag.Bool("cd", false, "set CD flag in query")
|
||||
rd := flag.Bool("rd", true, "unset RD flag in query")
|
||||
tcp := flag.Bool("tcp", false, "TCP mode")
|
||||
nsid := flag.Bool("nsid", false, "ask for NSID")
|
||||
fp := flag.Bool("fp", false, "enable server detection")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [@server] [qtype] [qclass] [name ...]\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [-port 53] [@server]\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
conf, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
nameserver := "@" + conf.Servers[0]
|
||||
qtype := uint16(0)
|
||||
qclass := uint16(dns.ClassINET) // Default qclass
|
||||
var qname []string
|
||||
|
||||
flag.Parse()
|
||||
|
||||
Flags:
|
||||
for i := 0; i < flag.NArg(); i++ {
|
||||
// If it starts with @ it is a nameserver
|
||||
if flag.Arg(i)[0] == '@' {
|
||||
nameserver = flag.Arg(i)
|
||||
continue Flags
|
||||
}
|
||||
// First class, then type, to make ANY queries possible
|
||||
// And if it looks like type, it is a type
|
||||
if k, ok := dns.Str_rr[strings.ToUpper(flag.Arg(i))]; ok {
|
||||
qtype = k
|
||||
continue Flags
|
||||
}
|
||||
// If it looks like a class, it is a class
|
||||
if k, ok := dns.Str_class[strings.ToUpper(flag.Arg(i))]; ok {
|
||||
qclass = k
|
||||
continue Flags
|
||||
}
|
||||
// If it starts with TYPExxx it is unknown rr
|
||||
if strings.HasPrefix(flag.Arg(i), "TYPE") {
|
||||
i, e := strconv.Atoi(string([]byte(flag.Arg(i))[4:]))
|
||||
if e == nil {
|
||||
qtype = uint16(i)
|
||||
continue Flags
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Anything else is a qname
|
||||
qname = append(qname, flag.Arg(i))
|
||||
}
|
||||
if len(qname) == 0 {
|
||||
qname = make([]string, 1)
|
||||
qname[0] = "."
|
||||
qtype = dns.TypeNS
|
||||
}
|
||||
if qtype == 0 {
|
||||
qtype = dns.TypeA
|
||||
}
|
||||
|
||||
nameserver = string([]byte(nameserver)[1:]) // chop off @
|
||||
nameserver += ":" + strconv.Itoa(*port)
|
||||
|
||||
// ipv6 todo
|
||||
// We use the async query handling, just to show how
|
||||
// it is to be used.
|
||||
dns.HandleQueryFunc(".", q)
|
||||
dns.ListenAndQuery(nil, nil)
|
||||
c := dns.NewClient()
|
||||
if *tcp {
|
||||
c.Net = "tcp"
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.MsgHdr.Authoritative = *aa
|
||||
m.MsgHdr.AuthenticatedData = *ad
|
||||
m.MsgHdr.CheckingDisabled = *cd
|
||||
m.MsgHdr.RecursionDesired = *rd
|
||||
m.Question = make([]dns.Question, 1)
|
||||
if *dnssec || *nsid {
|
||||
o := new(dns.RR_OPT)
|
||||
o.Hdr.Name = "."
|
||||
o.Hdr.Rrtype = dns.TypeOPT
|
||||
if *dnssec {
|
||||
o.SetDo()
|
||||
o.SetUDPSize(dns.DefaultMsgSize)
|
||||
}
|
||||
if *nsid {
|
||||
o.SetNsid("")
|
||||
}
|
||||
m.Extra = append(m.Extra, o)
|
||||
}
|
||||
|
||||
if *fp {
|
||||
startParse(nameserver)
|
||||
return
|
||||
}
|
||||
for _, v := range qname {
|
||||
m.Question[0] = dns.Question{v, qtype, qclass}
|
||||
m.Id = dns.Id()
|
||||
if *query {
|
||||
fmt.Printf("%s\n", msgToFingerprint(m))
|
||||
fmt.Printf("%s\n", m.String())
|
||||
}
|
||||
c.Do(m, nameserver)
|
||||
}
|
||||
|
||||
i := 0
|
||||
forever:
|
||||
for {
|
||||
select {
|
||||
case r := <-dns.DefaultReplyChan:
|
||||
if r.Reply != nil {
|
||||
if r.Reply.Rcode == dns.RcodeSuccess {
|
||||
if r.Request.Id != r.Reply.Id {
|
||||
fmt.Printf("Id mismatch\n")
|
||||
}
|
||||
}
|
||||
if *check {
|
||||
sigCheck(r.Reply, nameserver, *tcp)
|
||||
nsecCheck(r.Reply)
|
||||
}
|
||||
if *short {
|
||||
r.Reply = shortMsg(r.Reply)
|
||||
}
|
||||
|
||||
fmt.Printf("%v", r.Reply)
|
||||
}
|
||||
i++
|
||||
if i == len(qname) {
|
||||
break forever
|
||||
}
|
||||
}
|
||||
fp := new(fingerprint)
|
||||
for _, s := range fingerprints() {
|
||||
fp.setString(s)
|
||||
fp1 := probe(c, nameserver, fp)
|
||||
println(s, "|", fp1.String())
|
||||
}
|
||||
}
|
||||
|
||||
func sectionCheck(set []dns.RR, server string, tcp bool) {
|
||||
for _, rr := range set {
|
||||
if rr.Header().Rrtype == dns.TypeRRSIG {
|
||||
rrset := getRRset(set, rr.Header().Name, rr.(*dns.RR_RRSIG).TypeCovered)
|
||||
key := getKey(rr.(*dns.RR_RRSIG).SignerName, rr.(*dns.RR_RRSIG).KeyTag, server, tcp)
|
||||
if key == nil {
|
||||
fmt.Printf(";? DNSKEY %s/%d not found\n", rr.(*dns.RR_RRSIG).SignerName, rr.(*dns.RR_RRSIG).KeyTag)
|
||||
continue
|
||||
}
|
||||
if err := rr.(*dns.RR_RRSIG).Verify(key, rrset); err != nil {
|
||||
fmt.Printf(";- Bogus signature, %s does not validate (DNSKEY %s/%d)\n", shortSig(rr.(*dns.RR_RRSIG)), key.Header().Name, key.KeyTag())
|
||||
} else {
|
||||
fmt.Printf(";+ Secure signature, %s validates (DNSKEY %s/%d)\n", shortSig(rr.(*dns.RR_RRSIG)), key.Header().Name, key.KeyTag())
|
||||
}
|
||||
}
|
||||
// A list of all the evil finger prints
|
||||
func fingerprints() []string {
|
||||
return []string {
|
||||
".,CH,TXT,QUERY,NOERROR,qr,aa,tc,RD,ra,ad,cd,z,0,0,0,0,DO,4097,NSID", // general
|
||||
"auThoRs.bInD.,CH,TXT,QUERY,NOERROR,qr,aa,tc,rd,ra,ad,cd,z,0,0,0,0,do,0,nsid", // case
|
||||
"bind.,NONE,SOA,NOTIFY,NOERROR,qr,AA,tc,RD,ra,ad,cd,Z,0,0,0,0,do,0,nsid", // notify
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have nsec3 records and if so, check them
|
||||
func nsecCheck(in *dns.Msg) {
|
||||
for _, r := range in.Answer {
|
||||
if r.Header().Rrtype == dns.TypeNSEC3 {
|
||||
goto Check
|
||||
}
|
||||
}
|
||||
for _, r := range in.Ns {
|
||||
if r.Header().Rrtype == dns.TypeNSEC3 {
|
||||
goto Check
|
||||
}
|
||||
}
|
||||
for _, r := range in.Extra {
|
||||
if r.Header().Rrtype == dns.TypeNSEC3 {
|
||||
goto Check
|
||||
}
|
||||
}
|
||||
return
|
||||
Check:
|
||||
w, err := in.Nsec3Verify(in.Question[0])
|
||||
switch w {
|
||||
case dns.NSEC3_NXDOMAIN:
|
||||
fmt.Printf(";+ Correct denial of existence (NSEC3/NXDOMAIN)\n")
|
||||
case dns.NSEC3_NODATA:
|
||||
fmt.Printf(";+ Correct denial of existence (NSEC3/NODATA)\n")
|
||||
default:
|
||||
// w == 0
|
||||
if err != nil {
|
||||
fmt.Printf(";- Incorrect denial of existence (NSEC3): %s\n",err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the sigs in the msg, get the signer's key (additional query), get the
|
||||
// rrset from the message, check the signature(s)
|
||||
func sigCheck(in *dns.Msg, server string, tcp bool) {
|
||||
sectionCheck(in.Answer, server, tcp)
|
||||
sectionCheck(in.Ns, server, tcp)
|
||||
sectionCheck(in.Extra, server, tcp)
|
||||
}
|
||||
|
||||
// Return the RRset belonging to the signature with name and type t
|
||||
func getRRset(l []dns.RR, name string, t uint16) []dns.RR {
|
||||
l1 := make([]dns.RR, 0)
|
||||
for _, rr := range l {
|
||||
if rr.Header().Name == name && rr.Header().Rrtype == t {
|
||||
l1 = append(l1, rr)
|
||||
}
|
||||
}
|
||||
return l1
|
||||
}
|
||||
|
||||
// Get the key from the DNS (uses the local resolver) and return them.
|
||||
// If nothing is found we return nil
|
||||
func getKey(name string, keytag uint16, server string, tcp bool) *dns.RR_DNSKEY {
|
||||
c := dns.NewClient()
|
||||
if tcp {
|
||||
c.Net = "tcp"
|
||||
}
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(name, dns.TypeDNSKEY)
|
||||
r, err := c.Exchange(m, server)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, k := range r.Answer {
|
||||
if k1, ok := k.(*dns.RR_DNSKEY); ok {
|
||||
if k1.KeyTag() == keytag {
|
||||
return k1
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// shorten RRSIG to "miek.nl RRSIG(NS)"
|
||||
func shortSig(sig *dns.RR_RRSIG) string {
|
||||
return sig.Header().Name + " RRSIG(" + dns.Rr_str[sig.TypeCovered] + ")"
|
||||
}
|
||||
|
||||
// Walk trough message and short Key data and Sig data
|
||||
func shortMsg(in *dns.Msg) *dns.Msg {
|
||||
for i := 0; i < len(in.Answer); i++ {
|
||||
in.Answer[i] = shortRR(in.Answer[i])
|
||||
}
|
||||
for i := 0; i < len(in.Ns); i++ {
|
||||
in.Ns[i] = shortRR(in.Ns[i])
|
||||
}
|
||||
for i := 0; i < len(in.Extra); i++ {
|
||||
in.Extra[i] = shortRR(in.Extra[i])
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func shortRR(r dns.RR) dns.RR {
|
||||
switch t := r.(type) {
|
||||
case *dns.RR_DS:
|
||||
t.Digest = "..."
|
||||
case *dns.RR_DNSKEY:
|
||||
t.PublicKey = "..."
|
||||
case *dns.RR_RRSIG:
|
||||
t.Signature = "..."
|
||||
case *dns.RR_NSEC3:
|
||||
t.Salt = "-" // Nobody cares
|
||||
if len(t.TypeBitMap) > 5 {
|
||||
t.TypeBitMap = t.TypeBitMap[1:5]
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue