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