|
|
|
package dns
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"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.
|
|
|
|
func (zp *ZoneParser) generate(l lex) (RR, bool) {
|
|
|
|
token := l.token
|
|
|
|
step := 1
|
|
|
|
if i := strings.IndexByte(token, '/'); i >= 0 {
|
|
|
|
if i+1 == len(token) {
|
|
|
|
return zp.setParseError("bad step in $GENERATE range", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := strconv.Atoi(token[i+1:])
|
|
|
|
if err != nil || s <= 0 {
|
|
|
|
return zp.setParseError("bad step in $GENERATE range", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
step = s
|
|
|
|
token = token[:i]
|
|
|
|
}
|
|
|
|
|
|
|
|
sx := strings.SplitN(token, "-", 2)
|
|
|
|
if len(sx) != 2 {
|
|
|
|
return zp.setParseError("bad start-stop in $GENERATE range", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
start, err := strconv.Atoi(sx[0])
|
|
|
|
if err != nil {
|
|
|
|
return zp.setParseError("bad start in $GENERATE range", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
end, err := strconv.Atoi(sx[1])
|
|
|
|
if err != nil {
|
|
|
|
return zp.setParseError("bad stop in $GENERATE range", l)
|
|
|
|
}
|
|
|
|
if end < 0 || start < 0 || end < start {
|
|
|
|
return zp.setParseError("bad range in $GENERATE range", l)
|
|
|
|
}
|
|
|
|
|
|
|
|
zp.c.Next() // _BLANK
|
|
|
|
|
|
|
|
// Create a complete new string, which we then parse again.
|
|
|
|
var s string
|
|
|
|
for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
|
|
|
|
if l.err {
|
|
|
|
return zp.setParseError("bad data in $GENERATE directive", l)
|
|
|
|
}
|
|
|
|
if l.value == zNewline {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
s += l.token
|
|
|
|
}
|
|
|
|
|
|
|
|
r := &generateReader{
|
|
|
|
s: s,
|
|
|
|
|
|
|
|
cur: start,
|
|
|
|
start: start,
|
|
|
|
end: end,
|
|
|
|
step: step,
|
|
|
|
|
|
|
|
file: zp.file,
|
|
|
|
lex: &l,
|
|
|
|
}
|
|
|
|
zp.sub = NewZoneParser(r, zp.origin, zp.file)
|
|
|
|
zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
|
|
|
|
zp.sub.SetDefaultTTL(defaultTtl)
|
|
|
|
return zp.subNext()
|
|
|
|
}
|
|
|
|
|
|
|
|
type generateReader struct {
|
|
|
|
s string
|
|
|
|
si int
|
|
|
|
|
|
|
|
cur int
|
|
|
|
start int
|
|
|
|
end int
|
|
|
|
step int
|
|
|
|
|
|
|
|
mod bytes.Buffer
|
|
|
|
|
|
|
|
escape bool
|
|
|
|
|
|
|
|
eof bool
|
|
|
|
|
|
|
|
file string
|
|
|
|
lex *lex
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *generateReader) parseError(msg string, end int) *ParseError {
|
|
|
|
r.eof = true // Make errors sticky.
|
|
|
|
|
|
|
|
l := *r.lex
|
|
|
|
l.token = r.s[r.si-1 : end]
|
|
|
|
l.column += r.si // l.column starts one zBLANK before r.s
|
|
|
|
|
|
|
|
return &ParseError{r.file, msg, l}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *generateReader) Read(p []byte) (int, error) {
|
|
|
|
// NewZLexer, through NewZoneParser, should use ReadByte and
|
|
|
|
// not end up here.
|
|
|
|
|
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *generateReader) ReadByte() (byte, error) {
|
|
|
|
if r.eof {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
if r.mod.Len() > 0 {
|
|
|
|
return r.mod.ReadByte()
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.si >= len(r.s) {
|
|
|
|
r.si = 0
|
|
|
|
r.cur += r.step
|
|
|
|
|
|
|
|
r.eof = r.cur > r.end || r.cur < 0
|
|
|
|
return '\n', nil
|
|
|
|
}
|
|
|
|
|
|
|
|
si := r.si
|
|
|
|
r.si++
|
|
|
|
|
|
|
|
switch r.s[si] {
|
|
|
|
case '\\':
|
|
|
|
if r.escape {
|
|
|
|
r.escape = false
|
|
|
|
return '\\', nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r.escape = true
|
|
|
|
return r.ReadByte()
|
|
|
|
case '$':
|
|
|
|
if r.escape {
|
|
|
|
r.escape = false
|
|
|
|
return '$', nil
|
|
|
|
}
|
|
|
|
|
|
|
|
mod := "%d"
|
|
|
|
|
|
|
|
if si >= len(r.s)-1 {
|
|
|
|
// End of the string
|
|
|
|
fmt.Fprintf(&r.mod, mod, r.cur)
|
|
|
|
return r.mod.ReadByte()
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.s[si+1] == '$' {
|
|
|
|
r.si++
|
|
|
|
return '$', nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var offset int
|
|
|
|
|
|
|
|
// Search for { and }
|
|
|
|
if r.s[si+1] == '{' {
|
|
|
|
// Modifier block
|
|
|
|
sep := strings.Index(r.s[si+2:], "}")
|
|
|
|
if sep < 0 {
|
|
|
|
return 0, r.parseError("bad modifier in $GENERATE", len(r.s))
|
|
|
|
}
|
|
|
|
|
|
|
|
var errMsg string
|
|
|
|
mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep])
|
|
|
|
if errMsg != "" {
|
|
|
|
return 0, r.parseError(errMsg, si+3+sep)
|
|
|
|
}
|
|
|
|
if r.start+offset < 0 || r.end+offset > 1<<31-1 {
|
|
|
|
return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
|
|
|
|
}
|
|
|
|
|
|
|
|
r.si += 2 + sep // Jump to it
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(&r.mod, mod, r.cur+offset)
|
|
|
|
return r.mod.ReadByte()
|
|
|
|
default:
|
|
|
|
if r.escape { // Pretty useless here
|
|
|
|
r.escape = false
|
|
|
|
return r.ReadByte()
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.s[si], nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
|
|
|
|
func modToPrintf(s string) (string, int, string) {
|
|
|
|
// Modifier is { offset [ ,width [ ,base ] ] } - provide default
|
|
|
|
// values for optional width and type, if necessary.
|
|
|
|
var offStr, widthStr, base string
|
|
|
|
switch xs := strings.Split(s, ","); len(xs) {
|
|
|
|
case 1:
|
|
|
|
offStr, widthStr, base = xs[0], "0", "d"
|
|
|
|
case 2:
|
|
|
|
offStr, widthStr, base = xs[0], xs[1], "d"
|
|
|
|
case 3:
|
|
|
|
offStr, widthStr, base = xs[0], xs[1], xs[2]
|
|
|
|
default:
|
|
|
|
return "", 0, "bad modifier in $GENERATE"
|
|
|
|
}
|
|
|
|
|
|
|
|
switch base {
|
|
|
|
case "o", "d", "x", "X":
|
|
|
|
default:
|
|
|
|
return "", 0, "bad base in $GENERATE"
|
|
|
|
}
|
|
|
|
|
|
|
|
offset, err := strconv.Atoi(offStr)
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, "bad offset in $GENERATE"
|
|
|
|
}
|
|
|
|
|
|
|
|
width, err := strconv.Atoi(widthStr)
|
|
|
|
if err != nil || width < 0 || width > 255 {
|
|
|
|
return "", 0, "bad width in $GENERATE"
|
|
|
|
}
|
|
|
|
|
|
|
|
if width == 0 {
|
|
|
|
return "%" + base, offset, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return "%0" + widthStr + base, offset, ""
|
|
|
|
}
|