scan.go raw
1 package dns
2
3 import (
4 "bufio"
5 "fmt"
6 "io"
7 "io/fs"
8 "os"
9 "path"
10 "path/filepath"
11 "strconv"
12 "strings"
13 )
14
15 const maxTok = 512 // Token buffer start size, and growth size amount.
16
17 // The maximum depth of $INCLUDE directives supported by the
18 // ZoneParser API.
19 const maxIncludeDepth = 7
20
21 // Tokenize a RFC 1035 zone file. The tokenizer will normalize it:
22 // * Add ownernames if they are left blank;
23 // * Suppress sequences of spaces;
24 // * Make each RR fit on one line (_NEWLINE is send as last)
25 // * Handle comments: ;
26 // * Handle braces - anywhere.
27 const (
28 // Zonefile
29 zEOF = iota
30 zString
31 zBlank
32 zQuote
33 zNewline
34 zRrtpe
35 zOwner
36 zClass
37 zDirOrigin // $ORIGIN
38 zDirTTL // $TTL
39 zDirInclude // $INCLUDE
40 zDirGenerate // $GENERATE
41
42 // Privatekey file
43 zValue
44 zKey
45
46 zExpectOwnerDir // Ownername
47 zExpectOwnerBl // Whitespace after the ownername
48 zExpectAny // Expect rrtype, ttl or class
49 zExpectAnyNoClass // Expect rrtype or ttl
50 zExpectAnyNoClassBl // The whitespace after _EXPECT_ANY_NOCLASS
51 zExpectAnyNoTTL // Expect rrtype or class
52 zExpectAnyNoTTLBl // Whitespace after _EXPECT_ANY_NOTTL
53 zExpectRrtype // Expect rrtype
54 zExpectRrtypeBl // Whitespace BEFORE rrtype
55 zExpectRdata // The first element of the rdata
56 zExpectDirTTLBl // Space after directive $TTL
57 zExpectDirTTL // Directive $TTL
58 zExpectDirOriginBl // Space after directive $ORIGIN
59 zExpectDirOrigin // Directive $ORIGIN
60 zExpectDirIncludeBl // Space after directive $INCLUDE
61 zExpectDirInclude // Directive $INCLUDE
62 zExpectDirGenerate // Directive $GENERATE
63 zExpectDirGenerateBl // Space after directive $GENERATE
64 )
65
66 // ParseError is a parsing error. It contains the parse error and the location in the io.Reader
67 // where the error occurred.
68 type ParseError struct {
69 file string
70 err string
71 wrappedErr error
72 lex lex
73 }
74
75 func (e *ParseError) Error() (s string) {
76 if e.file != "" {
77 s = e.file + ": "
78 }
79 if e.err == "" && e.wrappedErr != nil {
80 e.err = e.wrappedErr.Error()
81 }
82 s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
83 strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
84 return
85 }
86
87 func (e *ParseError) Unwrap() error { return e.wrappedErr }
88
89 type lex struct {
90 token string // text of the token
91 err bool // when true, token text has lexer error
92 value uint8 // value: zString, _BLANK, etc.
93 torc uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar
94 line int // line in the file
95 column int // column in the file
96 }
97
98 // ttlState describes the state necessary to fill in an omitted RR TTL
99 type ttlState struct {
100 ttl uint32 // ttl is the current default TTL
101 isByDirective bool // isByDirective indicates whether ttl was set by a $TTL directive
102 }
103
104 // NewRR reads a string s and returns the first RR.
105 // If s contains no records, NewRR will return nil with no error.
106 //
107 // The class defaults to IN, TTL defaults to 3600, and
108 // origin for resolving relative domain names defaults to the DNS root (.).
109 // Full zone file syntax is supported, including directives like $TTL and $ORIGIN.
110 // All fields of the returned RR are set from the read data, except RR.Header().Rdlength which is set to 0.
111 // Is you need a partial resource record with no rdata - for instance - for dynamic updates, see the [ANY]
112 // documentation.
113 func 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 r.
121 //
122 // The string file is used in error reporting and to resolve relative
123 // $INCLUDE directives.
124 //
125 // See NewRR for more documentation.
126 func ReadRR(r io.Reader, file string) (RR, error) {
127 zp := NewZoneParser(r, ".", file)
128 zp.SetDefaultTTL(defaultTtl)
129 zp.SetIncludeAllowed(true)
130 rr, _ := zp.Next()
131 return rr, zp.Err()
132 }
133
134 // ZoneParser is a parser for an RFC 1035 style zonefile.
135 //
136 // Each parsed RR in the zone is returned sequentially from Next. An
137 // optional comment can be retrieved with Comment.
138 //
139 // The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
140 // supported. Although $INCLUDE is disabled by default.
141 // Note that $GENERATE's range support up to a maximum of 65535 steps.
142 //
143 // Basic usage pattern when reading from a string (z) containing the
144 // zone data:
145 //
146 // zp := NewZoneParser(strings.NewReader(z), "", "")
147 //
148 // for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
149 // // Do something with rr
150 // }
151 //
152 // if err := zp.Err(); err != nil {
153 // // log.Println(err)
154 // }
155 //
156 // Comments specified after an RR (and on the same line!) are
157 // returned too:
158 //
159 // foo. IN A 10.0.0.1 ; this is a comment
160 //
161 // The text "; this is comment" is returned from Comment. Comments inside
162 // the RR are returned concatenated along with the RR. Comments on a line
163 // by themselves are discarded.
164 //
165 // Callers should not assume all returned data in an Resource Record is
166 // syntactically correct, e.g. illegal base64 in RRSIGs will be returned as-is.
167 type ZoneParser struct {
168 c *zlexer
169
170 parseErr *ParseError
171
172 origin string
173 file string
174
175 defttl *ttlState
176
177 h RR_Header
178
179 // sub is used to parse $INCLUDE files and $GENERATE directives.
180 // Next, by calling subNext, forwards the resulting RRs from this
181 // sub parser to the calling code.
182 sub *ZoneParser
183 r io.Reader
184 fsys fs.FS
185
186 includeDepth uint8
187
188 includeAllowed bool
189 generateDisallowed bool
190 }
191
192 // NewZoneParser returns an RFC 1035 style zonefile parser that reads
193 // from r.
194 //
195 // The string file is used in error reporting and to resolve relative
196 // $INCLUDE directives. The string origin is used as the initial
197 // origin, as if the file would start with an $ORIGIN directive.
198 func NewZoneParser(r io.Reader, origin, file string) *ZoneParser {
199 var pe *ParseError
200 if origin != "" {
201 origin = Fqdn(origin)
202 if _, ok := IsDomainName(origin); !ok {
203 pe = &ParseError{file: file, err: "bad initial origin name"}
204 }
205 }
206
207 return &ZoneParser{
208 c: newZLexer(r),
209
210 parseErr: pe,
211
212 origin: origin,
213 file: file,
214 }
215 }
216
217 // SetDefaultTTL sets the parsers default TTL to ttl.
218 func (zp *ZoneParser) SetDefaultTTL(ttl uint32) {
219 zp.defttl = &ttlState{ttl, false}
220 }
221
222 // SetIncludeAllowed controls whether $INCLUDE directives are
223 // allowed. $INCLUDE directives are not supported by default.
224 //
225 // The $INCLUDE directive will open and read from a user controlled
226 // file on the system. Even if the file is not a valid zonefile, the
227 // contents of the file may be revealed in error messages, such as:
228 //
229 // /etc/passwd: dns: not a TTL: "root:x:0:0:root:/root:/bin/bash" at line: 1:31
230 // /etc/shadow: dns: not a TTL: "root:$6$<redacted>::0:99999:7:::" at line: 1:125
231 func (zp *ZoneParser) SetIncludeAllowed(v bool) {
232 zp.includeAllowed = v
233 }
234
235 // SetIncludeFS provides an [fs.FS] to use when looking for the target of
236 // $INCLUDE directives. ($INCLUDE must still be enabled separately by calling
237 // [ZoneParser.SetIncludeAllowed].) If fsys is nil, [os.Open] will be used.
238 //
239 // When fsys is an on-disk FS, the ability of $INCLUDE to reach files from
240 // outside its root directory depends upon the FS implementation. For
241 // instance, [os.DirFS] will refuse to open paths like "../../etc/passwd",
242 // however it will still follow links which may point anywhere on the system.
243 //
244 // FS paths are slash-separated on all systems, even Windows. $INCLUDE paths
245 // containing other characters such as backslash and colon may be accepted as
246 // valid, but those characters will never be interpreted by an FS
247 // implementation as path element separators. See [fs.ValidPath] for more
248 // details.
249 func (zp *ZoneParser) SetIncludeFS(fsys fs.FS) {
250 zp.fsys = fsys
251 }
252
253 // Err returns the first non-EOF error that was encountered by the
254 // ZoneParser.
255 func (zp *ZoneParser) Err() error {
256 if zp.parseErr != nil {
257 return zp.parseErr
258 }
259
260 if zp.sub != nil {
261 if err := zp.sub.Err(); err != nil {
262 return err
263 }
264 }
265
266 return zp.c.Err()
267 }
268
269 func (zp *ZoneParser) setParseError(err string, l lex) (RR, bool) {
270 zp.parseErr = &ParseError{file: zp.file, err: err, lex: l}
271 return nil, false
272 }
273
274 // Comment returns an optional text comment that occurred alongside
275 // the RR.
276 func (zp *ZoneParser) Comment() string {
277 if zp.parseErr != nil {
278 return ""
279 }
280
281 if zp.sub != nil {
282 return zp.sub.Comment()
283 }
284
285 return zp.c.Comment()
286 }
287
288 func (zp *ZoneParser) subNext() (RR, bool) {
289 if rr, ok := zp.sub.Next(); ok {
290 return rr, true
291 }
292
293 if zp.sub.r != nil {
294 if c, ok := zp.sub.r.(io.Closer); ok {
295 c.Close()
296 }
297 zp.sub.r = nil
298 }
299
300 if zp.sub.Err() != nil {
301 // We have errors to surface.
302 return nil, false
303 }
304
305 zp.sub = nil
306 return zp.Next()
307 }
308
309 // Next advances the parser to the next RR in the zonefile and
310 // returns the (RR, true). It will return (nil, false) when the
311 // parsing stops, either by reaching the end of the input or an
312 // error. After Next returns (nil, false), the Err method will return
313 // any error that occurred during parsing.
314 func (zp *ZoneParser) Next() (RR, bool) {
315 if zp.parseErr != nil {
316 return nil, false
317 }
318 if zp.sub != nil {
319 return zp.subNext()
320 }
321
322 // 6 possible beginnings of a line (_ is a space):
323 //
324 // 0. zRRTYPE -> all omitted until the rrtype
325 // 1. zOwner _ zRrtype -> class/ttl omitted
326 // 2. zOwner _ zString _ zRrtype -> class omitted
327 // 3. zOwner _ zString _ zClass _ zRrtype -> ttl/class
328 // 4. zOwner _ zClass _ zRrtype -> ttl omitted
329 // 5. zOwner _ zClass _ zString _ zRrtype -> class/ttl (reversed)
330 //
331 // After detecting these, we know the zRrtype so we can jump to functions
332 // handling the rdata for each of these types.
333
334 st := zExpectOwnerDir // initial state
335 h := &zp.h
336
337 for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
338 // zlexer spotted an error already
339 if l.err {
340 return zp.setParseError(l.token, l)
341 }
342
343 switch st {
344 case zExpectOwnerDir:
345 // We can also expect a directive, like $TTL or $ORIGIN
346 if zp.defttl != nil {
347 h.Ttl = zp.defttl.ttl
348 }
349
350 h.Class = ClassINET
351
352 switch l.value {
353 case zNewline:
354 st = zExpectOwnerDir
355 case zOwner:
356 name, ok := toAbsoluteName(l.token, zp.origin)
357 if !ok {
358 return zp.setParseError("bad owner name", l)
359 }
360
361 h.Name = name
362
363 st = zExpectOwnerBl
364 case zDirTTL:
365 st = zExpectDirTTLBl
366 case zDirOrigin:
367 st = zExpectDirOriginBl
368 case zDirInclude:
369 st = zExpectDirIncludeBl
370 case zDirGenerate:
371 st = zExpectDirGenerateBl
372 case zRrtpe:
373 h.Rrtype = l.torc
374
375 st = zExpectRdata
376 case zClass:
377 h.Class = l.torc
378
379 st = zExpectAnyNoClassBl
380 case zBlank:
381 // Discard, can happen when there is nothing on the
382 // line except the RR type
383 case zString:
384 ttl, ok := stringToTTL(l.token)
385 if !ok {
386 return zp.setParseError("not a TTL", l)
387 }
388
389 h.Ttl = ttl
390
391 if zp.defttl == nil || !zp.defttl.isByDirective {
392 zp.defttl = &ttlState{ttl, false}
393 }
394
395 st = zExpectAnyNoTTLBl
396 default:
397 return zp.setParseError("syntax error at beginning", l)
398 }
399 case zExpectDirIncludeBl:
400 if l.value != zBlank {
401 return zp.setParseError("no blank after $INCLUDE-directive", l)
402 }
403
404 st = zExpectDirInclude
405 case zExpectDirInclude:
406 if l.value != zString {
407 return zp.setParseError("expecting $INCLUDE value, not this...", l)
408 }
409
410 neworigin := zp.origin // There may be optionally a new origin set after the filename, if not use current one
411 switch l, _ := zp.c.Next(); l.value {
412 case zBlank:
413 l, _ := zp.c.Next()
414 if l.value == zString {
415 name, ok := toAbsoluteName(l.token, zp.origin)
416 if !ok {
417 return zp.setParseError("bad origin name", l)
418 }
419
420 neworigin = name
421 }
422 case zNewline, zEOF:
423 // Ok
424 default:
425 return zp.setParseError("garbage after $INCLUDE", l)
426 }
427
428 if !zp.includeAllowed {
429 return zp.setParseError("$INCLUDE directive not allowed", l)
430 }
431 if zp.includeDepth >= maxIncludeDepth {
432 return zp.setParseError("too deeply nested $INCLUDE", l)
433 }
434
435 // Start with the new file
436 includePath := l.token
437 var r1 io.Reader
438 var e1 error
439 if zp.fsys != nil {
440 // fs.FS always uses / as separator, even on Windows, so use
441 // path instead of filepath here:
442 if !path.IsAbs(includePath) {
443 includePath = path.Join(path.Dir(zp.file), includePath)
444 }
445
446 // os.DirFS, and probably others, expect all paths to be
447 // relative, so clean the path and remove leading / if
448 // present:
449 includePath = strings.TrimLeft(path.Clean(includePath), "/")
450
451 r1, e1 = zp.fsys.Open(includePath)
452 } else {
453 if !filepath.IsAbs(includePath) {
454 includePath = filepath.Join(filepath.Dir(zp.file), includePath)
455 }
456 r1, e1 = os.Open(includePath)
457 }
458 if e1 != nil {
459 var as string
460 if includePath != l.token {
461 as = fmt.Sprintf(" as `%s'", includePath)
462 }
463 zp.parseErr = &ParseError{
464 file: zp.file,
465 wrappedErr: fmt.Errorf("failed to open `%s'%s: %w", l.token, as, e1),
466 lex: l,
467 }
468 return nil, false
469 }
470
471 zp.sub = NewZoneParser(r1, neworigin, includePath)
472 zp.sub.defttl, zp.sub.includeDepth, zp.sub.r = zp.defttl, zp.includeDepth+1, r1
473 zp.sub.SetIncludeAllowed(true)
474 zp.sub.SetIncludeFS(zp.fsys)
475 return zp.subNext()
476 case zExpectDirTTLBl:
477 if l.value != zBlank {
478 return zp.setParseError("no blank after $TTL-directive", l)
479 }
480
481 st = zExpectDirTTL
482 case zExpectDirTTL:
483 if l.value != zString {
484 return zp.setParseError("expecting $TTL value, not this...", l)
485 }
486
487 if err := slurpRemainder(zp.c); err != nil {
488 return zp.setParseError(err.err, err.lex)
489 }
490
491 ttl, ok := stringToTTL(l.token)
492 if !ok {
493 return zp.setParseError("expecting $TTL value, not this...", l)
494 }
495
496 zp.defttl = &ttlState{ttl, true}
497
498 st = zExpectOwnerDir
499 case zExpectDirOriginBl:
500 if l.value != zBlank {
501 return zp.setParseError("no blank after $ORIGIN-directive", l)
502 }
503
504 st = zExpectDirOrigin
505 case zExpectDirOrigin:
506 if l.value != zString {
507 return zp.setParseError("expecting $ORIGIN value, not this...", l)
508 }
509
510 if err := slurpRemainder(zp.c); err != nil {
511 return zp.setParseError(err.err, err.lex)
512 }
513
514 name, ok := toAbsoluteName(l.token, zp.origin)
515 if !ok {
516 return zp.setParseError("bad origin name", l)
517 }
518
519 zp.origin = name
520
521 st = zExpectOwnerDir
522 case zExpectDirGenerateBl:
523 if l.value != zBlank {
524 return zp.setParseError("no blank after $GENERATE-directive", l)
525 }
526
527 st = zExpectDirGenerate
528 case zExpectDirGenerate:
529 if zp.generateDisallowed {
530 return zp.setParseError("nested $GENERATE directive not allowed", l)
531 }
532 if l.value != zString {
533 return zp.setParseError("expecting $GENERATE value, not this...", l)
534 }
535
536 return zp.generate(l)
537 case zExpectOwnerBl:
538 if l.value != zBlank {
539 return zp.setParseError("no blank after owner", l)
540 }
541
542 st = zExpectAny
543 case zExpectAny:
544 switch l.value {
545 case zRrtpe:
546 if zp.defttl == nil {
547 return zp.setParseError("missing TTL with no previous value", l)
548 }
549
550 h.Rrtype = l.torc
551
552 st = zExpectRdata
553 case zClass:
554 h.Class = l.torc
555
556 st = zExpectAnyNoClassBl
557 case zString:
558 ttl, ok := stringToTTL(l.token)
559 if !ok {
560 return zp.setParseError("not a TTL", l)
561 }
562
563 h.Ttl = ttl
564
565 if zp.defttl == nil || !zp.defttl.isByDirective {
566 zp.defttl = &ttlState{ttl, false}
567 }
568
569 st = zExpectAnyNoTTLBl
570 default:
571 return zp.setParseError("expecting RR type, TTL or class, not this...", l)
572 }
573 case zExpectAnyNoClassBl:
574 if l.value != zBlank {
575 return zp.setParseError("no blank before class", l)
576 }
577
578 st = zExpectAnyNoClass
579 case zExpectAnyNoTTLBl:
580 if l.value != zBlank {
581 return zp.setParseError("no blank before TTL", l)
582 }
583
584 st = zExpectAnyNoTTL
585 case zExpectAnyNoTTL:
586 switch l.value {
587 case zClass:
588 h.Class = l.torc
589
590 st = zExpectRrtypeBl
591 case zRrtpe:
592 h.Rrtype = l.torc
593
594 st = zExpectRdata
595 default:
596 return zp.setParseError("expecting RR type or class, not this...", l)
597 }
598 case zExpectAnyNoClass:
599 switch l.value {
600 case zString:
601 ttl, ok := stringToTTL(l.token)
602 if !ok {
603 return zp.setParseError("not a TTL", l)
604 }
605
606 h.Ttl = ttl
607
608 if zp.defttl == nil || !zp.defttl.isByDirective {
609 zp.defttl = &ttlState{ttl, false}
610 }
611
612 st = zExpectRrtypeBl
613 case zRrtpe:
614 h.Rrtype = l.torc
615
616 st = zExpectRdata
617 default:
618 return zp.setParseError("expecting RR type or TTL, not this...", l)
619 }
620 case zExpectRrtypeBl:
621 if l.value != zBlank {
622 return zp.setParseError("no blank before RR type", l)
623 }
624
625 st = zExpectRrtype
626 case zExpectRrtype:
627 if l.value != zRrtpe {
628 return zp.setParseError("unknown RR type", l)
629 }
630
631 h.Rrtype = l.torc
632
633 st = zExpectRdata
634 case zExpectRdata:
635 var (
636 rr RR
637 parseAsRFC3597 bool
638 )
639 if newFn, ok := TypeToRR[h.Rrtype]; ok {
640 rr = newFn()
641 *rr.Header() = *h
642
643 // We may be parsing a known RR type using the RFC3597 format.
644 // If so, we handle that here in a generic way.
645 //
646 // This is also true for PrivateRR types which will have the
647 // RFC3597 parsing done for them and the Unpack method called
648 // to populate the RR instead of simply deferring to Parse.
649 if zp.c.Peek().token == "\\#" {
650 parseAsRFC3597 = true
651 }
652 } else {
653 rr = &RFC3597{Hdr: *h}
654 }
655
656 _, isPrivate := rr.(*PrivateRR)
657 if !isPrivate && zp.c.Peek().token == "" {
658 // This is a dynamic update rr.
659
660 if err := slurpRemainder(zp.c); err != nil {
661 return zp.setParseError(err.err, err.lex)
662 }
663
664 return rr, true
665 } else if l.value == zNewline {
666 return zp.setParseError("unexpected newline", l)
667 }
668
669 parseAsRR := rr
670 if parseAsRFC3597 {
671 parseAsRR = &RFC3597{Hdr: *h}
672 }
673
674 if err := parseAsRR.parse(zp.c, zp.origin); err != nil {
675 // err is a concrete *ParseError without the file field set.
676 // The setParseError call below will construct a new
677 // *ParseError with file set to zp.file.
678
679 // err.lex may be nil in which case we substitute our current
680 // lex token.
681 if err.lex == (lex{}) {
682 return zp.setParseError(err.err, l)
683 }
684
685 return zp.setParseError(err.err, err.lex)
686 }
687
688 if parseAsRFC3597 {
689 err := parseAsRR.(*RFC3597).fromRFC3597(rr)
690 if err != nil {
691 return zp.setParseError(err.Error(), l)
692 }
693 }
694
695 return rr, true
696 }
697 }
698
699 // If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this
700 // is not an error, because an empty zone file is still a zone file.
701 return nil, false
702 }
703
704 type zlexer struct {
705 br io.ByteReader
706
707 readErr error
708
709 line int
710 column int
711
712 comBuf string
713 comment string
714
715 l lex
716 cachedL *lex
717
718 brace int
719 quote bool
720 space bool
721 commt bool
722 rrtype bool
723 owner bool
724
725 nextL bool
726
727 eol bool // end-of-line
728 }
729
730 func newZLexer(r io.Reader) *zlexer {
731 br, ok := r.(io.ByteReader)
732 if !ok {
733 br = bufio.NewReaderSize(r, 1024)
734 }
735
736 return &zlexer{
737 br: br,
738
739 line: 1,
740
741 owner: true,
742 }
743 }
744
745 func (zl *zlexer) Err() error {
746 if zl.readErr == io.EOF {
747 return nil
748 }
749
750 return zl.readErr
751 }
752
753 // readByte returns the next byte from the input
754 func (zl *zlexer) readByte() (byte, bool) {
755 if zl.readErr != nil {
756 return 0, false
757 }
758
759 c, err := zl.br.ReadByte()
760 if err != nil {
761 zl.readErr = err
762 return 0, false
763 }
764
765 // delay the newline handling until the next token is delivered,
766 // fixes off-by-one errors when reporting a parse error.
767 if zl.eol {
768 zl.line++
769 zl.column = 0
770 zl.eol = false
771 }
772
773 if c == '\n' {
774 zl.eol = true
775 } else {
776 zl.column++
777 }
778
779 return c, true
780 }
781
782 func (zl *zlexer) Peek() lex {
783 if zl.nextL {
784 return zl.l
785 }
786
787 l, ok := zl.Next()
788 if !ok {
789 return l
790 }
791
792 if zl.nextL {
793 // Cache l. Next returns zl.cachedL then zl.l.
794 zl.cachedL = &l
795 } else {
796 // In this case l == zl.l, so we just tell Next to return zl.l.
797 zl.nextL = true
798 }
799
800 return l
801 }
802
803 func (zl *zlexer) Next() (lex, bool) {
804 l := &zl.l
805 switch {
806 case zl.cachedL != nil:
807 l, zl.cachedL = zl.cachedL, nil
808 return *l, true
809 case zl.nextL:
810 zl.nextL = false
811 return *l, true
812 case l.err:
813 // Parsing errors should be sticky.
814 return lex{value: zEOF}, false
815 }
816
817 var (
818 str = make([]byte, maxTok) // Hold string text
819 com = make([]byte, maxTok) // Hold comment text
820
821 stri int // Offset in str (0 means empty)
822 comi int // Offset in com (0 means empty)
823
824 escape bool
825 )
826
827 if zl.comBuf != "" {
828 comi = copy(com[:], zl.comBuf)
829 zl.comBuf = ""
830 }
831
832 zl.comment = ""
833
834 for x, ok := zl.readByte(); ok; x, ok = zl.readByte() {
835 l.line, l.column = zl.line, zl.column
836
837 if stri >= len(str) {
838 // if buffer length is insufficient, increase it.
839 str = append(str[:], make([]byte, maxTok)...)
840 }
841 if comi >= len(com) {
842 // if buffer length is insufficient, increase it.
843 com = append(com[:], make([]byte, maxTok)...)
844 }
845
846 switch x {
847 case ' ', '\t':
848 if escape || zl.quote {
849 // Inside quotes or escaped this is legal.
850 str[stri] = x
851 stri++
852
853 escape = false
854 break
855 }
856
857 if zl.commt {
858 com[comi] = x
859 comi++
860 break
861 }
862
863 var retL lex
864 if stri == 0 {
865 // Space directly in the beginning, handled in the grammar
866 } else if zl.owner {
867 // If we have a string and it's the first, make it an owner
868 l.value = zOwner
869 l.token = string(str[:stri])
870
871 // escape $... start with a \ not a $, so this will work
872 switch strings.ToUpper(l.token) {
873 case "$TTL":
874 l.value = zDirTTL
875 case "$ORIGIN":
876 l.value = zDirOrigin
877 case "$INCLUDE":
878 l.value = zDirInclude
879 case "$GENERATE":
880 l.value = zDirGenerate
881 }
882
883 retL = *l
884 } else {
885 l.value = zString
886 l.token = string(str[:stri])
887
888 if !zl.rrtype {
889 tokenUpper := strings.ToUpper(l.token)
890 if t, ok := StringToType[tokenUpper]; ok {
891 l.value = zRrtpe
892 l.torc = t
893
894 zl.rrtype = true
895 } else if strings.HasPrefix(tokenUpper, "TYPE") {
896 t, ok := typeToInt(l.token)
897 if !ok {
898 l.token = "unknown RR type"
899 l.err = true
900 return *l, true
901 }
902
903 l.value = zRrtpe
904 l.torc = t
905
906 zl.rrtype = true
907 }
908
909 if t, ok := StringToClass[tokenUpper]; ok {
910 l.value = zClass
911 l.torc = t
912 } else if strings.HasPrefix(tokenUpper, "CLASS") {
913 t, ok := classToInt(l.token)
914 if !ok {
915 l.token = "unknown class"
916 l.err = true
917 return *l, true
918 }
919
920 l.value = zClass
921 l.torc = t
922 }
923 }
924
925 retL = *l
926 }
927
928 zl.owner = false
929
930 if !zl.space {
931 zl.space = true
932
933 l.value = zBlank
934 l.token = " "
935
936 if retL == (lex{}) {
937 return *l, true
938 }
939
940 zl.nextL = true
941 }
942
943 if retL != (lex{}) {
944 return retL, true
945 }
946 case ';':
947 if escape || zl.quote {
948 // Inside quotes or escaped this is legal.
949 str[stri] = x
950 stri++
951
952 escape = false
953 break
954 }
955
956 zl.commt = true
957 zl.comBuf = ""
958
959 if comi > 1 {
960 // A newline was previously seen inside a comment that
961 // was inside braces and we delayed adding it until now.
962 com[comi] = ' ' // convert newline to space
963 comi++
964 if comi >= len(com) {
965 l.token = "comment length insufficient for parsing"
966 l.err = true
967 return *l, true
968 }
969 }
970
971 com[comi] = ';'
972 comi++
973
974 if stri > 0 {
975 zl.comBuf = string(com[:comi])
976
977 l.value = zString
978 l.token = string(str[:stri])
979 return *l, true
980 }
981 case '\r':
982 escape = false
983
984 if zl.quote {
985 str[stri] = x
986 stri++
987 }
988
989 // discard if outside of quotes
990 case '\n':
991 escape = false
992
993 // Escaped newline
994 if zl.quote {
995 str[stri] = x
996 stri++
997 break
998 }
999
1000 if zl.commt {
1001 // Reset a comment
1002 zl.commt = false
1003 zl.rrtype = false
1004
1005 // If not in a brace this ends the comment AND the RR
1006 if zl.brace == 0 {
1007 zl.owner = true
1008
1009 l.value = zNewline
1010 l.token = "\n"
1011 zl.comment = string(com[:comi])
1012 return *l, true
1013 }
1014
1015 zl.comBuf = string(com[:comi])
1016 break
1017 }
1018
1019 if zl.brace == 0 {
1020 // If there is previous text, we should output it here
1021 var retL lex
1022 if stri != 0 {
1023 l.value = zString
1024 l.token = string(str[:stri])
1025
1026 if !zl.rrtype {
1027 tokenUpper := strings.ToUpper(l.token)
1028 if t, ok := StringToType[tokenUpper]; ok {
1029 zl.rrtype = true
1030
1031 l.value = zRrtpe
1032 l.torc = t
1033 }
1034 }
1035
1036 retL = *l
1037 }
1038
1039 l.value = zNewline
1040 l.token = "\n"
1041
1042 zl.comment = zl.comBuf
1043 zl.comBuf = ""
1044 zl.rrtype = false
1045 zl.owner = true
1046
1047 if retL != (lex{}) {
1048 zl.nextL = true
1049 return retL, true
1050 }
1051
1052 return *l, true
1053 }
1054 case '\\':
1055 // comments do not get escaped chars, everything is copied
1056 if zl.commt {
1057 com[comi] = x
1058 comi++
1059 break
1060 }
1061
1062 // something already escaped must be in string
1063 if escape {
1064 str[stri] = x
1065 stri++
1066
1067 escape = false
1068 break
1069 }
1070
1071 // something escaped outside of string gets added to string
1072 str[stri] = x
1073 stri++
1074
1075 escape = true
1076 case '"':
1077 if zl.commt {
1078 com[comi] = x
1079 comi++
1080 break
1081 }
1082
1083 if escape {
1084 str[stri] = x
1085 stri++
1086
1087 escape = false
1088 break
1089 }
1090
1091 zl.space = false
1092
1093 // send previous gathered text and the quote
1094 var retL lex
1095 if stri != 0 {
1096 l.value = zString
1097 l.token = string(str[:stri])
1098
1099 retL = *l
1100 }
1101
1102 // send quote itself as separate token
1103 l.value = zQuote
1104 l.token = "\""
1105
1106 zl.quote = !zl.quote
1107
1108 if retL != (lex{}) {
1109 zl.nextL = true
1110 return retL, true
1111 }
1112
1113 return *l, true
1114 case '(', ')':
1115 if zl.commt {
1116 com[comi] = x
1117 comi++
1118 break
1119 }
1120
1121 if escape || zl.quote {
1122 // Inside quotes or escaped this is legal.
1123 str[stri] = x
1124 stri++
1125
1126 escape = false
1127 break
1128 }
1129
1130 switch x {
1131 case ')':
1132 zl.brace--
1133
1134 if zl.brace < 0 {
1135 l.token = "extra closing brace"
1136 l.err = true
1137 return *l, true
1138 }
1139 case '(':
1140 zl.brace++
1141 }
1142 default:
1143 escape = false
1144
1145 if zl.commt {
1146 com[comi] = x
1147 comi++
1148 break
1149 }
1150
1151 str[stri] = x
1152 stri++
1153
1154 zl.space = false
1155 }
1156 }
1157
1158 if zl.readErr != nil && zl.readErr != io.EOF {
1159 // Don't return any tokens after a read error occurs.
1160 return lex{value: zEOF}, false
1161 }
1162
1163 var retL lex
1164 if stri > 0 {
1165 // Send remainder of str
1166 l.value = zString
1167 l.token = string(str[:stri])
1168 retL = *l
1169
1170 if comi <= 0 {
1171 return retL, true
1172 }
1173 }
1174
1175 if comi > 0 {
1176 // Send remainder of com
1177 l.value = zNewline
1178 l.token = "\n"
1179 zl.comment = string(com[:comi])
1180
1181 if retL != (lex{}) {
1182 zl.nextL = true
1183 return retL, true
1184 }
1185
1186 return *l, true
1187 }
1188
1189 if zl.brace != 0 {
1190 l.token = "unbalanced brace"
1191 l.err = true
1192 return *l, true
1193 }
1194
1195 return lex{value: zEOF}, false
1196 }
1197
1198 func (zl *zlexer) Comment() string {
1199 if zl.l.err {
1200 return ""
1201 }
1202
1203 return zl.comment
1204 }
1205
1206 // Extract the class number from CLASSxx
1207 func classToInt(token string) (uint16, bool) {
1208 offset := 5
1209 if len(token) < offset+1 {
1210 return 0, false
1211 }
1212 class, err := strconv.ParseUint(token[offset:], 10, 16)
1213 if err != nil {
1214 return 0, false
1215 }
1216 return uint16(class), true
1217 }
1218
1219 // Extract the rr number from TYPExxx
1220 func typeToInt(token string) (uint16, bool) {
1221 offset := 4
1222 if len(token) < offset+1 {
1223 return 0, false
1224 }
1225 typ, err := strconv.ParseUint(token[offset:], 10, 16)
1226 if err != nil {
1227 return 0, false
1228 }
1229 return uint16(typ), true
1230 }
1231
1232 // stringToTTL parses things like 2w, 2m, etc, and returns the time in seconds.
1233 func stringToTTL(token string) (uint32, bool) {
1234 var s, i uint32
1235 for _, c := range token {
1236 switch c {
1237 case 's', 'S':
1238 s += i
1239 i = 0
1240 case 'm', 'M':
1241 s += i * 60
1242 i = 0
1243 case 'h', 'H':
1244 s += i * 60 * 60
1245 i = 0
1246 case 'd', 'D':
1247 s += i * 60 * 60 * 24
1248 i = 0
1249 case 'w', 'W':
1250 s += i * 60 * 60 * 24 * 7
1251 i = 0
1252 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
1253 i *= 10
1254 i += uint32(c) - '0'
1255 default:
1256 return 0, false
1257 }
1258 }
1259 return s + i, true
1260 }
1261
1262 // Parse LOC records' <digits>[.<digits>][mM] into a
1263 // mantissa exponent format. Token should contain the entire
1264 // string (i.e. no spaces allowed)
1265 func stringToCm(token string) (e, m uint8, ok bool) {
1266 if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' {
1267 token = token[0 : len(token)-1]
1268 }
1269
1270 var (
1271 meters, cmeters, val int
1272 err error
1273 )
1274 mStr, cmStr, hasCM := strings.Cut(token, ".")
1275 if hasCM {
1276 // There's no point in having more than 2 digits in this part, and would rather make the implementation complicated ('123' should be treated as '12').
1277 // So we simply reject it.
1278 // We also make sure the first character is a digit to reject '+-' signs.
1279 cmeters, err = strconv.Atoi(cmStr)
1280 if err != nil || len(cmStr) > 2 || cmStr[0] < '0' || cmStr[0] > '9' {
1281 return
1282 }
1283 if len(cmStr) == 1 {
1284 // 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
1285 cmeters *= 10
1286 }
1287 }
1288 // This slightly ugly condition will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
1289 if !hasCM || mStr != "" {
1290 meters, err = strconv.Atoi(mStr)
1291 // RFC1876 states the max value is 90000000.00. The latter two conditions enforce it.
1292 if err != nil || mStr[0] < '0' || mStr[0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
1293 return
1294 }
1295 }
1296
1297 if meters > 0 {
1298 e = 2
1299 val = meters
1300 } else {
1301 e = 0
1302 val = cmeters
1303 }
1304 for val >= 10 {
1305 e++
1306 val /= 10
1307 }
1308 return e, uint8(val), true
1309 }
1310
1311 func toAbsoluteName(name, origin string) (absolute string, ok bool) {
1312 // check for an explicit origin reference
1313 if name == "@" {
1314 // require a nonempty origin
1315 if origin == "" {
1316 return "", false
1317 }
1318 return origin, true
1319 }
1320
1321 // this can happen when we have a comment after a RR that has a domain, '... MX 20 ; this is wrong'.
1322 // technically a newline can be in a domain name, but this is clearly an error and the newline only shows
1323 // because of the scanning and the comment.
1324 if name == "\n" {
1325 return "", false
1326 }
1327
1328 // require a valid domain name
1329 _, ok = IsDomainName(name)
1330 if !ok || name == "" {
1331 return "", false
1332 }
1333
1334 // check if name is already absolute
1335 if IsFqdn(name) {
1336 return name, true
1337 }
1338
1339 // require a nonempty origin
1340 if origin == "" {
1341 return "", false
1342 }
1343 return appendOrigin(name, origin), true
1344 }
1345
1346 func appendOrigin(name, origin string) string {
1347 if origin == "." {
1348 return name + origin
1349 }
1350 return name + "." + origin
1351 }
1352
1353 // LOC record helper function
1354 func locCheckNorth(token string, latitude uint32) (uint32, bool) {
1355 if latitude > 90*1000*60*60 {
1356 return latitude, false
1357 }
1358 switch token {
1359 case "n", "N":
1360 return LOC_EQUATOR + latitude, true
1361 case "s", "S":
1362 return LOC_EQUATOR - latitude, true
1363 }
1364 return latitude, false
1365 }
1366
1367 // LOC record helper function
1368 func locCheckEast(token string, longitude uint32) (uint32, bool) {
1369 if longitude > 180*1000*60*60 {
1370 return longitude, false
1371 }
1372 switch token {
1373 case "e", "E":
1374 return LOC_EQUATOR + longitude, true
1375 case "w", "W":
1376 return LOC_EQUATOR - longitude, true
1377 }
1378 return longitude, false
1379 }
1380
1381 // "Eat" the rest of the "line"
1382 func slurpRemainder(c *zlexer) *ParseError {
1383 l, _ := c.Next()
1384 switch l.value {
1385 case zBlank:
1386 l, _ = c.Next()
1387 if l.value != zNewline && l.value != zEOF {
1388 return &ParseError{err: "garbage after rdata", lex: l}
1389 }
1390 case zNewline:
1391 case zEOF:
1392 default:
1393 return &ParseError{err: "garbage after rdata", lex: l}
1394 }
1395 return nil
1396 }
1397
1398 // Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64"
1399 // Used for NID and L64 record.
1400 func stringToNodeID(l lex) (uint64, *ParseError) {
1401 if len(l.token) < 19 {
1402 return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
1403 }
1404 // There must be three colons at fixes positions, if not its a parse error
1405 if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
1406 return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
1407 }
1408 s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19]
1409 u, err := strconv.ParseUint(s, 16, 64)
1410 if err != nil {
1411 return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
1412 }
1413 return u, nil
1414 }
1415