dns/dnsmessage: correctly handle multiple and >255 byte TXT records

Previously, we only accepted a single string for TXT records and then
chunked it into 255 byte segments. This is wrong. TXT records are a
sequence of strings, each up to 255 bytes.

Updates golang/go#24288

Change-Id: Ib2c085ec127ccecf0c7bda930100b667cabc1f4b
Reviewed-on: https://go-review.googlesource.com/100196
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/dns/dnsmessage/message.go b/dns/dnsmessage/message.go
index 624f9b6..0ab4aab 100644
--- a/dns/dnsmessage/message.go
+++ b/dns/dnsmessage/message.go
@@ -90,6 +90,7 @@
 	errTooManyAuthorities = errors.New("too many Authorities to pack (>65535)")
 	errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
 	errNonCanonicalName   = errors.New("name is not in canonical format (it must end with a .)")
+	errStringTooLong      = errors.New("character string exceeds maximum length (255)")
 )
 
 // Internal constants.
@@ -218,6 +219,7 @@
 	return 0
 }
 
+// pack appends the wire format of the header to msg.
 func (h *header) pack(msg []byte) []byte {
 	msg = packUint16(msg, h.id)
 	msg = packUint16(msg, h.bits)
@@ -280,6 +282,7 @@
 	realType() Type
 }
 
+// pack appends the wire format of the Resource to msg.
 func (r *Resource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	if r.Body == nil {
 		return msg, errNilResouceBody
@@ -1311,9 +1314,10 @@
 	Length uint16
 }
 
-// pack packs all of the fields in a ResourceHeader except for the length. The
-// length bytes are returned as a slice so they can be filled in after the rest
-// of the Resource has been packed.
+// pack appends the wire format of the ResourceHeader to oldMsg.
+//
+// The bytes where length was packed are returned as a slice so they can be
+// updated after the rest of the Resource has been packed.
 func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]int, compressionOff int) (msg []byte, length []byte, err error) {
 	msg = oldMsg
 	if msg, err = h.Name.pack(msg, compression, compressionOff); err != nil {
@@ -1385,6 +1389,7 @@
 	return newOff, nil
 }
 
+// packUint16 appends the wire format of field to msg.
 func packUint16(msg []byte, field uint16) []byte {
 	return append(msg, byte(field>>8), byte(field))
 }
@@ -1403,6 +1408,7 @@
 	return off + uint16Len, nil
 }
 
+// packType appends the wire format of field to msg.
 func packType(msg []byte, field Type) []byte {
 	return packUint16(msg, uint16(field))
 }
@@ -1416,6 +1422,7 @@
 	return skipUint16(msg, off)
 }
 
+// packClass appends the wire format of field to msg.
 func packClass(msg []byte, field Class) []byte {
 	return packUint16(msg, uint16(field))
 }
@@ -1429,6 +1436,7 @@
 	return skipUint16(msg, off)
 }
 
+// packUint32 appends the wire format of field to msg.
 func packUint32(msg []byte, field uint32) []byte {
 	return append(
 		msg,
@@ -1454,17 +1462,16 @@
 	return off + uint32Len, nil
 }
 
-func packText(msg []byte, field string) []byte {
-	for len(field) > 0 {
-		l := len(field)
-		if l > 255 {
-			l = 255
-		}
-		msg = append(msg, byte(l))
-		msg = append(msg, field[:l]...)
-		field = field[l:]
+// packText appends the wire format of field to msg.
+func packText(msg []byte, field string) ([]byte, error) {
+	l := len(field)
+	if l > 255 {
+		return nil, errStringTooLong
 	}
-	return msg
+	msg = append(msg, byte(l))
+	msg = append(msg, field...)
+
+	return msg, nil
 }
 
 func unpackText(msg []byte, off int) (string, int, error) {
@@ -1490,6 +1497,7 @@
 	return endOff, nil
 }
 
+// packBytes appends the wire format of field to msg.
 func packBytes(msg []byte, field []byte) []byte {
 	return append(msg, field...)
 }
@@ -1534,7 +1542,7 @@
 	return string(n.Data[:n.Length])
 }
 
-// pack packs a domain name.
+// pack appends the wire format of the Name to msg.
 //
 // Domain names are a sequence of counted strings split at the dots. They end
 // with a zero-length string. Compression can be used to reuse domain suffixes.
@@ -1716,6 +1724,7 @@
 	Class Class
 }
 
+// pack appends the wire format of the Question to msg.
 func (q *Question) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	msg, err := q.Name.pack(msg, compression, compressionOff)
 	if err != nil {
@@ -1796,6 +1805,7 @@
 	return TypeCNAME
 }
 
+// pack appends the wire format of the CNAMEResource to msg.
 func (r *CNAMEResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return r.CNAME.pack(msg, compression, compressionOff)
 }
@@ -1818,6 +1828,7 @@
 	return TypeMX
 }
 
