Add seperate fp program

This commit is contained in:
Miek Gieben 2012-01-23 18:03:15 +01:00
parent 8f66b74c86
commit 90228852e1
5 changed files with 994 additions and 0 deletions

8
examples/fp/Makefile Normal file
View File

@ -0,0 +1,8 @@
# Copyright 2009 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# 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
DEPS=../../
include $(GOROOT)/src/Make.cmd

292
examples/fp/dns.go Normal file
View File

@ -0,0 +1,292 @@
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
}
*/

324
examples/fp/fp.go Normal file
View File

@ -0,0 +1,324 @@
// Package main provides ...
package main
import (
"dns"
"fmt"
"strconv"
"strings"
)
// .,IN,NS,QUERY,NOERROR,qr,aa,tc,rd,ra,ad,cd,z,0,0,0,0,do,0,nsid
const (
// Detected software types
NSD = "NSD"
BIND = "BIND"
POWERDNS = "PowerDNS"
WINDOWSDNS = "Windows DNS"
MARADNS = "MaraDNS"
NEUSTARDNS = "Neustar DNS"
ATLAS = "Atlas"
YADIFA = "Yadifa"
// Vendors
ISC = "ISC"
MARA = "MaraDNS.org" // check
NLNETLABS = "NLnet Labs"
MICROSOFT = "Microsoft"
POWER = "PowerDNS.com"
NEUSTAR = "Neustar"
VERISIGN = "Verisign"
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
// returns a fingerprint.
func sendProbe(c *dns.Client, addr string, f *fingerprint) *fingerprint {
m := f.toProbe()
r, err := c.Exchange(m, addr)
if err != nil {
return errorToFingerprint(err)
}
return msgToFingerprint(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"
type fingerprint struct {
Query dns.Question // Question to ask or Question of the reply
Error error
Opcode int
Rcode int
Response bool
Authoritative bool
Truncated bool
RecursionDesired bool
RecursionAvailable bool
AuthenticatedData bool
CheckingDisabled bool
Zero bool
Question int
Answer int
Ns int
Extra int
Do bool
UDPSize int
Nsid bool
}
// 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
func (f *fingerprint) String() string {
if f == nil {
return "<nil>"
}
// Use the same order as in Perl's fpdns. But use much more flags.
var s string
// The Question.
if len(f.Query.Name) == 0 {
s = "."
} else {
s = f.Query.Name
}
if _, ok := dns.Class_str[f.Query.Qclass]; ok {
s += "," + dns.Class_str[f.Query.Qclass]
} else {
s += "," + "CLASS" + strconv.Itoa(int(f.Query.Qclass))
}
if _, ok := dns.Rr_str[f.Query.Qtype]; ok {
s += "," + dns.Rr_str[f.Query.Qtype]
} else {
s += "," + "TYPE" + strconv.Itoa(int(f.Query.Qtype))
}
if op, ok := dns.Opcode_str[f.Opcode]; ok {
s += "," + op
} else { // number
s += "," + valueOfInt(f.Opcode)
}
if op, ok := dns.Rcode_str[f.Rcode]; ok {
s += "," + op
} else { // number
s += "," + valueOfInt(f.Rcode)
}
s += valueOfBool(f.Response, ",qr")
s += valueOfBool(f.Authoritative, ",aa")
s += valueOfBool(f.Truncated, ",tc")
s += valueOfBool(f.RecursionDesired, ",rd")
s += valueOfBool(f.RecursionAvailable, ",ra")
s += valueOfBool(f.AuthenticatedData, ",ad")
s += valueOfBool(f.CheckingDisabled, ",cd")
s += valueOfBool(f.Zero, ",z")
s += "," + valueOfInt(f.Question)
s += "," + valueOfInt(f.Answer)
s += "," + valueOfInt(f.Ns)
s += "," + valueOfInt(f.Extra)
s += valueOfBool(f.Do, ",do")
s += "," + valueOfInt(f.UDPSize)
s += valueOfBool(f.Nsid, ",nsid")
return s
}
// fingerStringNoSections returns the strings representation
// without the sections' count and the EDNS0 stuff and the query
func (f *fingerprint) StringNoSections() string {
s := strings.SplitN(f.String(), ",", 14)
return strings.Join(s[2:13], ",")
}
// SetString set the string to fp.. todo
func (f *fingerprint) setString(str string) {
for i, s := range strings.Split(str, ",") {
switch i {
case 0: // question section domain name
f.Query.Name = s
case 1: // Qclass
f.Query.Qclass = 0
if c, ok := dns.Str_class[s]; ok {
f.Query.Qclass = c
}
case 2: // Qtype
f.Query.Qtype = 0
if c, ok := dns.Str_rr[s]; ok {
f.Query.Qtype = c
}
case 3:
if op, ok := dns.Str_opcode[s]; ok {
f.Opcode = op
} else { // number
f.Opcode = valueOfString(s)
}
case 4:
if op, ok := dns.Str_rcode[s]; ok {
f.Rcode = op
} else { // number
f.Rcode = valueOfString(s)
}
case 5:
f.Response = s == strings.ToUpper("qr")
case 6:
f.Authoritative = s == strings.ToUpper("aa")
case 7:
f.Truncated = s == strings.ToUpper("tc")
case 8:
f.RecursionDesired = s == strings.ToUpper("rd")
case 9:
f.RecursionAvailable = s == strings.ToUpper("ra")
case 10:
f.AuthenticatedData = s == strings.ToUpper("ad")
case 11:
f.CheckingDisabled = s == strings.ToUpper("cd")
case 12:
f.Zero = s == strings.ToUpper("z")
case 13, 14, 15, 16:
// Can not set lenght of the section in the message
case 17:
f.Do = s == strings.ToUpper("do")
case 18:
f.UDPSize = valueOfString(s)
case 19:
f.Nsid = s == strings.ToUpper("nsid")
default:
panic("unhandled fingerprint")
}
}
return
}
func (f *fingerprint) ok() bool {
return f.Error == nil
}
func (f *fingerprint) error() string {
if f.Error == nil {
panic("error is nil")
}
return f.Error.Error()
}
func errorToFingerprint(e error) *fingerprint {
f := new(fingerprint)
f.Error = e
return f
}
func msgToFingerprint(m *dns.Msg) *fingerprint {
if m == nil {
return nil
}
h := m.MsgHdr
f := new(fingerprint)
// Set the old query
if len(m.Question) > 0 {
f.Query.Name = m.Question[0].Name
f.Query.Qtype = m.Question[0].Qtype
f.Query.Qclass = m.Question[0].Qclass
}
f.Opcode = h.Opcode
f.Rcode = h.Rcode
f.Response = h.Response
f.Authoritative = h.Authoritative
f.Truncated = h.Truncated
f.RecursionDesired = h.RecursionDesired
f.RecursionAvailable = h.RecursionAvailable
f.AuthenticatedData = h.AuthenticatedData
f.CheckingDisabled = h.CheckingDisabled
f.Zero = h.Zero
f.Question = len(m.Question)
f.Answer = len(m.Answer)
f.Ns = len(m.Ns)
f.Extra = len(m.Extra)
f.Do = false
f.UDPSize = 0
for _, r := range m.Extra {
if r.Header().Rrtype == dns.TypeOPT {
// version is always 0 - and I cannot set it anyway
f.Do = r.(*dns.RR_OPT).Do()
f.UDPSize = int(r.(*dns.RR_OPT).UDPSize())
if len(r.(*dns.RR_OPT).Option) == 1 {
// Only support NSID atm
f.Nsid = r.(*dns.RR_OPT).Option[0].Code == dns.OptionCodeNSID
}
}
}
return f
}
// 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 {
m := new(dns.Msg)
m.MsgHdr.Id = dns.Id()
m.Question = make([]dns.Question, 1)
m.Question[0] = dns.Question{f.Query.Name, f.Query.Qtype, f.Query.Qclass}
m.MsgHdr.Opcode = f.Opcode
m.MsgHdr.Rcode = f.Rcode
m.MsgHdr.Response = f.Response
m.MsgHdr.Authoritative = f.Authoritative
m.MsgHdr.Truncated = f.Truncated
m.MsgHdr.RecursionDesired = f.RecursionDesired
m.MsgHdr.AuthenticatedData = f.AuthenticatedData
m.MsgHdr.CheckingDisabled = f.CheckingDisabled
m.MsgHdr.Zero = f.Zero
if f.Do {
// Add an OPT section.
m.SetEdns0(0, true)
// We have added an OPT RR, set the size.
m.Extra[0].(*dns.RR_OPT).SetUDPSize(uint16(f.UDPSize))
if f.Nsid {
m.Extra[0].(*dns.RR_OPT).SetNsid("")
}
}
return m
}
func valueOfBool(b bool, w string) string {
if b {
return strings.ToUpper(w)
}
return strings.ToLower(w)
}
func valueOfInt(i int) string {
return strconv.Itoa(i)
}
func valueOfString(s string) int {
i, _ := strconv.Atoi(s)
return i
}

81
examples/fp/lex.go Normal file
View File

@ -0,0 +1,81 @@
// 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)
}
}

289
examples/fp/q.go Normal file
View File

@ -0,0 +1,289 @@
package main
import (
"dns"
"flag"
"fmt"
"os"
"strconv"
"strings"
)
func q(w dns.RequestWriter, m *dns.Msg) {
w.Send(m)
r, err := w.Receive()
if err != nil {
fmt.Printf("%s\n", err.Error())
}
w.Write(r)
}
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])
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
}
}
// 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
}
}
}
}
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())
}
}
}
}
// 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
}