435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
248 lines
4.9 KiB
Go
248 lines
4.9 KiB
Go
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 || (end-start)/step > 65535 {
|
|
return zp.setParseError("bad range in $GENERATE range", l)
|
|
}
|
|
|
|
// _BLANK
|
|
l, ok := zp.c.Next()
|
|
if !ok || l.value != zBlank {
|
|
return zp.setParseError("garbage after $GENERATE range", l)
|
|
}
|
|
|
|
// 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.generateDisallowed = true
|
|
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, ""
|
|
}
|