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