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.SplitN(s, ",", 3) 137 if len(xs) != 3 { 138 return "", 0, errors.New("bad modifier in $GENERATE") 139 } 140 // xs[0] is offset, xs[1] is width, xs[2] is base 141 if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { 142 return "", 0, errors.New("bad base in $GENERATE") 143 } 144 offset, err := strconv.Atoi(xs[0]) 145 if err != nil || offset > 255 { 146 return "", 0, errors.New("bad offset in $GENERATE") 147 } 148 width, err := strconv.Atoi(xs[1]) 149 if err != nil || width > 255 { 150 return "", offset, errors.New("bad width in $GENERATE") 151 } 152 switch { 153 case width < 0: 154 return "", offset, errors.New("bad width in $GENERATE") 155 case width == 0: 156 return "%" + xs[1] + xs[2], offset, nil 157 } 158 return "%0" + xs[1] + xs[2], offset, nil 159} 160