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