Skip to content
Snippets Groups Projects
scan.go 29.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • ale's avatar
    ale committed
    	"fmt"
    
    ale's avatar
    ale committed
    	"path/filepath"
    
    	"strconv"
    	"strings"
    )
    
    const maxTok = 2048 // Largest token we can return.
    
    
    // The maximum depth of $INCLUDE directives supported by the
    // ZoneParser API.
    const maxIncludeDepth = 7
    
    
    // Tokinize a RFC 1035 zone file. The tokenizer will normalize it:
    // * Add ownernames if they are left blank;
    // * Suppress sequences of spaces;
    // * Make each RR fit on one line (_NEWLINE is send as last)
    // * Handle comments: ;
    // * Handle braces - anywhere.
    const (
    	// Zonefile
    	zEOF = iota
    	zString
    	zBlank
    	zQuote
    	zNewline
    	zRrtpe
    	zOwner
    	zClass
    	zDirOrigin   // $ORIGIN
    
    ale's avatar
    ale committed
    	zDirTTL      // $TTL
    
    	zDirInclude  // $INCLUDE
    	zDirGenerate // $GENERATE
    
    	// Privatekey file
    	zValue
    	zKey
    
    	zExpectOwnerDir      // Ownername
    	zExpectOwnerBl       // Whitespace after the ownername
    	zExpectAny           // Expect rrtype, ttl or class
    	zExpectAnyNoClass    // Expect rrtype or ttl
    	zExpectAnyNoClassBl  // The whitespace after _EXPECT_ANY_NOCLASS
    
    ale's avatar
    ale committed
    	zExpectAnyNoTTL      // Expect rrtype or class
    	zExpectAnyNoTTLBl    // Whitespace after _EXPECT_ANY_NOTTL
    
    	zExpectRrtype        // Expect rrtype
    	zExpectRrtypeBl      // Whitespace BEFORE rrtype
    	zExpectRdata         // The first element of the rdata
    
    ale's avatar
    ale committed
    	zExpectDirTTLBl      // Space after directive $TTL
    	zExpectDirTTL        // Directive $TTL
    
    	zExpectDirOriginBl   // Space after directive $ORIGIN
    	zExpectDirOrigin     // Directive $ORIGIN
    	zExpectDirIncludeBl  // Space after directive $INCLUDE
    	zExpectDirInclude    // Directive $INCLUDE
    	zExpectDirGenerate   // Directive $GENERATE
    	zExpectDirGenerateBl // Space after directive $GENERATE
    )
    
    // ParseError is a parsing error. It contains the parse error and the location in the io.Reader
    
    ale's avatar
    ale committed
    // where the error occurred.
    
    type ParseError struct {
    	file string
    	err  string
    	lex  lex
    }
    
    func (e *ParseError) Error() (s string) {
    	if e.file != "" {
    		s = e.file + ": "
    	}
    	s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
    		strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
    	return
    }
    
    type lex struct {
    
    	token  string // text of the token
    	err    bool   // when true, token text has lexer error
    	value  uint8  // value: zString, _BLANK, etc.
    	torc   uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar
    	line   int    // line in the file
    	column int    // column in the file
    
    ale's avatar
    ale committed
    // ttlState describes the state necessary to fill in an omitted RR TTL
    type ttlState struct {
    	ttl           uint32 // ttl is the current default TTL
    	isByDirective bool   // isByDirective indicates whether ttl was set by a $TTL directive
    }
    
    
    // NewRR reads the RR contained in the string s. Only the first RR is returned.
    // If s contains no records, NewRR will return nil with no error.
    
    // The class defaults to IN and TTL defaults to 3600. The full zone file syntax
    // like $TTL, $ORIGIN, etc. is supported. All fields of the returned RR are
    // set, except RR.Header().Rdlength which is set to 0.
    
    func NewRR(s string) (RR, error) {
    	if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline
    		return ReadRR(strings.NewReader(s+"\n"), "")
    	}
    	return ReadRR(strings.NewReader(s), "")
    }
    
    
    // ReadRR reads the RR contained in r.
    //
    // The string file is used in error reporting and to resolve relative
    // $INCLUDE directives.
    //
    
    // See NewRR for more documentation.
    
    func ReadRR(r io.Reader, file string) (RR, error) {
    	zp := NewZoneParser(r, ".", file)
    	zp.SetDefaultTTL(defaultTtl)
    	zp.SetIncludeAllowed(true)
    	rr, _ := zp.Next()
    	return rr, zp.Err()
    
    // ZoneParser is a parser for an RFC 1035 style zonefile.
    //
    // Each parsed RR in the zone is returned sequentially from Next. An
    // optional comment can be retrieved with Comment.
    //
    // The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all
    // supported. Although $INCLUDE is disabled by default.
    
    // Note that $GENERATE's range support up to a maximum of 65535 steps.
    
    //
    // Basic usage pattern when reading from a string (z) containing the
    // zone data:
    //
    //	zp := NewZoneParser(strings.NewReader(z), "", "")
    //
    //	for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
    //		// Do something with rr
    //	}
    //
    //	if err := zp.Err(); err != nil {
    //		// log.Println(err)
    //	}
    //
    // Comments specified after an RR (and on the same line!) are
    // returned too:
    //
    //	foo. IN A 10.0.0.1 ; this is a comment
    //
    // The text "; this is comment" is returned from Comment. Comments inside
    // the RR are returned concatenated along with the RR. Comments on a line
    // by themselves are discarded.
    type ZoneParser struct {
    	c *zlexer
    
    	parseErr *ParseError
    
    	origin string
    	file   string
    
    	defttl *ttlState
    
    	h RR_Header
    
    	// sub is used to parse $INCLUDE files and $GENERATE directives.
    	// Next, by calling subNext, forwards the resulting RRs from this
    	// sub parser to the calling code.
    	sub    *ZoneParser
    	osFile *os.File
    
    	includeDepth uint8
    
    
    	includeAllowed     bool
    	generateDisallowed bool
    
    }
    
    // NewZoneParser returns an RFC 1035 style zonefile parser that reads
    // from r.
    //
    // The string file is used in error reporting and to resolve relative
    // $INCLUDE directives. The string origin is used as the initial
    // origin, as if the file would start with an $ORIGIN directive.
    func NewZoneParser(r io.Reader, origin, file string) *ZoneParser {
    	var pe *ParseError
    
    ale's avatar
    ale committed
    	if origin != "" {
    		origin = Fqdn(origin)
    		if _, ok := IsDomainName(origin); !ok {
    
    			pe = &ParseError{file, "bad initial origin name", lex{}}
    
    ale's avatar
    ale committed
    		}
    
    	return &ZoneParser{
    		c: newZLexer(r),
    
    		parseErr: pe,
    
    		origin: origin,
    		file:   file,
    	}
    }
    
    // SetDefaultTTL sets the parsers default TTL to ttl.
    func (zp *ZoneParser) SetDefaultTTL(ttl uint32) {
    	zp.defttl = &ttlState{ttl, false}
    }
    
    // SetIncludeAllowed controls whether $INCLUDE directives are
    // allowed. $INCLUDE directives are not supported by default.
    //
    // The $INCLUDE directive will open and read from a user controlled
    // file on the system. Even if the file is not a valid zonefile, the
    // contents of the file may be revealed in error messages, such as:
    //
    //	/etc/passwd: dns: not a TTL: "root:x:0:0:root:/root:/bin/bash" at line: 1:31
    //	/etc/shadow: dns: not a TTL: "root:$6$<redacted>::0:99999:7:::" at line: 1:125
    func (zp *ZoneParser) SetIncludeAllowed(v bool) {
    	zp.includeAllowed = v
    }
    
    // Err returns the first non-EOF error that was encountered by the
    // ZoneParser.
    func (zp *ZoneParser) Err() error {
    	if zp.parseErr != nil {
    		return zp.parseErr
    	}
    
    	if zp.sub != nil {
    		if err := zp.sub.Err(); err != nil {
    			return err
    		}
    	}
    
    	return zp.c.Err()
    }
    
    func (zp *ZoneParser) setParseError(err string, l lex) (RR, bool) {
    	zp.parseErr = &ParseError{zp.file, err, l}
    	return nil, false
    }
    
    // Comment returns an optional text comment that occurred alongside
    // the RR.
    func (zp *ZoneParser) Comment() string {
    
    	if zp.parseErr != nil {
    		return ""
    	}
    
    	if zp.sub != nil {
    		return zp.sub.Comment()
    	}
    
    	return zp.c.Comment()
    
    }
    
    func (zp *ZoneParser) subNext() (RR, bool) {
    	if rr, ok := zp.sub.Next(); ok {
    		return rr, true
    	}
    
    	if zp.sub.osFile != nil {
    		zp.sub.osFile.Close()
    		zp.sub.osFile = nil
    	}
    
    	if zp.sub.Err() != nil {
    		// We have errors to surface.
    		return nil, false
    	}
    
    	zp.sub = nil
    	return zp.Next()
    }
    
    // Next advances the parser to the next RR in the zonefile and
    // returns the (RR, true). It will return (nil, false) when the
    // parsing stops, either by reaching the end of the input or an
    // error. After Next returns (nil, false), the Err method will return
    // any error that occurred during parsing.
    func (zp *ZoneParser) Next() (RR, bool) {
    	if zp.parseErr != nil {
    		return nil, false
    	}
    	if zp.sub != nil {
    		return zp.subNext()
    	}
    
    	// 6 possible beginnings of a line (_ is a space):
    	//
    	//   0. zRRTYPE                              -> all omitted until the rrtype
    	//   1. zOwner _ zRrtype                     -> class/ttl omitted
    	//   2. zOwner _ zString _ zRrtype           -> class omitted
    	//   3. zOwner _ zString _ zClass  _ zRrtype -> ttl/class
    	//   4. zOwner _ zClass  _ zRrtype           -> ttl omitted
    	//   5. zOwner _ zClass  _ zString _ zRrtype -> class/ttl (reversed)
    	//
    	// After detecting these, we know the zRrtype so we can jump to functions
    	// handling the rdata for each of these types.
    
    
    	st := zExpectOwnerDir // initial state
    
    	h := &zp.h
    
    	for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
    		// zlexer spotted an error already
    		if l.err {
    			return zp.setParseError(l.token, l)
    
    		switch st {
    		case zExpectOwnerDir:
    			// We can also expect a directive, like $TTL or $ORIGIN
    
    			if zp.defttl != nil {
    				h.Ttl = zp.defttl.ttl
    
    ale's avatar
    ale committed
    			}
    
    			h.Class = ClassINET
    
    			switch l.value {
    			case zNewline:
    				st = zExpectOwnerDir
    			case zOwner:
    
    				name, ok := toAbsoluteName(l.token, zp.origin)
    
    					return zp.setParseError("bad owner name", l)
    
    ale's avatar
    ale committed
    				h.Name = name
    
    				st = zExpectOwnerBl
    
    ale's avatar
    ale committed
    			case zDirTTL:
    				st = zExpectDirTTLBl
    
    			case zDirOrigin:
    				st = zExpectDirOriginBl
    			case zDirInclude:
    				st = zExpectDirIncludeBl
    			case zDirGenerate:
    				st = zExpectDirGenerateBl
    			case zRrtpe:
    				h.Rrtype = l.torc
    
    				st = zExpectRdata
    			case zClass:
    				h.Class = l.torc
    
    				st = zExpectAnyNoClassBl
    			case zBlank:
    				// Discard, can happen when there is nothing on the
    				// line except the RR type
    			case zString:
    
    ale's avatar
    ale committed
    				ttl, ok := stringToTTL(l.token)
    
    					return zp.setParseError("not a TTL", l)
    
    
    				if zp.defttl == nil || !zp.defttl.isByDirective {
    					zp.defttl = &ttlState{ttl, false}
    
    ale's avatar
    ale committed
    				}
    
    				st = zExpectAnyNoTTLBl
    
    				return zp.setParseError("syntax error at beginning", l)
    
    			}
    		case zExpectDirIncludeBl:
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank after $INCLUDE-directive", l)
    
    			st = zExpectDirInclude
    		case zExpectDirInclude:
    			if l.value != zString {
    
    				return zp.setParseError("expecting $INCLUDE value, not this...", l)
    
    
    			neworigin := zp.origin // There may be optionally a new origin set after the filename, if not use current one
    			switch l, _ := zp.c.Next(); l.value {
    
    				l, _ := zp.c.Next()
    
    				if l.value == zString {
    
    					name, ok := toAbsoluteName(l.token, zp.origin)
    
    ale's avatar
    ale committed
    					if !ok {
    
    						return zp.setParseError("bad origin name", l)
    
    ale's avatar
    ale committed
    					neworigin = name
    
    				}
    			case zNewline, zEOF:
    				// Ok
    			default:
    
    				return zp.setParseError("garbage after $INCLUDE", l)
    			}
    
    			if !zp.includeAllowed {
    				return zp.setParseError("$INCLUDE directive not allowed", l)
    			}
    			if zp.includeDepth >= maxIncludeDepth {
    				return zp.setParseError("too deeply nested $INCLUDE", l)
    
    			// Start with the new file
    
    ale's avatar
    ale committed
    			includePath := l.token
    			if !filepath.IsAbs(includePath) {
    
    				includePath = filepath.Join(filepath.Dir(zp.file), includePath)
    
    ale's avatar
    ale committed
    			}
    
    ale's avatar
    ale committed
    			r1, e1 := os.Open(includePath)
    
    				var as string
    
    ale's avatar
    ale committed
    				if !filepath.IsAbs(l.token) {
    
    					as = fmt.Sprintf(" as `%s'", includePath)
    
    ale's avatar
    ale committed
    				}
    
    
    				msg := fmt.Sprintf("failed to open `%s'%s: %v", l.token, as, e1)
    				return zp.setParseError(msg, l)
    
    
    			zp.sub = NewZoneParser(r1, neworigin, includePath)
    			zp.sub.defttl, zp.sub.includeDepth, zp.sub.osFile = zp.defttl, zp.includeDepth+1, r1
    			zp.sub.SetIncludeAllowed(true)
    			return zp.subNext()
    
    ale's avatar
    ale committed
    		case zExpectDirTTLBl:
    
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank after $TTL-directive", l)
    
    ale's avatar
    ale committed
    			st = zExpectDirTTL
    		case zExpectDirTTL:
    
    			if l.value != zString {
    
    				return zp.setParseError("expecting $TTL value, not this...", l)
    
    			if err := slurpRemainder(zp.c); err != nil {
    				return zp.setParseError(err.err, err.lex)
    
    ale's avatar
    ale committed
    			ttl, ok := stringToTTL(l.token)
    
    				return zp.setParseError("expecting $TTL value, not this...", l)
    
    
    			zp.defttl = &ttlState{ttl, true}
    
    
    			st = zExpectOwnerDir
    		case zExpectDirOriginBl:
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank after $ORIGIN-directive", l)
    
    			st = zExpectDirOrigin
    		case zExpectDirOrigin:
    			if l.value != zString {
    
    				return zp.setParseError("expecting $ORIGIN value, not this...", l)
    
    			if err := slurpRemainder(zp.c); err != nil {
    				return zp.setParseError(err.err, err.lex)
    
    
    			name, ok := toAbsoluteName(l.token, zp.origin)
    
    ale's avatar
    ale committed
    			if !ok {
    
    				return zp.setParseError("bad origin name", l)
    
    			st = zExpectOwnerDir
    		case zExpectDirGenerateBl:
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank after $GENERATE-directive", l)
    
    			st = zExpectDirGenerate
    		case zExpectDirGenerate:
    
    			if zp.generateDisallowed {
    				return zp.setParseError("nested $GENERATE directive not allowed", l)
    			}
    
    			if l.value != zString {
    
    				return zp.setParseError("expecting $GENERATE value, not this...", l)
    
    
    			return zp.generate(l)
    
    		case zExpectOwnerBl:
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank after owner", l)
    
    			st = zExpectAny
    		case zExpectAny:
    			switch l.value {
    			case zRrtpe:
    
    				if zp.defttl == nil {
    					return zp.setParseError("missing TTL with no previous value", l)
    
    ale's avatar
    ale committed
    				}
    
    				st = zExpectRdata
    			case zClass:
    				h.Class = l.torc
    
    				st = zExpectAnyNoClassBl
    			case zString:
    
    ale's avatar
    ale committed
    				ttl, ok := stringToTTL(l.token)
    
    					return zp.setParseError("not a TTL", l)
    
    
    				if zp.defttl == nil || !zp.defttl.isByDirective {
    					zp.defttl = &ttlState{ttl, false}
    
    ale's avatar
    ale committed
    				}
    
    ale's avatar
    ale committed
    				st = zExpectAnyNoTTLBl
    
    				return zp.setParseError("expecting RR type, TTL or class, not this...", l)
    
    			}
    		case zExpectAnyNoClassBl:
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank before class", l)
    
    			st = zExpectAnyNoClass
    
    ale's avatar
    ale committed
    		case zExpectAnyNoTTLBl:
    
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank before TTL", l)
    
    ale's avatar
    ale committed
    			st = zExpectAnyNoTTL
    		case zExpectAnyNoTTL:
    
    			switch l.value {
    			case zClass:
    				h.Class = l.torc
    
    				st = zExpectRrtypeBl
    			case zRrtpe:
    				h.Rrtype = l.torc
    
    				st = zExpectRdata
    			default:
    
    				return zp.setParseError("expecting RR type or class, not this...", l)
    
    			}
    		case zExpectAnyNoClass:
    			switch l.value {
    			case zString:
    
    ale's avatar
    ale committed
    				ttl, ok := stringToTTL(l.token)
    
    					return zp.setParseError("not a TTL", l)
    
    
    				if zp.defttl == nil || !zp.defttl.isByDirective {
    					zp.defttl = &ttlState{ttl, false}
    
    ale's avatar
    ale committed
    				}
    
    				st = zExpectRrtypeBl
    			case zRrtpe:
    				h.Rrtype = l.torc
    
    				st = zExpectRdata
    			default:
    
    				return zp.setParseError("expecting RR type or TTL, not this...", l)
    
    			}
    		case zExpectRrtypeBl:
    			if l.value != zBlank {
    
    				return zp.setParseError("no blank before RR type", l)
    
    			st = zExpectRrtype
    		case zExpectRrtype:
    			if l.value != zRrtpe {
    
    				return zp.setParseError("unknown RR type", l)
    
    			st = zExpectRdata
    		case zExpectRdata:
    
    			var (
    				rr             RR
    				parseAsRFC3597 bool
    			)
    			if newFn, ok := TypeToRR[h.Rrtype]; ok {
    
    				rr = newFn()
    				*rr.Header() = *h
    
    
    				// We may be parsing a known RR type using the RFC3597 format.
    				// If so, we handle that here in a generic way.
    				//
    				// This is also true for PrivateRR types which will have the
    				// RFC3597 parsing done for them and the Unpack method called
    				// to populate the RR instead of simply deferring to Parse.
    				if zp.c.Peek().token == "\\#" {
    					parseAsRFC3597 = true
    				}
    
    			} else {
    				rr = &RFC3597{Hdr: *h}
    			}
    
    			_, isPrivate := rr.(*PrivateRR)
    			if !isPrivate && zp.c.Peek().token == "" {
    				// This is a dynamic update rr.
    
    				// TODO(tmthrgd): Previously slurpRemainder was only called
    				// for certain RR types, which may have been important.
    				if err := slurpRemainder(zp.c); err != nil {
    					return zp.setParseError(err.err, err.lex)
    
    				return rr, true
    			} else if l.value == zNewline {
    				return zp.setParseError("unexpected newline", l)
    
    			parseAsRR := rr
    			if parseAsRFC3597 {
    				parseAsRR = &RFC3597{Hdr: *h}
    			}
    
    			if err := parseAsRR.parse(zp.c, zp.origin); err != nil {
    
    				// err is a concrete *ParseError without the file field set.
    				// The setParseError call below will construct a new
    				// *ParseError with file set to zp.file.
    
    
    				// err.lex may be nil in which case we substitute our current
    				// lex token.
    
    				if err.lex == (lex{}) {
    					return zp.setParseError(err.err, l)
    				}
    
    				return zp.setParseError(err.err, err.lex)
    			}
    
    
    			if parseAsRFC3597 {
    				err := parseAsRR.(*RFC3597).fromRFC3597(rr)
    				if err != nil {
    					return zp.setParseError(err.Error(), l)
    				}
    			}
    
    
    			return rr, true
    
    	// If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this
    	// is not an error, because an empty zone file is still a zone file.
    
    	return nil, false
    }
    
    type zlexer struct {
    	br io.ByteReader
    
    	readErr error
    
    	line   int
    	column int
    
    
    	comBuf  string
    	comment string
    
    	l       lex
    	cachedL *lex
    
    
    	brace  int
    	quote  bool
    	space  bool
    	commt  bool
    	rrtype bool
    	owner  bool
    
    	nextL bool
    
    	eol bool // end-of-line
    }
    
    func newZLexer(r io.Reader) *zlexer {
    	br, ok := r.(io.ByteReader)
    	if !ok {
    		br = bufio.NewReaderSize(r, 1024)
    	}
    
    	return &zlexer{
    		br: br,
    
    		line: 1,
    
    		owner: true,
    	}
    }
    
    func (zl *zlexer) Err() error {
    	if zl.readErr == io.EOF {
    		return nil
    	}
    
    	return zl.readErr
    }
    
    // readByte returns the next byte from the input
    func (zl *zlexer) readByte() (byte, bool) {
    	if zl.readErr != nil {
    		return 0, false
    	}
    
    	c, err := zl.br.ReadByte()
    	if err != nil {
    		zl.readErr = err
    		return 0, false
    	}
    
    	// delay the newline handling until the next token is delivered,
    	// fixes off-by-one errors when reporting a parse error.
    	if zl.eol {
    		zl.line++
    		zl.column = 0
    		zl.eol = false
    	}
    
    	if c == '\n' {
    		zl.eol = true
    	} else {
    		zl.column++
    	}
    
    	return c, true
    
    func (zl *zlexer) Peek() lex {
    	if zl.nextL {
    		return zl.l
    	}
    
    	l, ok := zl.Next()
    	if !ok {
    		return l
    	}
    
    	if zl.nextL {
    		// Cache l. Next returns zl.cachedL then zl.l.
    		zl.cachedL = &l
    	} else {
    		// In this case l == zl.l, so we just tell Next to return zl.l.
    		zl.nextL = true
    	}
    
    	return l
    }
    
    
    func (zl *zlexer) Next() (lex, bool) {
    	l := &zl.l
    
    	switch {
    	case zl.cachedL != nil:
    		l, zl.cachedL = zl.cachedL, nil
    		return *l, true
    	case zl.nextL:
    
    		zl.nextL = false
    		return *l, true
    
    	case l.err:
    
    		// Parsing errors should be sticky.
    		return lex{value: zEOF}, false
    	}
    
    	var (
    		str [maxTok]byte // Hold string text
    		com [maxTok]byte // Hold comment text
    
    		stri int // Offset in str (0 means empty)
    		comi int // Offset in com (0 means empty)
    
    		escape bool
    	)
    
    
    	if zl.comBuf != "" {
    		comi = copy(com[:], zl.comBuf)
    		zl.comBuf = ""
    
    	zl.comment = ""
    
    
    	for x, ok := zl.readByte(); ok; x, ok = zl.readByte() {
    		l.line, l.column = zl.line, zl.column
    
    		if stri >= len(str) {
    
    			l.token = "token length insufficient for parsing"
    			l.err = true
    
    			return *l, true
    
    		if comi >= len(com) {
    
    			l.token = "comment length insufficient for parsing"
    			l.err = true
    
    			return *l, true
    
    			if escape || zl.quote {
    				// Inside quotes or escaped this is legal.
    
    
    				escape = false
    
    
    			if zl.commt {
    
    			if stri == 0 {
    				// Space directly in the beginning, handled in the grammar
    
    			} else if zl.owner {
    
    				// If we have a string and its the first, make it an owner
    				l.value = zOwner
    				l.token = string(str[:stri])
    
    				// escape $... start with a \ not a $, so this will work
    
    				switch strings.ToUpper(l.token) {
    
    ale's avatar
    ale committed
    					l.value = zDirTTL
    
    				case "$ORIGIN":
    					l.value = zDirOrigin
    				case "$INCLUDE":
    					l.value = zDirInclude
    				case "$GENERATE":
    					l.value = zDirGenerate
    				}
    
    			} else {
    				l.value = zString
    				l.token = string(str[:stri])
    
    
    				if !zl.rrtype {
    					tokenUpper := strings.ToUpper(l.token)
    					if t, ok := StringToType[tokenUpper]; ok {
    
    						l.value = zRrtpe
    						l.torc = t
    
    
    						zl.rrtype = true
    					} else if strings.HasPrefix(tokenUpper, "TYPE") {
    						t, ok := typeToInt(l.token)
    						if !ok {
    							l.token = "unknown RR type"
    							l.err = true
    							return *l, true
    
    
    						l.value = zRrtpe
    						l.torc = t
    
    						zl.rrtype = true
    
    
    					if t, ok := StringToClass[tokenUpper]; ok {
    
    						l.value = zClass
    						l.torc = t
    
    					} else if strings.HasPrefix(tokenUpper, "CLASS") {
    						t, ok := classToInt(l.token)
    						if !ok {
    							l.token = "unknown class"
    							l.err = true
    							return *l, true
    
    
    						l.value = zClass
    						l.torc = t
    
    ale's avatar
    ale committed
    
    
    			zl.owner = false
    
    			if !zl.space {
    				zl.space = true
    
    
    				l.value = zBlank
    				l.token = " "
    
    
    				if retL == (lex{}) {
    					return *l, true
    				}
    
    				zl.nextL = true
    			}
    
    			if retL != (lex{}) {
    				return retL, true
    
    			if escape || zl.quote {
    				// Inside quotes or escaped this is legal.
    
    
    				escape = false
    
    
    			zl.commt = true
    
    			zl.comBuf = ""
    
    
    			if comi > 1 {
    				// A newline was previously seen inside a comment that
    				// was inside braces and we delayed adding it until now.
    				com[comi] = ' ' // convert newline to space
    				comi++
    
    				if comi >= len(com) {
    					l.token = "comment length insufficient for parsing"
    					l.err = true
    					return *l, true
    				}
    
    
    			com[comi] = ';'
    			comi++
    
    
    				zl.comBuf = string(com[:comi])
    
    				l.value = zString
    				l.token = string(str[:stri])
    
    				return *l, true
    
    			}
    		case '\r':
    			escape = false
    
    
    			if zl.quote {
    
    			// discard if outside of quotes
    		case '\n':
    			escape = false
    
    			if zl.quote {
    
    
    			if zl.commt {
    
    				zl.commt = false
    				zl.rrtype = false
    
    
    				// If not in a brace this ends the comment AND the RR
    
    				if zl.brace == 0 {
    					zl.owner = true
    
    
    					l.value = zNewline
    					l.token = "\n"
    
    					zl.comment = string(com[:comi])
    
    					return *l, true
    
    				zl.comBuf = string(com[:comi])
    
    			if zl.brace == 0 {
    
    				// If there is previous text, we should output it here
    
    				var retL lex
    
    				if stri != 0 {
    					l.value = zString
    					l.token = string(str[:stri])
    
    
    					if !zl.rrtype {
    						tokenUpper := strings.ToUpper(l.token)
    						if t, ok := StringToType[tokenUpper]; ok {
    							zl.rrtype = true
    
    
    				l.value = zNewline
    				l.token = "\n"
    
    				zl.comment = zl.comBuf
    				zl.comBuf = ""
    
    				zl.rrtype = false
    				zl.owner = true
    
    				if retL != (lex{}) {
    					zl.nextL = true
    					return retL, true
    				}