| package dns |
| |
| import ( |
| "bytes" |
| "fmt" |
| "strconv" |
| "strings" |
| ) |
| |
| // Parse the $GENERATE statement as used in BIND9 zones. |
| // See http://www.zytrax.com/books/dns/ch8/generate.html for instance. |
| // We are called after '$GENERATE '. After which we expect: |
| // * the range (12-24/2) |
| // * lhs (ownername) |
| // * [[ttl][class]] |
| // * type |
| // * rhs (rdata) |
| // But we are lazy here, only the range is parsed *all* occurences |
| // of $ after that are interpreted. |
| // Any error are returned as a string value, the empty string signals |
| // "no error". |
| func generate(l lex, c chan lex, t chan *Token, o string) string { |
| step := 1 |
| if i := strings.IndexAny(l.token, "/"); i != -1 { |
| if i+1 == len(l.token) { |
| return "bad step in $GENERATE range" |
| } |
| if s, e := strconv.Atoi(l.token[i+1:]); e == nil { |
| if s < 0 { |
| return "bad step in $GENERATE range" |
| } |
| step = s |
| } else { |
| return "bad step in $GENERATE range" |
| } |
| l.token = l.token[:i] |
| } |
| sx := strings.SplitN(l.token, "-", 2) |
| if len(sx) != 2 { |
| return "bad start-stop in $GENERATE range" |
| } |
| start, err := strconv.Atoi(sx[0]) |
| if err != nil { |
| return "bad start in $GENERATE range" |
| } |
| end, err := strconv.Atoi(sx[1]) |
| if err != nil { |
| return "bad stop in $GENERATE range" |
| } |
| if end < 0 || start < 0 || end < start { |
| return "bad range in $GENERATE range" |
| } |
| |
| <-c // _BLANK |
| // Create a complete new string, which we then parse again. |
| s := "" |
| BuildRR: |
| l = <-c |
| if l.value != zNewline && l.value != zEOF { |
| s += l.token |
| goto BuildRR |
| } |
| for i := start; i <= end; i += step { |
| var ( |
| escape bool |
| dom bytes.Buffer |
| mod string |
| err string |
| offset int |
| ) |
| |
| for j := 0; j < len(s); j++ { // No 'range' because we need to jump around |
| switch s[j] { |
| case '\\': |
| if escape { |
| dom.WriteByte('\\') |
| escape = false |
| continue |
| } |
| escape = true |
| case '$': |
| mod = "%d" |
| offset = 0 |
| if escape { |
| dom.WriteByte('$') |
| escape = false |
| continue |
| } |
| escape = false |
| if j+1 >= len(s) { // End of the string |
| dom.WriteString(fmt.Sprintf(mod, i+offset)) |
| continue |
| } else { |
| if s[j+1] == '$' { |
| dom.WriteByte('$') |
| j++ |
| continue |
| } |
| } |
| // Search for { and } |
| if s[j+1] == '{' { // Modifier block |
| sep := strings.Index(s[j+2:], "}") |
| if sep == -1 { |
| return "bad modifier in $GENERATE" |
| } |
| mod, offset, err = modToPrintf(s[j+2 : j+2+sep]) |
| if err != "" { |
| return err |
| } |
| j += 2 + sep // Jump to it |
| } |
| dom.WriteString(fmt.Sprintf(mod, i+offset)) |
| default: |
| if escape { // Pretty useless here |
| escape = false |
| continue |
| } |
| dom.WriteByte(s[j]) |
| } |
| } |
| // Re-parse the RR and send it on the current channel t |
| rx, e := NewRR("$ORIGIN " + o + "\n" + dom.String()) |
| if e != nil { |
| return e.(*ParseError).err |
| } |
| t <- &Token{RR: rx} |
| // Its more efficient to first built the rrlist and then parse it in |
| // one go! But is this a problem? |
| } |
| return "" |
| } |
| |
| // Convert a $GENERATE modifier 0,0,d to something Printf can deal with. |
| func modToPrintf(s string) (string, int, string) { |
| xs := strings.SplitN(s, ",", 3) |
| if len(xs) != 3 { |
| return "", 0, "bad modifier in $GENERATE" |
| } |
| // xs[0] is offset, xs[1] is width, xs[2] is base |
| if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { |
| return "", 0, "bad base in $GENERATE" |
| } |
| offset, err := strconv.Atoi(xs[0]) |
| if err != nil || offset > 255 { |
| return "", 0, "bad offset in $GENERATE" |
| } |
| width, err := strconv.Atoi(xs[1]) |
| if err != nil || width > 255 { |
| return "", offset, "bad width in $GENERATE" |
| } |
| switch { |
| case width < 0: |
| return "", offset, "bad width in $GENERATE" |
| case width == 0: |
| return "%" + xs[1] + xs[2], offset, "" |
| } |
| return "%0" + xs[1] + xs[2], offset, "" |
| } |