diff --git a/README.md b/README.md index b09cb4cf..3ef388c2 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository. * 6605 - ECDSA * 6725 - IANA Registry Update * 6742 - ILNP DNS +* 6844 - CAA record * 6891 - EDNS0 update * 6895 - DNS IANA considerations * 6975 - Algorithm Understanding in DNSSEC @@ -138,6 +139,5 @@ Example programs can be found in the `github.com/miekg/exdns` repository. * privatekey.Precompute() when signing? * Last remaining RRs: APL, ATMA, A6 and NXT and IPSECKEY; * Missing in parsing: ISDN, UNSPEC, ATMA; -* CAA parsing is broken; * NSEC(3) cover/match/closest enclose; * Replies with TC bit are not parsed to the end; diff --git a/dns_test.go b/dns_test.go index d16fe09f..7cf810ea 100644 --- a/dns_test.go +++ b/dns_test.go @@ -429,9 +429,6 @@ func TestToRFC3597(t *testing.T) { func TestNoRdataPack(t *testing.T) { data := make([]byte, 1024) for typ, fn := range typeToRR { - if typ == TypeCAA { - continue // TODO(miek): known omission - } r := fn() *r.Header() = RR_Header{Name: "miek.nl.", Rrtype: typ, Class: ClassINET, Ttl: 3600} _, err := PackRR(r, data, 0, nil, false) diff --git a/msg.go b/msg.go index 7ccd5319..dc989557 100644 --- a/msg.go +++ b/msg.go @@ -543,6 +543,36 @@ func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) { return offset, nil } +func packOctetString(s string, msg []byte, offset int, tmp []byte) (int, error) { + if offset >= len(msg) { + return offset, ErrBuf + } + bs := tmp[:len(s)] + copy(bs, s) + for i := 0; i < len(bs); i++ { + if len(msg) <= offset { + return offset, ErrBuf + } + if bs[i] == '\\' { + i++ + if i == len(bs) { + break + } + // check for \DDD + if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { + msg[offset] = dddToByte(bs[i:]) + i += 2 + } else { + msg[offset] = bs[i] + } + } else { + msg[offset] = bs[i] + } + offset++ + } + return offset, nil +} + func unpackTxt(msg []byte, offset, rdend int) ([]string, int, error) { var err error var ss []string @@ -890,6 +920,12 @@ func packStructValue(val reflect.Value, msg []byte, off int, compression map[str // length of string. String is RAW (not encoded in hex, nor base64) copy(msg[off:off+len(s)], s) off += len(s) + case `dns:"octet"`: + bytesTmp := make([]byte, 0) + off, err = packOctetString(fv.String(), msg, off, bytesTmp) + if err != nil { + return lenmsg, err + } case `dns:"txt"`: fallthrough case "": @@ -1254,6 +1290,13 @@ func unpackStructValue(val reflect.Value, msg []byte, off int) (off1 int, err er switch val.Type().Field(i).Tag { default: return lenmsg, &Error{"bad tag unpacking string: " + val.Type().Field(i).Tag.Get("dns")} + case `dns:"octet"`: + strend := lenrd + if strend > lenmsg { + return lenmsg, &Error{err: "overflow unpacking octet"} + } + s = string(msg[off:strend]) + off = strend case `dns:"hex"`: hexend := lenrd if val.FieldByName("Hdr").FieldByName("Rrtype").Uint() == uint64(TypeHIP) { diff --git a/parse_test.go b/parse_test.go index b0d02f45..1c2ef865 100644 --- a/parse_test.go +++ b/parse_test.go @@ -1455,3 +1455,25 @@ func TestParseHINFO(t *testing.T) { } } } + +func TestParseCAA(t *testing.T) { + lt := map[string]string{ + "example.net. CAA 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"", + "example.net. CAA 0 issuewild \"symantec.com; stuff\"": "example.net.\t3600\tIN\tCAA\t0 issuewild \"symantec.com; stuff\"", + "example.net. CAA 128 tbs \"critical\"": "example.net.\t3600\tIN\tCAA\t128 tbs \"critical\"", + "example.net. CAA 2 auth \"0>09\\006\\010+\\006\\001\\004\\001\\214y\\002\\003\\001\\006\\009`\\134H\\001e\\003\\004\\002\\001\\004 y\\209\\012\\221r\\220\\156Q\\218\\150\\150{\\166\\245:\\231\\182%\\157:\\133\\179}\\1923r\\238\\151\\255\\128q\\145\\002\\001\\000\"": "example.net.\t3600\tIN\tCAA\t2 auth \"0>09\\006\\010+\\006\\001\\004\\001\\214y\\002\\003\\001\\006\\009`\\134H\\001e\\003\\004\\002\\001\\004 y\\209\\012\\221r\\220\\156Q\\218\\150\\150{\\166\\245:\\231\\182%\\157:\\133\\179}\\1923r\\238\\151\\255\\128q\\145\\002\\001\\000\"", + "example.net. TYPE257 0 issue \"symantec.com\"": "example.net.\t3600\tIN\tCAA\t0 issue \"symantec.com\"", + } + for i, o := range lt { + rr, err := NewRR(i) + if err != nil { + t.Error("failed to parse RR: ", err) + continue + } + if rr.String() != o { + t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String()) + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} diff --git a/types.go b/types.go index bd95fd9a..9f00ce89 100644 --- a/types.go +++ b/types.go @@ -501,6 +501,34 @@ func sprintName(s string) string { return string(dst) } +func sprintCAAValue(s string) string { + src := []byte(s) + dst := make([]byte, 0, len(src)) + dst = append(dst, '"') + for i := 0; i < len(src); { + if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { + dst = append(dst, src[i:i+2]...) + i += 2 + } else { + b, n := nextByte(src, i) + if n == 0 { + i++ // dangling back slash + } else if b == '.' { + dst = append(dst, b) + } else { + if b < ' ' || b > '~' { + dst = appendByte(dst, b) + } else { + dst = append(dst, b) + } + } + i += n + } + } + dst = append(dst, '"') + return string(dst) +} + func sprintTxt(txt []string) string { var out []byte for i, s := range txt { @@ -543,21 +571,24 @@ func appendTXTStringByte(s []byte, b byte) []byte { return append(s, '\\', b) } if b < ' ' || b > '~' { - var buf [3]byte - bufs := strconv.AppendInt(buf[:0], int64(b), 10) - s = append(s, '\\') - for i := 0; i < 3-len(bufs); i++ { - s = append(s, '0') - } - for _, r := range bufs { - s = append(s, r) - } - return s - + return appendByte(s, b) } return append(s, b) } +func appendByte(s []byte, b byte) []byte { + var buf [3]byte + bufs := strconv.AppendInt(buf[:0], int64(b), 10) + s = append(s, '\\') + for i := 0; i < 3-len(bufs); i++ { + s = append(s, '0') + } + for _, r := range bufs { + s = append(s, r) + } + return s +} + func nextByte(b []byte, offset int) (byte, int) { if offset >= len(b) { return 0, 0 @@ -1527,8 +1558,6 @@ func (rr *EUI64) copy() RR { return &EUI64{*rr.Hdr.copyHeader(), rr.Ad func (rr *EUI64) String() string { return rr.Hdr.String() + euiToString(rr.Address, 64) } func (rr *EUI64) len() int { return rr.Hdr.len() + 8 } -// Support in incomplete - just handle it as unknown record -/* type CAA struct { Hdr RR_Header Flag uint8 @@ -1538,14 +1567,9 @@ type CAA struct { func (rr *CAA) Header() *RR_Header { return &rr.Hdr } func (rr *CAA) copy() RR { return &CAA{*rr.Hdr.copyHeader(), rr.Flag, rr.Tag, rr.Value} } -func (rr *CAA) len() int { return rr.Hdr.len() + 1 + len(rr.Tag) + 1 + len(rr.Value) } +func (rr *CAA) len() int { return rr.Hdr.len() + 1 + len(rr.Tag) + len(rr.Value)/2 } +func (rr *CAA) String() string { return rr.Hdr.String() + strconv.Itoa(int(rr.Flag)) + " " + rr.Tag + " " + sprintCAAValue(rr.Value) } -func (rr *CAA) String() string { - s := rr.Hdr.String() + strconv.FormatInt(int64(rr.Flag), 10) + " " + rr.Tag - s += strconv.QuoteToASCII(rr.Value) - return s -} -*/ type UID struct { Hdr RR_Header @@ -1668,10 +1692,10 @@ func copyIP(ip net.IP) net.IP { // Map of constructors for each RR type. var typeToRR = map[uint16]func() RR{ - TypeA: func() RR { return new(A) }, - TypeAAAA: func() RR { return new(AAAA) }, - TypeAFSDB: func() RR { return new(AFSDB) }, - // TypeCAA: func() RR { return new(CAA) }, + TypeA: func() RR { return new(A) }, + TypeAAAA: func() RR { return new(AAAA) }, + TypeAFSDB: func() RR { return new(AFSDB) }, + TypeCAA: func() RR { return new(CAA) }, TypeCDS: func() RR { return new(CDS) }, TypeCERT: func() RR { return new(CERT) }, TypeCNAME: func() RR { return new(CNAME) }, diff --git a/zscan_rr.go b/zscan_rr.go index e5ca2615..3763d3f4 100644 --- a/zscan_rr.go +++ b/zscan_rr.go @@ -2170,10 +2170,44 @@ func setIPSECKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) return rr, nil, c1 } +func setCAA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(CAA) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + i, err := strconv.Atoi(l.token) + if err != nil { + return nil, &ParseError{f, "bad CAA Flag", l}, "" + } + rr.Flag = uint8(i) + + <-c // zBlank + l = <-c // zString + if l.value != zString { + return nil, &ParseError{f, "bad CAA Tag", l}, "" + } + rr.Tag = l.token + + <-c // zBlank + s, e, c1 := endingToTxtSlice(c, "bad CAA Value", f) + if e != nil { + return nil, e, "" + } + if len(s) > 1 { + return nil, &ParseError{f, "bad CAA Value", l}, "" + } else { + rr.Value = s[0] + } + return rr, nil, c1 +} + var typeToparserFunc = map[uint16]parserFunc{ TypeAAAA: parserFunc{setAAAA, false}, TypeAFSDB: parserFunc{setAFSDB, false}, TypeA: parserFunc{setA, false}, + TypeCAA: parserFunc{setCAA, true}, TypeCDS: parserFunc{setCDS, true}, TypeCDNSKEY: parserFunc{setCDNSKEY, true}, TypeCERT: parserFunc{setCERT, true},