generate.go raw

   1  package dns
   2  
   3  import (
   4  	"bytes"
   5  	"fmt"
   6  	"io"
   7  	"strconv"
   8  	"strings"
   9  )
  10  
  11  // Parse the $GENERATE statement as used in BIND9 zones.
  12  // See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
  13  // We are called after '$GENERATE '. After which we expect:
  14  // * the range (12-24/2)
  15  // * lhs (ownername)
  16  // * [[ttl][class]]
  17  // * type
  18  // * rhs (rdata)
  19  // But we are lazy here, only the range is parsed *all* occurrences
  20  // of $ after that are interpreted.
  21  func (zp *ZoneParser) generate(l lex) (RR, bool) {
  22  	token := l.token
  23  	step := int64(1)
  24  	if i := strings.IndexByte(token, '/'); i >= 0 {
  25  		if i+1 == len(token) {
  26  			return zp.setParseError("bad step in $GENERATE range", l)
  27  		}
  28  
  29  		s, err := strconv.ParseInt(token[i+1:], 10, 64)
  30  		if err != nil || s <= 0 {
  31  			return zp.setParseError("bad step in $GENERATE range", l)
  32  		}
  33  
  34  		step = s
  35  		token = token[:i]
  36  	}
  37  
  38  	startStr, endStr, ok := strings.Cut(token, "-")
  39  	if !ok {
  40  		return zp.setParseError("bad start-stop in $GENERATE range", l)
  41  	}
  42  
  43  	start, err := strconv.ParseInt(startStr, 10, 64)
  44  	if err != nil {
  45  		return zp.setParseError("bad start in $GENERATE range", l)
  46  	}
  47  
  48  	end, err := strconv.ParseInt(endStr, 10, 64)
  49  	if err != nil {
  50  		return zp.setParseError("bad stop in $GENERATE range", l)
  51  	}
  52  	if end < 0 || start < 0 || end < start || (end-start)/step > 65535 {
  53  		return zp.setParseError("bad range in $GENERATE range", l)
  54  	}
  55  
  56  	// _BLANK
  57  	l, ok = zp.c.Next()
  58  	if !ok || l.value != zBlank {
  59  		return zp.setParseError("garbage after $GENERATE range", l)
  60  	}
  61  
  62  	// Create a complete new string, which we then parse again.
  63  	var s string
  64  	for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
  65  		if l.err {
  66  			return zp.setParseError("bad data in $GENERATE directive", l)
  67  		}
  68  		if l.value == zNewline {
  69  			break
  70  		}
  71  
  72  		s += l.token
  73  	}
  74  
  75  	r := &generateReader{
  76  		s: s,
  77  
  78  		cur:   start,
  79  		start: start,
  80  		end:   end,
  81  		step:  step,
  82  
  83  		file: zp.file,
  84  		lex:  &l,
  85  	}
  86  	zp.sub = NewZoneParser(r, zp.origin, zp.file)
  87  	zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
  88  	zp.sub.generateDisallowed = true
  89  	zp.sub.SetDefaultTTL(defaultTtl)
  90  	return zp.subNext()
  91  }
  92  
  93  type generateReader struct {
  94  	s  string
  95  	si int
  96  
  97  	cur   int64
  98  	start int64
  99  	end   int64
 100  	step  int64
 101  
 102  	mod bytes.Buffer
 103  
 104  	escape bool
 105  
 106  	eof bool
 107  
 108  	file string
 109  	lex  *lex
 110  }
 111  
 112  func (r *generateReader) parseError(msg string, end int) *ParseError {
 113  	r.eof = true // Make errors sticky.
 114  
 115  	l := *r.lex
 116  	l.token = r.s[r.si-1 : end]
 117  	l.column += r.si // l.column starts one zBLANK before r.s
 118  
 119  	return &ParseError{file: r.file, err: msg, lex: l}
 120  }
 121  
 122  func (r *generateReader) Read(p []byte) (int, error) {
 123  	// NewZLexer, through NewZoneParser, should use ReadByte and
 124  	// not end up here.
 125  
 126  	panic("not implemented")
 127  }
 128  
 129  func (r *generateReader) ReadByte() (byte, error) {
 130  	if r.eof {
 131  		return 0, io.EOF
 132  	}
 133  	if r.mod.Len() > 0 {
 134  		return r.mod.ReadByte()
 135  	}
 136  
 137  	if r.si >= len(r.s) {
 138  		r.si = 0
 139  		r.cur += r.step
 140  
 141  		r.eof = r.cur > r.end || r.cur < 0
 142  		return '\n', nil
 143  	}
 144  
 145  	si := r.si
 146  	r.si++
 147  
 148  	switch r.s[si] {
 149  	case '\\':
 150  		if r.escape {
 151  			r.escape = false
 152  			return '\\', nil
 153  		}
 154  
 155  		r.escape = true
 156  		return r.ReadByte()
 157  	case '$':
 158  		if r.escape {
 159  			r.escape = false
 160  			return '$', nil
 161  		}
 162  
 163  		mod := "%d"
 164  
 165  		if si >= len(r.s)-1 {
 166  			// End of the string
 167  			fmt.Fprintf(&r.mod, mod, r.cur)
 168  			return r.mod.ReadByte()
 169  		}
 170  
 171  		if r.s[si+1] == '$' {
 172  			r.si++
 173  			return '$', nil
 174  		}
 175  
 176  		var offset int64
 177  
 178  		// Search for { and }
 179  		if r.s[si+1] == '{' {
 180  			// Modifier block
 181  			sep := strings.Index(r.s[si+2:], "}")
 182  			if sep < 0 {
 183  				return 0, r.parseError("bad modifier in $GENERATE", len(r.s))
 184  			}
 185  
 186  			var errMsg string
 187  			mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep])
 188  			if errMsg != "" {
 189  				return 0, r.parseError(errMsg, si+3+sep)
 190  			}
 191  			if r.start+offset < 0 || r.end+offset > 1<<31-1 {
 192  				return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
 193  			}
 194  
 195  			r.si += 2 + sep // Jump to it
 196  		}
 197  
 198  		fmt.Fprintf(&r.mod, mod, r.cur+offset)
 199  		return r.mod.ReadByte()
 200  	default:
 201  		if r.escape { // Pretty useless here
 202  			r.escape = false
 203  			return r.ReadByte()
 204  		}
 205  
 206  		return r.s[si], nil
 207  	}
 208  }
 209  
 210  // Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
 211  func modToPrintf(s string) (string, int64, string) {
 212  	// Modifier is { offset [ ,width [ ,base ] ] } - provide default
 213  	// values for optional width and type, if necessary.
 214  	offStr, s, ok0 := strings.Cut(s, ",")
 215  	widthStr, s, ok1 := strings.Cut(s, ",")
 216  	base, _, ok2 := strings.Cut(s, ",")
 217  	if !ok0 {
 218  		widthStr = "0"
 219  	}
 220  	if !ok1 {
 221  		base = "d"
 222  	}
 223  	if ok2 {
 224  		return "", 0, "bad modifier in $GENERATE"
 225  	}
 226  
 227  	switch base {
 228  	case "o", "d", "x", "X":
 229  	default:
 230  		return "", 0, "bad base in $GENERATE"
 231  	}
 232  
 233  	offset, err := strconv.ParseInt(offStr, 10, 64)
 234  	if err != nil {
 235  		return "", 0, "bad offset in $GENERATE"
 236  	}
 237  
 238  	width, err := strconv.ParseUint(widthStr, 10, 8)
 239  	if err != nil {
 240  		return "", 0, "bad width in $GENERATE"
 241  	}
 242  
 243  	if width == 0 {
 244  		return "%" + base, offset, ""
 245  	}
 246  
 247  	return "%0" + widthStr + base, offset, ""
 248  }
 249