diff --git a/TODO.markdown b/TODO.markdown index 63c86049..611de243 100644 --- a/TODO.markdown +++ b/TODO.markdown @@ -6,15 +6,13 @@ need to be fixed. * Speed, we can always go faster. A simple reflect server now hits 35/45K qps * go test; only works correct on my machine * Add handy zone data structure (r/b tree)? Or not... -* Use the Exchange structure to deal with errors when resolving, esp. Timeout -* Add tsig check in 'q'? -* Tsig is handled in the library, api for querying tsig status * Query source address? * NSECx bitmap length array of 256 block lens set to 0. scan RRs, save highest RR / 8 in each block. len is 2 * # non-0 blocks + sum block len - + We now allocate 32 bytes for each nsec3 seen + ## Examples to add * Nameserver, with a small zone, 1 KSK and online signing; diff --git a/client.go b/client.go index 156a94f4..9e63f5f8 100644 --- a/client.go +++ b/client.go @@ -39,7 +39,7 @@ type reply struct { conn net.Conn tsigRequestMAC string tsigTimersOnly bool - tsigStatus int + tsigStatus int } // A Request is a incoming message from a Client. @@ -150,6 +150,7 @@ func NewClient() *Client { c.QueryChan = DefaultQueryChan c.ReadTimeout = 2 * 1e9 c.WriteTimeout = 2 * 1e9 + c.TsigSecret = make(map[string]string) return c } @@ -382,25 +383,18 @@ func (w *reply) readClient(p []byte) (n int, err error) { // signature is calculated. func (w *reply) Send(m *Msg) error { if m.IsTsig() { - secret := m.Extra[len(m.Extra)-1].(*RR_TSIG).Hdr.Name - _, ok := w.Client().TsigSecret[secret] - if !ok { + name := m.Extra[len(m.Extra)-1].(*RR_TSIG).Hdr.Name + if _, ok := w.Client().TsigSecret[name]; !ok { return ErrSecret } - // TODO(mg): compression makes this fail - if err := TsigGenerate(m, w.Client().TsigSecret[secret], w.tsigRequestMAC, w.tsigTimersOnly); err != nil { + out, mac, err := TsigGenerate(m, w.Client().TsigSecret[name], w.tsigRequestMAC, w.tsigTimersOnly) + if err != nil { + return err + } + w.tsigRequestMAC = mac + if _, err = w.writeClient(out); err != nil { return err } - w.tsigRequestMAC = m.Extra[len(m.Extra)-1].(*RR_TSIG).MAC // Save the requestMAC for the next packet - } - out, ok := m.Pack() - if !ok { - return ErrPack - } - // Tsig calculation should happen here - _, err := w.writeClient(out) - if err != nil { - return err } return nil } diff --git a/ex/q/q.go b/ex/q/q.go index 37ad126d..d7617d9a 100644 --- a/ex/q/q.go +++ b/ex/q/q.go @@ -7,6 +7,7 @@ import ( "os" "strconv" "strings" + "time" ) var dnskey *dns.RR_DNSKEY @@ -32,7 +33,7 @@ func main() { short := flag.Bool("short", false, "abbreviate long DNSSEC records") check := flag.Bool("check", false, "check internal DNSSEC consistency") anchor := flag.String("anchor", "", "use the DNSKEY in this file for interal DNSSEC consistency") - //tsig := flag.String("tsig", "", "request tsig with key: [hmac:]name:key") + tsig := flag.String("tsig", "", "request tsig with key: [hmac:]name:key") 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") @@ -127,9 +128,7 @@ Flags: 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. + // 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() @@ -163,6 +162,16 @@ Flags: if *query { fmt.Printf("%s\n", m.String()) } + // Add tsig + if *tsig != "" { + if algo, name, secret, ok := tsigKeyParse(*tsig); ok { + m.SetTsig(name, algo, 300, uint64(time.Now().Unix())) + c.TsigSecret[name] = secret; + } else { + fmt.Fprintf(os.Stderr, "TSIG key error\n") + return + } + } c.Do(m, nameserver) } @@ -219,6 +228,24 @@ forever: } } +func tsigKeyParse(s string) (algo, name, secret string, ok bool) { + s1 := strings.SplitN(s, ":", 3) + switch len(s1) { + case 2: + return "hmac-md5.sig-alg.reg.int.", s1[0], s1[1], true + case 3: + switch s1[0] { + case "hmac-md5": + return "hmac-md5.sig-alg.reg.int.", s1[0], s1[1], true + case "hmac-sha1": + return "hmac-sha1.", s1[1], s1[2], true + case "hmac-sha256": + return "hmac-sha256.", s1[1], s1[2], true + } + } + return +} + func sectionCheck(set []dns.RR, server string, tcp bool) { var key *dns.RR_DNSKEY for _, rr := range set { diff --git a/msg.go b/msg.go index 9c9e470a..434eda27 100644 --- a/msg.go +++ b/msg.go @@ -42,7 +42,7 @@ var ( ErrTime error = &Error{Err: "dns: bad time"} ErrNoSig error = &Error{Err: "dns: no signature found"} ErrSig error = &Error{Err: "dns: bad signature"} - ErrSecret error = &Error{Err: "dns: no secret defined"} + ErrSecret error = &Error{Err: "dns: no secrets defined"} ErrSigGen error = &Error{Err: "dns: bad signature generation"} ErrAuth error = &Error{Err: "dns: bad authentication"} ErrXfrSoa error = &Error{Err: "dns: no SOA seen"} diff --git a/parse_test.go b/parse_test.go index 5beea8be..69de738f 100644 --- a/parse_test.go +++ b/parse_test.go @@ -189,10 +189,10 @@ func TestParseDirectiveMisc(t *testing.T) { // Another one hear, geared to NSECx func TestParseNSEC(t *testing.T) { nsectests := map[string]string{ - "nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F", + "nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F", "p2209hipbpnm681knjnu0m1febshlv4e.nl. IN NSEC3 1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM": "p2209hipbpnm681knjnu0m1febshlv4e.nl.\t3600\tIN\tNSEC3\t1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM", - "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC", - "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534", + "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC", + "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534", } for i, o := range nsectests { rr, e := NewRR(i) diff --git a/rawmsg.go b/rawmsg.go index 0e969135..3585fca1 100644 --- a/rawmsg.go +++ b/rawmsg.go @@ -4,24 +4,39 @@ package dns -// RawSetId sets the message ID in buf. The offset 'off' must -// be positioned at the beginning of the message. -func RawSetId(msg []byte, off int, id uint16) bool { - msg[off], msg[off+1] = packUint16(id) - return true +// RawSetId sets the message ID in buf. +func RawSetId(msg []byte, i uint16) { + msg[0], msg[1] = packUint16(i) +} + +// RawSetQuestionLen sets the len of the question section. +func RawSetQuestionLen(msg []byte, i uint16) { + msg[4], msg[5] = packUint16(i) +} + +// RawSetAnswerLen sets the len of the question section. +func RawSetAnswerLen(msg []byte, i uint16) { + msg[6], msg[7] = packUint16(i) +} + +// RawSetsNsLen sets the len of the question section. +func RawSetNsLen(msg []byte, i uint16) { + msg[8], msg[9] = packUint16(i) +} + +// RawSetExtraLen sets the len of the question section. +func RawSetExtraLen(msg []byte, i uint16) { + msg[10], msg[11] = packUint16(i) } // RawSetRdlength sets the rdlength in the header of // the RR. The offset 'off' must be positioned at the // start of the header of the RR, 'end' must be the -// end of the RR. -func RawSetRdlength(msg []byte, off, end int) bool { +// end of the RR. There is no check if we overrun the buffer. +func RawSetRdlength(msg []byte, off, end int) { // We are at the start of the header, walk the domainname (might be compressed) Loop: for { - if off > len(msg) { - return false - } c := int(msg[off]) off++ switch c & 0xC0 { @@ -40,11 +55,8 @@ Loop: // The domainname has been seen, we at the start of the fixed part in the header. // Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length. off += 2 + 2 + 4 - if off+1 > len(msg) { - return false - } //off+1 is the end of the header, 'end' is the end of the rr //so 'end' - 'off+2' is the lenght of the rdata msg[off], msg[off+1] = packUint16(uint16(end - (off + 2))) - return true + return } diff --git a/tsig.go b/tsig.go index 82ace89a..fb7e4c64 100644 --- a/tsig.go +++ b/tsig.go @@ -82,34 +82,33 @@ type timerWireFmt struct { Fudge uint16 } -// TsigGenerate adds an TSIG RR to a message. The message should contain -// a "stub" TsigRR with the algorithm, key name (owner name of the RR), -// time fudge (defaults to 300 seconds) and the current time -// The TSIG MAC is saved in that Tsig RR. -// When TsigGenerate is called for the -// first time requestMAC is set to the empty string. -// If something goes wrong an error is returned, otherwise it is nil. -// TODO this needs to work on []byte, not *Msg, to take -// compression into account -// This -func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) error { +// TsigGenerate fills out the TSIG record attached to the message. +// The message should contain +// a "stub" TSIG RR with the algorithm, key name (owner name of the RR), +// time fudge (defaults to 300 seconds) and the current time +// The TSIG MAC is saved in that Tsig RR. +// When TsigGenerate is called for the first time requestMAC is set to the empty string and +// timersOnly is false. +// If something goes wrong an error is returned, otherwise it is nil. +func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { if !m.IsTsig() { - // panic? panic? panic("TSIG not last RR in additional") } // If we barf here, the caller is to blame rawsecret, err := packBase64([]byte(secret)) if err != nil { - return err + return nil, "", err } rr := m.Extra[len(m.Extra)-1].(*RR_TSIG) m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg - mbuf, _ := m.Pack() + mbuf, ok := m.Pack() + if !ok { + return nil, "", ErrPack + } buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) t := new(RR_TSIG) - var h hash.Hash switch rr.Algorithm { case HmacMD5: @@ -119,10 +118,10 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) error { case HmacSHA256: h = hmac.New(sha256.New, []byte(rawsecret)) default: - return ErrKeyAlg + return nil, "", ErrKeyAlg } - - t.MAC = hex.EncodeToString(h.Sum(buf)) + io.WriteString(h, string(buf)) + t.MAC = hex.EncodeToString(h.Sum(nil)) t.MACSize = uint16(len(t.MAC) / 2) // Size is half! t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0} @@ -130,9 +129,16 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) error { t.TimeSigned = rr.TimeSigned t.Algorithm = rr.Algorithm t.OrigId = m.MsgHdr.Id - - m.Extra = append(m.Extra, t) - return nil + + tbuf := make([]byte, t.Len()) + if off, ok := packRR(t, tbuf, 0, nil, false); ok { + tbuf = tbuf[:off] // reset to actual size used + } else { + return nil, "", ErrPack + } + mbuf = append(mbuf, tbuf...) + RawSetExtraLen(mbuf, uint16(len(m.Extra)+1)) + return mbuf, t.MAC, nil } // TsigVerify verifies the TSIG on a message. diff --git a/types.go b/types.go index 9abb8e68..b1aa1a72 100644 --- a/types.go +++ b/types.go @@ -178,7 +178,7 @@ func (rr *RR_ANY) Len() int { } type RR_CNAME struct { - Hdr RR_Header + Hdr RR_Header Target string "cdomain-name" } @@ -1096,7 +1096,7 @@ func dateToTime(s string) (uint32, error) { // need for RFC1982 calculations as this date is 48 bits func tsigTimeToDate(t uint64) string { // only use the lower 48 bits, TODO(mg), check for 48 bit size - return "" + return "TODO" /* ti := time.Unix(int64(t), 0).Unix() return ti.Format("20060102150405") diff --git a/zscan_rr.go b/zscan_rr.go index ad158c32..55a6cfd1 100644 --- a/zscan_rr.go +++ b/zscan_rr.go @@ -666,7 +666,7 @@ func setNSEC3(h RR_Header, c chan lex, o, f string) (RR, *ParseError) { rr.TypeBitMap = make([]uint16, 0) var ( - k uint16 + k uint16 ok bool ) l = <-c @@ -676,7 +676,7 @@ func setNSEC3(h RR_Header, c chan lex, o, f string) (RR, *ParseError) { // Ok case _STRING: if k, ok = Str_rr[strings.ToUpper(l.token)]; !ok { - if k, ok = typeToInt(l.token); ! ok { + if k, ok = typeToInt(l.token); !ok { return nil, &ParseError{f, "bad NSEC3 TypeBitMap", l} } }