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