+// pack appends the wire format of the MXResource to msg.
 func (r *MXResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	oldMsg := msg
 	msg = packUint16(msg, r.Pref)
@@ -1849,6 +1860,7 @@
 	return TypeNS
 }
 
+// pack appends the wire format of the NSResource to msg.
 func (r *NSResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return r.NS.pack(msg, compression, compressionOff)
 }
@@ -1870,6 +1882,7 @@
 	return TypePTR
 }
 
+// pack appends the wire format of the PTRResource to msg.
 func (r *PTRResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return r.PTR.pack(msg, compression, compressionOff)
 }
@@ -1901,6 +1914,7 @@
 	return TypeSOA
 }
 
+// pack appends the wire format of the SOAResource to msg.
 func (r *SOAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	oldMsg := msg
 	msg, err := r.NS.pack(msg, compression, compressionOff)
@@ -1953,19 +1967,28 @@
 
 // A TXTResource is a TXT Resource record.
 type TXTResource struct {
-	Txt string // Not a domain name.
+	TXT []string
 }
 
 func (r *TXTResource) realType() Type {
 	return TypeTXT
 }
 
+// pack appends the wire format of the TXTResource to msg.
 func (r *TXTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
-	return packText(msg, r.Txt), nil
+	oldMsg := msg
+	for _, s := range r.TXT {
+		var err error
+		msg, err = packText(msg, s)
+		if err != nil {
+			return oldMsg, err
+		}
+	}
+	return msg, nil
 }
 
 func unpackTXTResource(msg []byte, off int, length uint16) (TXTResource, error) {
-	var txt string
+	txts := make([]string, 0, 1)
 	for n := uint16(0); n < length; {
 		var t string
 		var err error
@@ -1977,9 +2000,9 @@
 			return TXTResource{}, errCalcLen
 		}
 		n += uint16(len(t)) + 1
-		txt += t
+		txts = append(txts, t)
 	}
-	return TXTResource{txt}, nil
+	return TXTResource{txts}, nil
 }
 
 // An SRVResource is an SRV Resource record.
@@ -1994,6 +2017,7 @@
 	return TypeSRV
 }
 
+// pack appends the wire format of the SRVResource to msg.
 func (r *SRVResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	oldMsg := msg
 	msg = packUint16(msg, r.Priority)
@@ -2035,6 +2059,7 @@
 	return TypeA
 }
 
+// pack appends the wire format of the AResource to msg.
 func (r *AResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return packBytes(msg, r.A[:]), nil
 }
@@ -2056,6 +2081,7 @@
 	return TypeAAAA
 }
 
+// pack appends the wire format of the AAAAResource to msg.
 func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
 	return packBytes(msg, r.AAAA[:]), nil
 }
diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go
index d4eca26..c143d7e 100644
--- a/dns/dnsmessage/message_test.go
+++ b/dns/dnsmessage/message_test.go
@@ -8,6 +8,7 @@
 	"bytes"
 	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -444,7 +445,15 @@
 			Type:  TypeTXT,
 			Class: ClassINET,
 		},
-		&TXTResource{loremIpsum},
+		&TXTResource{[]string{
+			"",
+			"",
+			"foo bar",
+			"",
+			"www.example.com",
+			"www.example.com.",
+			strings.Repeat(".", 255),
+		}},
 	}
 	buf, err := want.pack(make([]byte, 0, 8000), map[string]int{}, 0)
 	if err != nil {
@@ -468,6 +477,13 @@
 	}
 }
 
+func TestTooLongTxt(t *testing.T) {
+	rb := TXTResource{[]string{strings.Repeat(".", 256)}}
+	if _, err := rb.pack(make([]byte, 0, 8000), map[string]int{}, 0); err != errStringTooLong {
+		t.Errorf("Packing TXTRecord with 256 character string: got err = %v, want = %v", err, errStringTooLong)
+	}
+}
+
 func TestStartAppends(t *testing.T) {
 	buf := make([]byte, 2, 514)
 	wantBuf := []byte{4, 44}
@@ -1084,7 +1100,7 @@
 					Type:  TypeTXT,
 					Class: ClassINET,
 				},
