From 90228852e1772e9778587f5c68f9c793d70b43ed Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 23 Jan 2012 18:03:15 +0100 Subject: [PATCH] Add seperate fp program --- examples/fp/Makefile | 8 ++ examples/fp/dns.go | 292 ++++++++++++++++++++++++++++++++++++++ examples/fp/fp.go | 324 +++++++++++++++++++++++++++++++++++++++++++ examples/fp/lex.go | 81 +++++++++++ examples/fp/q.go | 289 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 994 insertions(+) create mode 100644 examples/fp/Makefile create mode 100644 examples/fp/dns.go create mode 100644 examples/fp/fp.go create mode 100644 examples/fp/lex.go create mode 100644 examples/fp/q.go diff --git a/examples/fp/Makefile b/examples/fp/Makefile new file mode 100644 index 00000000..42f8d013 --- /dev/null +++ b/examples/fp/Makefile @@ -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 diff --git a/examples/fp/dns.go b/examples/fp/dns.go new file mode 100644 index 00000000..4e233cbd --- /dev/null +++ b/examples/fp/dns.go @@ -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 +} +*/ diff --git a/examples/fp/fp.go b/examples/fp/fp.go new file mode 100644 index 00000000..6e279ddb --- /dev/null +++ b/examples/fp/fp.go @@ -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 "" + } + // 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 +} diff --git a/examples/fp/lex.go b/examples/fp/lex.go new file mode 100644 index 00000000..69529b7b --- /dev/null +++ b/examples/fp/lex.go @@ -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) + } +} diff --git a/examples/fp/q.go b/examples/fp/q.go new file mode 100644 index 00000000..a22f40d4 --- /dev/null +++ b/examples/fp/q.go @@ -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 +}