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