-				&TXTResource{"So Long, and Thanks for All the Fish"},
+				&TXTResource{[]string{"So Long, and Thanks for All the Fish"}},
 			},
 			{
 				ResourceHeader{
@@ -1092,139 +1108,8 @@
 					Type:  TypeTXT,
 					Class: ClassINET,
 				},
-				&TXTResource{"Hamster Huey and the Gooey Kablooie"},
+				&TXTResource{[]string{"Hamster Huey and the Gooey Kablooie"}},
 			},
 		},
 	}
 }
-
-const loremIpsum = `
-Lorem ipsum dolor sit amet, nec enim antiopam id, an ullum choro
-nonumes qui, pro eu debet honestatis mediocritatem. No alia enim eos,
-magna signiferumque ex vis. Mei no aperiri dissentias, cu vel quas
-regione. Malorum quaeque vim ut, eum cu semper aliquid invidunt, ei
-nam ipsum assentior.
-
-Nostrum appellantur usu no, vis ex probatus adipiscing. Cu usu illum
-facilis eleifend. Iusto conceptam complectitur vim id. Tale omnesque
-no usu, ei oblique sadipscing vim. At nullam voluptua usu, mei laudem
-reformidans et. Qui ei eros porro reformidans, ius suas veritus
-torquatos ex. Mea te facer alterum consequat.
-
-Soleat torquatos democritum sed et, no mea congue appareat, facer
-aliquam nec in. Has te ipsum tritani. At justo dicta option nec, movet
-phaedrum ad nam. Ea detracto verterem liberavisse has, delectus
-suscipiantur in mei. Ex nam meliore complectitur. Ut nam omnis
-honestatis quaerendum, ea mea nihil affert detracto, ad vix rebum
-mollis.
-
-Ut epicurei praesent neglegentur pri, prima fuisset intellegebat ad
-vim. An habemus comprehensam usu, at enim dignissim pro. Eam reque
-vivendum adipisci ea. Vel ne odio choro minimum. Sea admodum
-dissentiet ex. Mundi tamquam evertitur ius cu. Homero postea iisque ut
-pro, vel ne saepe senserit consetetur.
-
-Nulla utamur facilisis ius ea, in viderer diceret pertinax eum. Mei no
-enim quodsi facilisi, ex sed aeterno appareat mediocritatem, eum
-sententiae deterruisset ut. At suas timeam euismod cum, offendit
-appareat interpretaris ne vix. Vel ea civibus albucius, ex vim quidam
-accusata intellegebat, noluisse instructior sea id. Nec te nonumes
-habemus appellantur, quis dignissim vituperata eu nam.
-
-At vix apeirian patrioque vituperatoribus, an usu agam assum. Debet
-iisque an mea. Per eu dicant ponderum accommodare. Pri alienum
-placerat senserit an, ne eum ferri abhorreant vituperatoribus. Ut mea
-eligendi disputationi. Ius no tation everti impedit, ei magna quidam
-mediocritatem pri.
-
-Legendos perpetua iracundia ne usu, no ius ullum epicurei intellegam,
-ad modus epicuri lucilius eam. In unum quaerendum usu. Ne diam paulo
-has, ea veri virtute sed. Alia honestatis conclusionemque mea eu, ut
-iudico albucius his.
-
-Usu essent probatus eu, sed omnis dolor delicatissimi ex. No qui augue
-dissentias dissentiet. Laudem recteque no usu, vel an velit noluisse,
-an sed utinam eirmod appetere. Ne mea fuisset inimicus ocurreret. At
-vis dicant abhorreant, utinam forensibus nec ne, mei te docendi
-consequat. Brute inermis persecuti cum id. Ut ipsum munere propriae
-usu, dicit graeco disputando id has.
-
-Eros dolore quaerendum nam ei. Timeam ornatus inciderint pro id. Nec
-torquatos sadipscing ei, ancillae molestie per in. Malis principes duo
-ea, usu liber postulant ei.
-
-Graece timeam voluptatibus eu eam. Alia probatus quo no, ea scripta
-feugiat duo. Congue option meliore ex qui, noster invenire appellantur
-ea vel. Eu exerci legendos vel. Consetetur repudiandae vim ut. Vix an
-probo minimum, et nam illud falli tempor.
-
-Cum dico signiferumque eu. Sed ut regione maiorum, id veritus insolens
-tacimates vix. Eu mel sint tamquam lucilius, duo no oporteat
-tacimates. Atqui augue concludaturque vix ei, id mel utroque menandri.
-
-Ad oratio blandit aliquando pro. Vis et dolorum rationibus
-philosophia, ad cum nulla molestie. Hinc fuisset adversarium eum et,
-ne qui nisl verear saperet, vel te quaestio forensibus. Per odio
-option delenit an. Alii placerat has no, in pri nihil platonem
-cotidieque. Est ut elit copiosae scaevola, debet tollit maluisset sea
-an.
-
-Te sea hinc debet pericula, liber ridens fabulas cu sed, quem mutat
-accusam mea et. Elitr labitur albucius et pri, an labore feugait mel.
-Velit zril melius usu ea. Ad stet putent interpretaris qui. Mel no
-error volumus scripserit. In pro paulo iudico, quo ei dolorem
-verterem, affert fabellas dissentiet ea vix.
-
-Vis quot deserunt te. Error aliquid detraxit eu usu, vis alia eruditi
-salutatus cu. Est nostrud bonorum an, ei usu alii salutatus. Vel at
-nisl primis, eum ex aperiri noluisse reformidans. Ad veri velit
-utroque vis, ex equidem detraxit temporibus has.
-
-Inermis appareat usu ne. Eros placerat periculis mea ad, in dictas
-pericula pro. Errem postulant at usu, ea nec amet ornatus mentitum. Ad
-mazim graeco eum, vel ex percipit volutpat iudicabit, sit ne delicata
-interesset. Mel sapientem prodesset abhorreant et, oblique suscipit
-eam id.
-
-An maluisset disputando mea, vidit mnesarchum pri et. Malis insolens
-inciderint no sea. Ea persius maluisset vix, ne vim appellantur
-instructior, consul quidam definiebas pri id. Cum integre feugiat
-pericula in, ex sed persius similique, mel ne natum dicit percipitur.
-
-Primis discere ne pri, errem putent definitionem at vis. Ei mel dolore
-neglegentur, mei tincidunt percipitur ei. Pro ad simul integre
-rationibus. Eu vel alii honestatis definitiones, mea no nonumy
-reprehendunt.
-
-Dicta appareat legendos est cu. Eu vel congue dicunt omittam, no vix
-adhuc minimum constituam, quot noluisse id mel. Eu quot sale mutat
-duo, ex nisl munere invenire duo. Ne nec ullum utamur. Pro alterum
-debitis nostrum no, ut vel aliquid vivendo.
-
-Aliquip fierent praesent quo ne, id sit audiam recusabo delicatissimi.
-Usu postulant incorrupte cu. At pro dicit tibique intellegam, cibo
-dolore impedit id eam, et aeque feugait assentior has. Quando sensibus
-nec ex. Possit sensibus pri ad, unum mutat periculis cu vix.
-
-Mundi tibique vix te, duo simul partiendo qualisque id, est at vidit
-sonet tempor. No per solet aeterno deseruisse. Petentium salutandi
-definiebas pri cu. Munere vivendum est in. Ei justo congue eligendi
-vis, modus offendit omittantur te mel.
-
-Integre voluptaria in qui, sit habemus tractatos constituam no. Utinam
-melius conceptam est ne, quo in minimum apeirian delicata, ut ius
-porro recusabo. Dicant expetenda vix no, ludus scripserit sed ex, eu
-his modo nostro. Ut etiam sonet his, quodsi inciderint philosophia te
-per. Nullam lobortis eu cum, vix an sonet efficiendi repudiandae. Vis
-ad idque fabellas intellegebat.
-
-Eum commodo senserit conclusionemque ex. Sed forensibus sadipscing ut,
-mei in facer delicata periculis, sea ne hinc putent cetero. Nec ne
-alia corpora invenire, alia prima soleat te cum. Eleifend posidonium
-nam at.
-
-Dolorum indoctum cu quo, ex dolor legendos recteque eam, cu pri zril
-discere. Nec civibus officiis dissentiunt ex, est te liber ludus
-elaboraret. Cum ea fabellas invenire. Ex vim nostrud eripuit
-comprehensam, nam te inermis delectus, saepe inermis senserit.
-`