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