package bind import ( "fmt" "io/ioutil" "strings" "time" "github.com/miekg/dns" ) type Signable interface { SetTsig(string, string, uint16, int64) *dns.Msg } func NewBINDConfig() *BINDConfig { return &BINDConfig{ zones: map[string]map[string]*Zone{}, keys: map[string]*Key{}, } } type BINDConfig struct { zones map[string]map[string]*Zone keys map[string]*Key } func (c *BINDConfig) Views() []string { v := []string{} for vn, _ := range c.zones { v = append(v, vn) } return v } func (c *BINDConfig) Zone(view, name string) *Zone { if !strings.HasSuffix(name, ".") { name = fmt.Sprintf("%s.", name) } if _, ok := c.zones[view]; !ok { return nil } z, ok := c.zones[view][name] if !ok { return nil } if z.InView != "" { return c.zones[z.InView][name] } return z } // Find the closest zone that we manage by striping dotted components off the // front of the domain until one matches. If there is a match return the zone // that matched and any prefix components, if any, as a dotted string. If none // match then return nil. func (c *BINDConfig) FindClosestZone(zoneIn, view string) (*Zone, string) { suffix := "" prefix := []string{} zc := strings.Split(zoneIn, ".") for i := 0; i <= len(zc)-2; i++ { prefix, suffix = zc[:i], strings.Join(zc[i:], ".") if zone := c.Zone(view, suffix); zone != nil { return zone, strings.Join(prefix, ".") } } return nil, "" } func (c *BINDConfig) AddKey(k *Key) { c.keys[k.Name] = k } func (c *BINDConfig) AddZone(z *Zone, view string) { if c.zones[view] == nil { c.zones[view] = map[string]*Zone{} } c.zones[view][z.Name] = z z.config = c } func NewZone(name string) *Zone { // Canonicalize name if !strings.HasSuffix(name, ".") { name = fmt.Sprintf("%s.", name) } return &Zone{ Name: name, keys: []string{}, } } type Zone struct { Name string Type string InView string keys []string config *BINDConfig } func (z *Zone) AddKey(key string) { z.keys = append(z.keys, key) } func (z *Zone) Keys() []*Key { k := []*Key{} for _, kn := range z.keys { k = append(k, z.config.keys[kn]) } return k } type Key struct { Name string Algorithm string Secret string } func (k *Key) CanonicalName() string { if !strings.HasSuffix(k.Name, ".") { return fmt.Sprintf("%s.", k.Name) } return k.Name } func (k *Key) CanonicalAlgorithm() string { if !strings.HasSuffix(k.Algorithm, ".") { return fmt.Sprintf("%s.", k.Algorithm) } return k.Name } func (k *Key) Sign(r Signable) { r.SetTsig(k.CanonicalName(), k.CanonicalAlgorithm(), 300, time.Now().Unix()) } func (k *Key) AsMap() map[string]string { return map[string]string{k.CanonicalName(): k.Secret} } func NewStringStack() *StringStack { return &StringStack{[]string{}} } type StringStack struct { items []string } func (s *StringStack) isEmpty() bool { return len(s.items) == 0 } func (s *StringStack) Peek() string { return s.items[len(s.items)-1] } func (s *StringStack) Pop() string { if s.isEmpty() { return "" } v := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return v } func (s *StringStack) Push(v string) { s.items = append(s.items, v) } func cleanValue(s string) string { return strings.Trim(strings.Trim(s, ";"), "\"") } func isContainerStart(line []string) bool { return line[len(line)-1] == "{" } func ParseBINDConfig(filename string) (*BINDConfig, error) { data, err := ioutil.ReadFile(filename) if err != nil { return nil, err } var view string var zone *Zone var key *Key config := NewBINDConfig() stack := NewStringStack() for _, line := range strings.Split(string(data), "\n") { line := strings.Split(strings.TrimSpace(line), " ") switch line[0] { case "};": t := stack.Pop() switch t { case "view": view = "" case "key": key = nil case "zone": zone = nil } case "view": view = cleanValue(line[1]) stack.Push("view") case "zone": zone = NewZone(cleanValue(line[1])) config.AddZone(zone, view) stack.Push("zone") case "in-view": zone.Type = "reference" zone.InView = cleanValue(line[1]) case "type": zone.Type = cleanValue(line[1]) case "grant": zone.AddKey(cleanValue(line[1])) case "key": if !isContainerStart(line) { continue } key = &Key{Name: cleanValue(line[1])} config.AddKey(key) stack.Push("key") case "algorithm": key.Algorithm = cleanValue(line[1]) case "secret": key.Secret = cleanValue(line[1]) default: if isContainerStart(line) { stack.Push(line[0]) } } } return config, nil }