160 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
package dns
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// Parse the $GENERATE statement as used in BIND9 zones.
 | 
						|
// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
 | 
						|
// We are called after '$GENERATE '. After which we expect:
 | 
						|
// * the range (12-24/2)
 | 
						|
// * lhs (ownername)
 | 
						|
// * [[ttl][class]]
 | 
						|
// * type
 | 
						|
// * rhs (rdata)
 | 
						|
// But we are lazy here, only the range is parsed *all* occurrences
 | 
						|
// of $ after that are interpreted.
 | 
						|
// Any error are returned as a string value, the empty string signals
 | 
						|
// "no error".
 | 
						|
func generate(l lex, c chan lex, t chan *Token, o string) string {
 | 
						|
	step := 1
 | 
						|
	if i := strings.IndexAny(l.token, "/"); i != -1 {
 | 
						|
		if i+1 == len(l.token) {
 | 
						|
			return "bad step in $GENERATE range"
 | 
						|
		}
 | 
						|
		if s, err := strconv.Atoi(l.token[i+1:]); err == nil {
 | 
						|
			if s < 0 {
 | 
						|
				return "bad step in $GENERATE range"
 | 
						|
			}
 | 
						|
			step = s
 | 
						|
		} else {
 | 
						|
			return "bad step in $GENERATE range"
 | 
						|
		}
 | 
						|
		l.token = l.token[:i]
 | 
						|
	}
 | 
						|
	sx := strings.SplitN(l.token, "-", 2)
 | 
						|
	if len(sx) != 2 {
 | 
						|
		return "bad start-stop in $GENERATE range"
 | 
						|
	}
 | 
						|
	start, err := strconv.Atoi(sx[0])
 | 
						|
	if err != nil {
 | 
						|
		return "bad start in $GENERATE range"
 | 
						|
	}
 | 
						|
	end, err := strconv.Atoi(sx[1])
 | 
						|
	if err != nil {
 | 
						|
		return "bad stop in $GENERATE range"
 | 
						|
	}
 | 
						|
	if end < 0 || start < 0 || end < start {
 | 
						|
		return "bad range in $GENERATE range"
 | 
						|
	}
 | 
						|
 | 
						|
	<-c // _BLANK
 | 
						|
	// Create a complete new string, which we then parse again.
 | 
						|
	s := ""
 | 
						|
BuildRR:
 | 
						|
	l = <-c
 | 
						|
	if l.value != zNewline && l.value != zEOF {
 | 
						|
		s += l.token
 | 
						|
		goto BuildRR
 | 
						|
	}
 | 
						|
	for i := start; i <= end; i += step {
 | 
						|
		var (
 | 
						|
			escape bool
 | 
						|
			dom    bytes.Buffer
 | 
						|
			mod    string
 | 
						|
			err    error
 | 
						|
			offset int
 | 
						|
		)
 | 
						|
 | 
						|
		for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
 | 
						|
			switch s[j] {
 | 
						|
			case '\\':
 | 
						|
				if escape {
 | 
						|
					dom.WriteByte('\\')
 | 
						|
					escape = false
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				escape = true
 | 
						|
			case '$':
 | 
						|
				mod = "%d"
 | 
						|
				offset = 0
 | 
						|
				if escape {
 | 
						|
					dom.WriteByte('$')
 | 
						|
					escape = false
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				escape = false
 | 
						|
				if j+1 >= len(s) { // End of the string
 | 
						|
					dom.WriteString(fmt.Sprintf(mod, i+offset))
 | 
						|
					continue
 | 
						|
				} else {
 | 
						|
					if s[j+1] == '$' {
 | 
						|
						dom.WriteByte('$')
 | 
						|
						j++
 | 
						|
						continue
 | 
						|
					}
 | 
						|
				}
 | 
						|
				// Search for { and }
 | 
						|
				if s[j+1] == '{' { // Modifier block
 | 
						|
					sep := strings.Index(s[j+2:], "}")
 | 
						|
					if sep == -1 {
 | 
						|
						return "bad modifier in $GENERATE"
 | 
						|
					}
 | 
						|
					mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
 | 
						|
					if err != nil {
 | 
						|
						return err.Error()
 | 
						|
					}
 | 
						|
					j += 2 + sep // Jump to it
 | 
						|
				}
 | 
						|
				dom.WriteString(fmt.Sprintf(mod, i+offset))
 | 
						|
			default:
 | 
						|
				if escape { // Pretty useless here
 | 
						|
					escape = false
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				dom.WriteByte(s[j])
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// Re-parse the RR and send it on the current channel t
 | 
						|
		rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String())
 | 
						|
		if err != nil {
 | 
						|
			return err.Error()
 | 
						|
		}
 | 
						|
		t <- &Token{RR: rx}
 | 
						|
		// Its more efficient to first built the rrlist and then parse it in
 | 
						|
		// one go! But is this a problem?
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
 | 
						|
func modToPrintf(s string) (string, int, error) {
 | 
						|
	xs := strings.SplitN(s, ",", 3)
 | 
						|
	if len(xs) != 3 {
 | 
						|
		return "", 0, errors.New("bad modifier in $GENERATE")
 | 
						|
	}
 | 
						|
	// xs[0] is offset, xs[1] is width, xs[2] is base
 | 
						|
	if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
 | 
						|
		return "", 0, errors.New("bad base in $GENERATE")
 | 
						|
	}
 | 
						|
	offset, err := strconv.Atoi(xs[0])
 | 
						|
	if err != nil || offset > 255 {
 | 
						|
		return "", 0, errors.New("bad offset in $GENERATE")
 | 
						|
	}
 | 
						|
	width, err := strconv.Atoi(xs[1])
 | 
						|
	if err != nil || width > 255 {
 | 
						|
		return "", offset, errors.New("bad width in $GENERATE")
 | 
						|
	}
 | 
						|
	switch {
 | 
						|
	case width < 0:
 | 
						|
		return "", offset, errors.New("bad width in $GENERATE")
 | 
						|
	case width == 0:
 | 
						|
		return "%" + xs[1] + xs[2], offset, nil
 | 
						|
	}
 | 
						|
	return "%0" + xs[1] + xs[2], offset, nil
 | 
						|
}
 |