1package dns
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
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// Any error are returned as a string value, the empty string signals
22// "no error".
23func generate(l lex, c chan lex, t chan *Token, o string) string {
24	step := 1
25	if i := strings.IndexAny(l.token, "/"); i != -1 {
26		if i+1 == len(l.token) {
27			return "bad step in $GENERATE range"
28		}
29		if s, err := strconv.Atoi(l.token[i+1:]); err == nil {
30			if s < 0 {
31				return "bad step in $GENERATE range"
32			}
33			step = s
34		} else {
35			return "bad step in $GENERATE range"
36		}
37		l.token = l.token[:i]
38	}
39	sx := strings.SplitN(l.token, "-", 2)
40	if len(sx) != 2 {
41		return "bad start-stop in $GENERATE range"
42	}
43	start, err := strconv.Atoi(sx[0])
44	if err != nil {
45		return "bad start in $GENERATE range"
46	}
47	end, err := strconv.Atoi(sx[1])
48	if err != nil {
49		return "bad stop in $GENERATE range"
50	}
51	if end < 0 || start < 0 || end < start {
52		return "bad range in $GENERATE range"
53	}
54
55	<-c // _BLANK
56	// Create a complete new string, which we then parse again.
57	s := ""
58BuildRR:
59	l = <-c
60	if l.value != zNewline && l.value != zEOF {
61		s += l.token
62		goto BuildRR
63	}
64	for i := start; i <= end; i += step {
65		var (
66			escape bool
67			dom    bytes.Buffer
68			mod    string
69			err    error
70			offset int
71		)
72
73		for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
74			switch s[j] {
75			case '\\':
76				if escape {
77					dom.WriteByte('\\')
78					escape = false
79					continue
80				}
81				escape = true
82			case '$':
83				mod = "%d"
84				offset = 0
85				if escape {
86					dom.WriteByte('$')
87					escape = false
88					continue
89				}
90				escape = false
91				if j+1 >= len(s) { // End of the string
92					dom.WriteString(fmt.Sprintf(mod, i+offset))
93					continue
94				} else {
95					if s[j+1] == '$' {
96						dom.WriteByte('$')
97						j++
98						continue
99					}
100				}
101				// Search for { and }
102				if s[j+1] == '{' { // Modifier block
103					sep := strings.Index(s[j+2:], "}")
104					if sep == -1 {
105						return "bad modifier in $GENERATE"
106					}
107					mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
108					if err != nil {
109						return err.Error()
110					}
111					j += 2 + sep // Jump to it
112				}
113				dom.WriteString(fmt.Sprintf(mod, i+offset))
114			default:
115				if escape { // Pretty useless here
116					escape = false
117					continue
118				}
119				dom.WriteByte(s[j])
120			}
121		}
122		// Re-parse the RR and send it on the current channel t
123		rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String())
124		if err != nil {
125			return err.Error()
126		}
127		t <- &Token{RR: rx}
128		// Its more efficient to first built the rrlist and then parse it in
129		// one go! But is this a problem?
130	}
131	return ""
132}
133
134// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
135func modToPrintf(s string) (string, int, error) {
136	xs := strings.Split(s, ",")
137
138	// Modifier is { offset [ ,width [ ,base ] ] } - provide default
139	// values for optional width and type, if necessary.
140	switch len(xs) {
141	case 1:
142		xs = append(xs, "0", "d")
143	case 2:
144		xs = append(xs, "d")
145	case 3:
146	default:
147		return "", 0, errors.New("bad modifier in $GENERATE")
148	}
149
150	// xs[0] is offset, xs[1] is width, xs[2] is base
151	if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
152		return "", 0, errors.New("bad base in $GENERATE")
153	}
154	offset, err := strconv.Atoi(xs[0])
155	if err != nil || offset > 255 {
156		return "", 0, errors.New("bad offset in $GENERATE")
157	}
158	width, err := strconv.Atoi(xs[1])
159	if err != nil || width > 255 {
160		return "", offset, errors.New("bad width in $GENERATE")
161	}
162	switch {
163	case width < 0:
164		return "", offset, errors.New("bad width in $GENERATE")
165	case width == 0:
166		return "%" + xs[1] + xs[2], offset, nil
167	}
168	return "%0" + xs[1] + xs[2], offset, nil
169}
170