172 lines
4.1 KiB
Go
172 lines
4.1 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 *zlexer, 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.Next() // _BLANK
|
|
// Create a complete new string, which we then parse again.
|
|
s := ""
|
|
BuildRR:
|
|
l, _ = c.Next()
|
|
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()
|
|
} else if start+offset < 0 || end+offset > 1<<31-1 {
|
|
return "bad offset in $GENERATE"
|
|
}
|
|
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.Split(s, ",")
|
|
|
|
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
|
|
// values for optional width and type, if necessary.
|
|
switch len(xs) {
|
|
case 1:
|
|
xs = append(xs, "0", "d")
|
|
case 2:
|
|
xs = append(xs, "d")
|
|
case 3:
|
|
default:
|
|
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 {
|
|
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
|
|
}
|