1package dns
2
3import (
4	"io"
5	"log"
6	"os"
7	"strconv"
8	"strings"
9)
10
11type debugging bool
12
13const debug debugging = false
14
15func (d debugging) Printf(format string, args ...interface{}) {
16	if d {
17		log.Printf(format, args...)
18	}
19}
20
21const maxTok = 2048 // Largest token we can return.
22const maxUint16 = 1<<16 - 1
23
24// Tokinize a RFC 1035 zone file. The tokenizer will normalize it:
25// * Add ownernames if they are left blank;
26// * Suppress sequences of spaces;
27// * Make each RR fit on one line (_NEWLINE is send as last)
28// * Handle comments: ;
29// * Handle braces - anywhere.
30const (
31	// Zonefile
32	zEOF = iota
33	zString
34	zBlank
35	zQuote
36	zNewline
37	zRrtpe
38	zOwner
39	zClass
40	zDirOrigin   // $ORIGIN
41	zDirTtl      // $TTL
42	zDirInclude  // $INCLUDE
43	zDirGenerate // $GENERATE
44
45	// Privatekey file
46	zValue
47	zKey
48
49	zExpectOwnerDir      // Ownername
50	zExpectOwnerBl       // Whitespace after the ownername
51	zExpectAny           // Expect rrtype, ttl or class
52	zExpectAnyNoClass    // Expect rrtype or ttl
53	zExpectAnyNoClassBl  // The whitespace after _EXPECT_ANY_NOCLASS
54	zExpectAnyNoTtl      // Expect rrtype or class
55	zExpectAnyNoTtlBl    // Whitespace after _EXPECT_ANY_NOTTL
56	zExpectRrtype        // Expect rrtype
57	zExpectRrtypeBl      // Whitespace BEFORE rrtype
58	zExpectRdata         // The first element of the rdata
59	zExpectDirTtlBl      // Space after directive $TTL
60	zExpectDirTtl        // Directive $TTL
61	zExpectDirOriginBl   // Space after directive $ORIGIN
62	zExpectDirOrigin     // Directive $ORIGIN
63	zExpectDirIncludeBl  // Space after directive $INCLUDE
64	zExpectDirInclude    // Directive $INCLUDE
65	zExpectDirGenerate   // Directive $GENERATE
66	zExpectDirGenerateBl // Space after directive $GENERATE
67)
68
69// ParseError is a parsing error. It contains the parse error and the location in the io.Reader
70// where the error occurred.
71type ParseError struct {
72	file string
73	err  string
74	lex  lex
75}
76
77func (e *ParseError) Error() (s string) {
78	if e.file != "" {
79		s = e.file + ": "
80	}
81	s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
82		strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
83	return
84}
85
86type lex struct {
87	token      string // text of the token
88	tokenUpper string // uppercase text of the token
89	length     int    // length of the token
90	err        bool   // when true, token text has lexer error
91	value      uint8  // value: zString, _BLANK, etc.
92	line       int    // line in the file
93	column     int    // column in the file
94	torc       uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar
95	comment    string // any comment text seen
96}
97
98// Token holds the token that are returned when a zone file is parsed.
99type Token struct {
100	// The scanned resource record when error is not nil.
101	RR
102	// When an error occurred, this has the error specifics.
103	Error *ParseError
104	// A potential comment positioned after the RR and on the same line.
105	Comment string
106}
107
108// NewRR reads the RR contained in the string s. Only the first RR is
109// returned. If s contains no RR, return nil with no error. The class
110// defaults to IN and TTL defaults to 3600. The full zone file syntax
111// like $TTL, $ORIGIN, etc. is supported. All fields of the returned
112// RR are set, except RR.Header().Rdlength which is set to 0.
113func NewRR(s string) (RR, error) {
114	if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline
115		return ReadRR(strings.NewReader(s+"\n"), "")
116	}
117	return ReadRR(strings.NewReader(s), "")
118}
119
120// ReadRR reads the RR contained in q.
121// See NewRR for more documentation.
122func ReadRR(q io.Reader, filename string) (RR, error) {
123	r := <-parseZoneHelper(q, ".", filename, 1)
124	if r == nil {
125		return nil, nil
126	}
127
128	if r.Error != nil {
129		return nil, r.Error
130	}
131	return r.RR, nil
132}
133
134// ParseZone reads a RFC 1035 style zonefile from r. It returns *Tokens on the
135// returned channel, which consist out the parsed RR, a potential comment or an error.
136// If there is an error the RR is nil. The string file is only used
137// in error reporting. The string origin is used as the initial origin, as
138// if the file would start with: $ORIGIN origin .
139// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported.
140// The channel t is closed by ParseZone when the end of r is reached.
141//
142// Basic usage pattern when reading from a string (z) containing the
143// zone data:
144//
145//	for x := range dns.ParseZone(strings.NewReader(z), "", "") {
146//		if x.Error != nil {
147//                  // log.Println(x.Error)
148//              } else {
149//                  // Do something with x.RR
150//              }
151//	}
152//
153// Comments specified after an RR (and on the same line!) are returned too:
154//
155//	foo. IN A 10.0.0.1 ; this is a comment
156//
157// The text "; this is comment" is returned in Token.Comment. Comments inside the
158// RR are discarded. Comments on a line by themselves are discarded too.
159func ParseZone(r io.Reader, origin, file string) chan *Token {
160	return parseZoneHelper(r, origin, file, 10000)
161}
162
163func parseZoneHelper(r io.Reader, origin, file string, chansize int) chan *Token {
164	t := make(chan *Token, chansize)
165	go parseZone(r, origin, file, t, 0)
166	return t
167}
168
169func parseZone(r io.Reader, origin, f string, t chan *Token, include int) {
170	defer func() {
171		if include == 0 {
172			close(t)
173		}
174	}()
175	s := scanInit(r)
176	c := make(chan lex)
177	// Start the lexer
178	go zlexer(s, c)
179	// 6 possible beginnings of a line, _ is a space
180	// 0. zRRTYPE                              -> all omitted until the rrtype
181	// 1. zOwner _ zRrtype                     -> class/ttl omitted
182	// 2. zOwner _ zString _ zRrtype           -> class omitted
183	// 3. zOwner _ zString _ zClass  _ zRrtype -> ttl/class
184	// 4. zOwner _ zClass  _ zRrtype           -> ttl omitted
185	// 5. zOwner _ zClass  _ zString _ zRrtype -> class/ttl (reversed)
186	// After detecting these, we know the zRrtype so we can jump to functions
187	// handling the rdata for each of these types.
188
189	if origin == "" {
190		origin = "."
191	}
192	origin = Fqdn(origin)
193	if _, ok := IsDomainName(origin); !ok {
194		t <- &Token{Error: &ParseError{f, "bad initial origin name", lex{}}}
195		return
196	}
197
198	st := zExpectOwnerDir // initial state
199	var h RR_Header
200	var defttl uint32 = defaultTtl
201	var prevName string
202	for l := range c {
203		// Lexer spotted an error already
204		if l.err == true {
205			t <- &Token{Error: &ParseError{f, l.token, l}}
206			return
207
208		}
209		switch st {
210		case zExpectOwnerDir:
211			// We can also expect a directive, like $TTL or $ORIGIN
212			h.Ttl = defttl
213			h.Class = ClassINET
214			switch l.value {
215			case zNewline:
216				st = zExpectOwnerDir
217			case zOwner:
218				h.Name = l.token
219				if l.token[0] == '@' {
220					h.Name = origin
221					prevName = h.Name
222					st = zExpectOwnerBl
223					break
224				}
225				if h.Name[l.length-1] != '.' {
226					h.Name = appendOrigin(h.Name, origin)
227				}
228				_, ok := IsDomainName(l.token)
229				if !ok {
230					t <- &Token{Error: &ParseError{f, "bad owner name", l}}
231					return
232				}
233				prevName = h.Name
234				st = zExpectOwnerBl
235			case zDirTtl:
236				st = zExpectDirTtlBl
237			case zDirOrigin:
238				st = zExpectDirOriginBl
239			case zDirInclude:
240				st = zExpectDirIncludeBl
241			case zDirGenerate:
242				st = zExpectDirGenerateBl
243			case zRrtpe:
244				h.Name = prevName
245				h.Rrtype = l.torc
246				st = zExpectRdata
247			case zClass:
248				h.Name = prevName
249				h.Class = l.torc
250				st = zExpectAnyNoClassBl
251			case zBlank:
252				// Discard, can happen when there is nothing on the
253				// line except the RR type
254			case zString:
255				ttl, ok := stringToTtl(l.token)
256				if !ok {
257					t <- &Token{Error: &ParseError{f, "not a TTL", l}}
258					return
259				}
260				h.Ttl = ttl
261				// Don't about the defttl, we should take the $TTL value
262				// defttl = ttl
263				st = zExpectAnyNoTtlBl
264
265			default:
266				t <- &Token{Error: &ParseError{f, "syntax error at beginning", l}}
267				return
268			}
269		case zExpectDirIncludeBl:
270			if l.value != zBlank {
271				t <- &Token{Error: &ParseError{f, "no blank after $INCLUDE-directive", l}}
272				return
273			}
274			st = zExpectDirInclude
275		case zExpectDirInclude:
276			if l.value != zString {
277				t <- &Token{Error: &ParseError{f, "expecting $INCLUDE value, not this...", l}}
278				return
279			}
280			neworigin := origin // There may be optionally a new origin set after the filename, if not use current one
281			l := <-c
282			switch l.value {
283			case zBlank:
284				l := <-c
285				if l.value == zString {
286					if _, ok := IsDomainName(l.token); !ok || l.length == 0 || l.err {
287						t <- &Token{Error: &ParseError{f, "bad origin name", l}}
288						return
289					}
290					// a new origin is specified.
291					if l.token[l.length-1] != '.' {
292						if origin != "." { // Prevent .. endings
293							neworigin = l.token + "." + origin
294						} else {
295							neworigin = l.token + origin
296						}
297					} else {
298						neworigin = l.token
299					}
300				}
301			case zNewline, zEOF:
302				// Ok
303			default:
304				t <- &Token{Error: &ParseError{f, "garbage after $INCLUDE", l}}
305				return
306			}
307			// Start with the new file
308			r1, e1 := os.Open(l.token)
309			if e1 != nil {
310				t <- &Token{Error: &ParseError{f, "failed to open `" + l.token + "'", l}}
311				return
312			}
313			if include+1 > 7 {
314				t <- &Token{Error: &ParseError{f, "too deeply nested $INCLUDE", l}}
315				return
316			}
317			parseZone(r1, l.token, neworigin, t, include+1)
318			st = zExpectOwnerDir
319		case zExpectDirTtlBl:
320			if l.value != zBlank {
321				t <- &Token{Error: &ParseError{f, "no blank after $TTL-directive", l}}
322				return
323			}
324			st = zExpectDirTtl
325		case zExpectDirTtl:
326			if l.value != zString {
327				t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}}
328				return
329			}
330			if e, _ := slurpRemainder(c, f); e != nil {
331				t <- &Token{Error: e}
332				return
333			}
334			ttl, ok := stringToTtl(l.token)
335			if !ok {
336				t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}}
337				return
338			}
339			defttl = ttl
340			st = zExpectOwnerDir
341		case zExpectDirOriginBl:
342			if l.value != zBlank {
343				t <- &Token{Error: &ParseError{f, "no blank after $ORIGIN-directive", l}}
344				return
345			}
346			st = zExpectDirOrigin
347		case zExpectDirOrigin:
348			if l.value != zString {
349				t <- &Token{Error: &ParseError{f, "expecting $ORIGIN value, not this...", l}}
350				return
351			}
352			if e, _ := slurpRemainder(c, f); e != nil {
353				t <- &Token{Error: e}
354			}
355			if _, ok := IsDomainName(l.token); !ok {
356				t <- &Token{Error: &ParseError{f, "bad origin name", l}}
357				return
358			}
359			if l.token[l.length-1] != '.' {
360				if origin != "." { // Prevent .. endings
361					origin = l.token + "." + origin
362				} else {
363					origin = l.token + origin
364				}
365			} else {
366				origin = l.token
367			}
368			st = zExpectOwnerDir
369		case zExpectDirGenerateBl:
370			if l.value != zBlank {
371				t <- &Token{Error: &ParseError{f, "no blank after $GENERATE-directive", l}}
372				return
373			}
374			st = zExpectDirGenerate
375		case zExpectDirGenerate:
376			if l.value != zString {
377				t <- &Token{Error: &ParseError{f, "expecting $GENERATE value, not this...", l}}
378				return
379			}
380			if errMsg := generate(l, c, t, origin); errMsg != "" {
381				t <- &Token{Error: &ParseError{f, errMsg, l}}
382				return
383			}
384			st = zExpectOwnerDir
385		case zExpectOwnerBl:
386			if l.value != zBlank {
387				t <- &Token{Error: &ParseError{f, "no blank after owner", l}}
388				return
389			}
390			st = zExpectAny
391		case zExpectAny:
392			switch l.value {
393			case zRrtpe:
394				h.Rrtype = l.torc
395				st = zExpectRdata
396			case zClass:
397				h.Class = l.torc
398				st = zExpectAnyNoClassBl
399			case zString:
400				ttl, ok := stringToTtl(l.token)
401				if !ok {
402					t <- &Token{Error: &ParseError{f, "not a TTL", l}}
403					return
404				}
405				h.Ttl = ttl
406				// defttl = ttl // don't set the defttl here
407				st = zExpectAnyNoTtlBl
408			default:
409				t <- &Token{Error: &ParseError{f, "expecting RR type, TTL or class, not this...", l}}
410				return
411			}
412		case zExpectAnyNoClassBl:
413			if l.value != zBlank {
414				t <- &Token{Error: &ParseError{f, "no blank before class", l}}
415				return
416			}
417			st = zExpectAnyNoClass
418		case zExpectAnyNoTtlBl:
419			if l.value != zBlank {
420				t <- &Token{Error: &ParseError{f, "no blank before TTL", l}}
421				return
422			}
423			st = zExpectAnyNoTtl
424		case zExpectAnyNoTtl:
425			switch l.value {
426			case zClass:
427				h.Class = l.torc
428				st = zExpectRrtypeBl
429			case zRrtpe:
430				h.Rrtype = l.torc
431				st = zExpectRdata
432			default:
433				t <- &Token{Error: &ParseError{f, "expecting RR type or class, not this...", l}}
434				return
435			}
436		case zExpectAnyNoClass:
437			switch l.value {
438			case zString:
439				ttl, ok := stringToTtl(l.token)
440				if !ok {
441					t <- &Token{Error: &ParseError{f, "not a TTL", l}}
442					return
443				}
444				h.Ttl = ttl
445				// defttl = ttl // don't set the def ttl anymore
446				st = zExpectRrtypeBl
447			case zRrtpe:
448				h.Rrtype = l.torc
449				st = zExpectRdata
450			default:
451				t <- &Token{Error: &ParseError{f, "expecting RR type or TTL, not this...", l}}
452				return
453			}
454		case zExpectRrtypeBl:
455			if l.value != zBlank {
456				t <- &Token{Error: &ParseError{f, "no blank before RR type", l}}
457				return
458			}
459			st = zExpectRrtype
460		case zExpectRrtype:
461			if l.value != zRrtpe {
462				t <- &Token{Error: &ParseError{f, "unknown RR type", l}}
463				return
464			}
465			h.Rrtype = l.torc
466			st = zExpectRdata
467		case zExpectRdata:
468			r, e, c1 := setRR(h, c, origin, f)
469			if e != nil {
470				// If e.lex is nil than we have encounter a unknown RR type
471				// in that case we substitute our current lex token
472				if e.lex.token == "" && e.lex.value == 0 {
473					e.lex = l // Uh, dirty
474				}
475				t <- &Token{Error: e}
476				return
477			}
478			t <- &Token{RR: r, Comment: c1}
479			st = zExpectOwnerDir
480		}
481	}
482	// If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this
483	// is not an error, because an empty zone file is still a zone file.
484}
485
486// zlexer scans the sourcefile and returns tokens on the channel c.
487func zlexer(s *scan, c chan lex) {
488	var l lex
489	str := make([]byte, maxTok) // Should be enough for any token
490	stri := 0                   // Offset in str (0 means empty)
491	com := make([]byte, maxTok) // Hold comment text
492	comi := 0
493	quote := false
494	escape := false
495	space := false
496	commt := false
497	rrtype := false
498	owner := true
499	brace := 0
500	x, err := s.tokenText()
501	defer close(c)
502	for err == nil {
503		l.column = s.position.Column
504		l.line = s.position.Line
505		if stri >= maxTok {
506			l.token = "token length insufficient for parsing"
507			l.err = true
508			debug.Printf("[%+v]", l.token)
509			c <- l
510			return
511		}
512		if comi >= maxTok {
513			l.token = "comment length insufficient for parsing"
514			l.err = true
515			debug.Printf("[%+v]", l.token)
516			c <- l
517			return
518		}
519
520		switch x {
521		case ' ', '\t':
522			if escape {
523				escape = false
524				str[stri] = x
525				stri++
526				break
527			}
528			if quote {
529				// Inside quotes this is legal
530				str[stri] = x
531				stri++
532				break
533			}
534			if commt {
535				com[comi] = x
536				comi++
537				break
538			}
539			if stri == 0 {
540				// Space directly in the beginning, handled in the grammar
541			} else if owner {
542				// If we have a string and its the first, make it an owner
543				l.value = zOwner
544				l.token = string(str[:stri])
545				l.tokenUpper = strings.ToUpper(l.token)
546				l.length = stri
547				// escape $... start with a \ not a $, so this will work
548				switch l.tokenUpper {
549				case "$TTL":
550					l.value = zDirTtl
551				case "$ORIGIN":
552					l.value = zDirOrigin
553				case "$INCLUDE":
554					l.value = zDirInclude
555				case "$GENERATE":
556					l.value = zDirGenerate
557				}
558				debug.Printf("[7 %+v]", l.token)
559				c <- l
560			} else {
561				l.value = zString
562				l.token = string(str[:stri])
563				l.tokenUpper = strings.ToUpper(l.token)
564				l.length = stri
565				if !rrtype {
566					if t, ok := StringToType[l.tokenUpper]; ok {
567						l.value = zRrtpe
568						l.torc = t
569						rrtype = true
570					} else {
571						if strings.HasPrefix(l.tokenUpper, "TYPE") {
572							t, ok := typeToInt(l.token)
573							if !ok {
574								l.token = "unknown RR type"
575								l.err = true
576								c <- l
577								return
578							}
579							l.value = zRrtpe
580							l.torc = t
581						}
582					}
583					if t, ok := StringToClass[l.tokenUpper]; ok {
584						l.value = zClass
585						l.torc = t
586					} else {
587						if strings.HasPrefix(l.tokenUpper, "CLASS") {
588							t, ok := classToInt(l.token)
589							if !ok {
590								l.token = "unknown class"
591								l.err = true
592								c <- l
593								return
594							}
595							l.value = zClass
596							l.torc = t
597						}
598					}
599				}
600				debug.Printf("[6 %+v]", l.token)
601				c <- l
602			}
603			stri = 0
604			// I reverse space stuff here
605			if !space && !commt {
606				l.value = zBlank
607				l.token = " "
608				l.length = 1
609				debug.Printf("[5 %+v]", l.token)
610				c <- l
611			}
612			owner = false
613			space = true
614		case ';':
615			if escape {
616				escape = false
617				str[stri] = x
618				stri++
619				break
620			}
621			if quote {
622				// Inside quotes this is legal
623				str[stri] = x
624				stri++
625				break
626			}
627			if stri > 0 {
628				l.value = zString
629				l.token = string(str[:stri])
630				l.tokenUpper = strings.ToUpper(l.token)
631				l.length = stri
632				debug.Printf("[4 %+v]", l.token)
633				c <- l
634				stri = 0
635			}
636			commt = true
637			com[comi] = ';'
638			comi++
639		case '\r':
640			escape = false
641			if quote {
642				str[stri] = x
643				stri++
644				break
645			}
646			// discard if outside of quotes
647		case '\n':
648			escape = false
649			// Escaped newline
650			if quote {
651				str[stri] = x
652				stri++
653				break
654			}
655			// inside quotes this is legal
656			if commt {
657				// Reset a comment
658				commt = false
659				rrtype = false
660				stri = 0
661				// If not in a brace this ends the comment AND the RR
662				if brace == 0 {
663					owner = true
664					owner = true
665					l.value = zNewline
666					l.token = "\n"
667					l.tokenUpper = l.token
668					l.length = 1
669					l.comment = string(com[:comi])
670					debug.Printf("[3 %+v %+v]", l.token, l.comment)
671					c <- l
672					l.comment = ""
673					comi = 0
674					break
675				}
676				com[comi] = ' ' // convert newline to space
677				comi++
678				break
679			}
680
681			if brace == 0 {
682				// If there is previous text, we should output it here
683				if stri != 0 {
684					l.value = zString
685					l.token = string(str[:stri])
686					l.tokenUpper = strings.ToUpper(l.token)
687
688					l.length = stri
689					if !rrtype {
690						if t, ok := StringToType[l.tokenUpper]; ok {
691							l.value = zRrtpe
692							l.torc = t
693							rrtype = true
694						}
695					}
696					debug.Printf("[2 %+v]", l.token)
697					c <- l
698				}
699				l.value = zNewline
700				l.token = "\n"
701				l.tokenUpper = l.token
702				l.length = 1
703				debug.Printf("[1 %+v]", l.token)
704				c <- l
705				stri = 0
706				commt = false
707				rrtype = false
708				owner = true
709				comi = 0
710			}
711		case '\\':
712			// comments do not get escaped chars, everything is copied
713			if commt {
714				com[comi] = x
715				comi++
716				break
717			}
718			// something already escaped must be in string
719			if escape {
720				str[stri] = x
721				stri++
722				escape = false
723				break
724			}
725			// something escaped outside of string gets added to string
726			str[stri] = x
727			stri++
728			escape = true
729		case '"':
730			if commt {
731				com[comi] = x
732				comi++
733				break
734			}
735			if escape {
736				str[stri] = x
737				stri++
738				escape = false
739				break
740			}
741			space = false
742			// send previous gathered text and the quote
743			if stri != 0 {
744				l.value = zString
745				l.token = string(str[:stri])
746				l.tokenUpper = strings.ToUpper(l.token)
747				l.length = stri
748
749				debug.Printf("[%+v]", l.token)
750				c <- l
751				stri = 0
752			}
753
754			// send quote itself as separate token
755			l.value = zQuote
756			l.token = "\""
757			l.tokenUpper = l.token
758			l.length = 1
759			c <- l
760			quote = !quote
761		case '(', ')':
762			if commt {
763				com[comi] = x
764				comi++
765				break
766			}
767			if escape {
768				str[stri] = x
769				stri++
770				escape = false
771				break
772			}
773			if quote {
774				str[stri] = x
775				stri++
776				break
777			}
778			switch x {
779			case ')':
780				brace--
781				if brace < 0 {
782					l.token = "extra closing brace"
783					l.tokenUpper = l.token
784					l.err = true
785					debug.Printf("[%+v]", l.token)
786					c <- l
787					return
788				}
789			case '(':
790				brace++
791			}
792		default:
793			escape = false
794			if commt {
795				com[comi] = x
796				comi++
797				break
798			}
799			str[stri] = x
800			stri++
801			space = false
802		}
803		x, err = s.tokenText()
804	}
805	if stri > 0 {
806		// Send remainder
807		l.token = string(str[:stri])
808		l.tokenUpper = strings.ToUpper(l.token)
809		l.length = stri
810		l.value = zString
811		debug.Printf("[%+v]", l.token)
812		c <- l
813	}
814	if brace != 0 {
815		l.token = "unbalanced brace"
816		l.tokenUpper = l.token
817		l.err = true
818		c <- l
819	}
820}
821
822// Extract the class number from CLASSxx
823func classToInt(token string) (uint16, bool) {
824	offset := 5
825	if len(token) < offset+1 {
826		return 0, false
827	}
828	class, err := strconv.ParseUint(token[offset:], 10, 16)
829	if err != nil {
830		return 0, false
831	}
832	return uint16(class), true
833}
834
835// Extract the rr number from TYPExxx
836func typeToInt(token string) (uint16, bool) {
837	offset := 4
838	if len(token) < offset+1 {
839		return 0, false
840	}
841	typ, err := strconv.ParseUint(token[offset:], 10, 16)
842	if err != nil {
843		return 0, false
844	}
845	return uint16(typ), true
846}
847
848// Parse things like 2w, 2m, etc, Return the time in seconds.
849func stringToTtl(token string) (uint32, bool) {
850	s := uint32(0)
851	i := uint32(0)
852	for _, c := range token {
853		switch c {
854		case 's', 'S':
855			s += i
856			i = 0
857		case 'm', 'M':
858			s += i * 60
859			i = 0
860		case 'h', 'H':
861			s += i * 60 * 60
862			i = 0
863		case 'd', 'D':
864			s += i * 60 * 60 * 24
865			i = 0
866		case 'w', 'W':
867			s += i * 60 * 60 * 24 * 7
868			i = 0
869		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
870			i *= 10
871			i += uint32(c) - '0'
872		default:
873			return 0, false
874		}
875	}
876	return s + i, true
877}
878
879// Parse LOC records' <digits>[.<digits>][mM] into a
880// mantissa exponent format. Token should contain the entire
881// string (i.e. no spaces allowed)
882func stringToCm(token string) (e, m uint8, ok bool) {
883	if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' {
884		token = token[0 : len(token)-1]
885	}
886	s := strings.SplitN(token, ".", 2)
887	var meters, cmeters, val int
888	var err error
889	switch len(s) {
890	case 2:
891		if cmeters, err = strconv.Atoi(s[1]); err != nil {
892			return
893		}
894		fallthrough
895	case 1:
896		if meters, err = strconv.Atoi(s[0]); err != nil {
897			return
898		}
899	case 0:
900		// huh?
901		return 0, 0, false
902	}
903	ok = true
904	if meters > 0 {
905		e = 2
906		val = meters
907	} else {
908		e = 0
909		val = cmeters
910	}
911	for val > 10 {
912		e++
913		val /= 10
914	}
915	if e > 9 {
916		ok = false
917	}
918	m = uint8(val)
919	return
920}
921
922func appendOrigin(name, origin string) string {
923	if origin == "." {
924		return name + origin
925	}
926	return name + "." + origin
927}
928
929// LOC record helper function
930func locCheckNorth(token string, latitude uint32) (uint32, bool) {
931	switch token {
932	case "n", "N":
933		return LOC_EQUATOR + latitude, true
934	case "s", "S":
935		return LOC_EQUATOR - latitude, true
936	}
937	return latitude, false
938}
939
940// LOC record helper function
941func locCheckEast(token string, longitude uint32) (uint32, bool) {
942	switch token {
943	case "e", "E":
944		return LOC_EQUATOR + longitude, true
945	case "w", "W":
946		return LOC_EQUATOR - longitude, true
947	}
948	return longitude, false
949}
950
951// "Eat" the rest of the "line". Return potential comments
952func slurpRemainder(c chan lex, f string) (*ParseError, string) {
953	l := <-c
954	com := ""
955	switch l.value {
956	case zBlank:
957		l = <-c
958		com = l.comment
959		if l.value != zNewline && l.value != zEOF {
960			return &ParseError{f, "garbage after rdata", l}, ""
961		}
962	case zNewline:
963		com = l.comment
964	case zEOF:
965	default:
966		return &ParseError{f, "garbage after rdata", l}, ""
967	}
968	return nil, com
969}
970
971// Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64"
972// Used for NID and L64 record.
973func stringToNodeID(l lex) (uint64, *ParseError) {
974	if len(l.token) < 19 {
975		return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
976	}
977	// There must be three colons at fixes postitions, if not its a parse error
978	if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
979		return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
980	}
981	s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19]
982	u, err := strconv.ParseUint(s, 16, 64)
983	if err != nil {
984		return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
985	}
986	return u, nil
987}
988