package file import ( "fmt" "log" "os" "path" "strings" "sync" "github.com/miekg/coredns/middleware/file/tree" "github.com/miekg/coredns/request" "github.com/fsnotify/fsnotify" "github.com/miekg/dns" ) // Zone defines a structure that contains all data related to a DNS zone. type Zone struct { origin string file string *tree.Tree Apex Apex TransferTo []string StartupOnce sync.Once TransferFrom []string Expired *bool NoReload bool reloadMu sync.RWMutex ReloadShutdown chan bool } // Apex contains the apex records of a zone: SOA, NS and their potential signatures. type Apex struct { SOA *dns.SOA NS []dns.RR SIGSOA []dns.RR SIGNS []dns.RR } // NewZone returns a new zone. func NewZone(name, file string) *Zone { z := &Zone{ origin: dns.Fqdn(name), file: path.Clean(file), Tree: &tree.Tree{}, Expired: new(bool), ReloadShutdown: make(chan bool), } *z.Expired = false return z } // Copy copies a zone *without* copying the zone's content. It is not a deep copy. func (z *Zone) Copy() *Zone { z1 := NewZone(z.origin, z.file) z1.TransferTo = z.TransferTo z1.TransferFrom = z.TransferFrom z1.Expired = z.Expired z1.Apex = z.Apex return z1 } // Insert inserts r into z. func (z *Zone) Insert(r dns.RR) error { r.Header().Name = strings.ToLower(r.Header().Name) switch h := r.Header().Rrtype; h { case dns.TypeNS: r.(*dns.NS).Ns = strings.ToLower(r.(*dns.NS).Ns) if r.Header().Name == z.origin { z.Apex.NS = append(z.Apex.NS, r) return nil } case dns.TypeSOA: r.(*dns.SOA).Ns = strings.ToLower(r.(*dns.SOA).Ns) r.(*dns.SOA).Mbox = strings.ToLower(r.(*dns.SOA).Mbox) z.Apex.SOA = r.(*dns.SOA) return nil case dns.TypeNSEC3, dns.TypeNSEC3PARAM: return fmt.Errorf("NSEC3 zone is not supported, dropping RR: %s for zone: %s", r.Header().Name, z.origin) case dns.TypeRRSIG: x := r.(*dns.RRSIG) switch x.TypeCovered { case dns.TypeSOA: z.Apex.SIGSOA = append(z.Apex.SIGSOA, x) return nil case dns.TypeNS: if r.Header().Name == z.origin { z.Apex.SIGNS = append(z.Apex.SIGNS, x) return nil } } case dns.TypeCNAME: r.(*dns.CNAME).Target = strings.ToLower(r.(*dns.CNAME).Target) case dns.TypeMX: r.(*dns.MX).Mx = strings.ToLower(r.(*dns.MX).Mx) case dns.TypeSRV: r.(*dns.SRV).Target = strings.ToLower(r.(*dns.SRV).Target) } z.Tree.Insert(r) return nil } // Delete deletes r from z. func (z *Zone) Delete(r dns.RR) { z.Tree.Delete(r) } // TransferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs. func (z *Zone) TransferAllowed(req request.Request) bool { for _, t := range z.TransferTo { if t == "*" { return true } } // TODO(miek): future matching against IP/CIDR notations return false } // All returns all records from the zone, the first record will be the SOA record, // otionally followed by all RRSIG(SOA)s. func (z *Zone) All() []dns.RR { z.reloadMu.RLock() defer z.reloadMu.RUnlock() records := []dns.RR{} allNodes := z.Tree.All() for _, a := range allNodes { records = append(records, a.All()...) } if len(z.Apex.SIGNS) > 0 { records = append(z.Apex.SIGNS, records...) } records = append(z.Apex.NS, records...) if len(z.Apex.SIGSOA) > 0 { records = append(z.Apex.SIGSOA, records...) } return append([]dns.RR{z.Apex.SOA}, records...) } // Reload reloads a zone when it is changed on disk. If z.NoRoload is true, no reloading will be done. func (z *Zone) Reload() error { if z.NoReload { return nil } watcher, err := fsnotify.NewWatcher() if err != nil { return err } err = watcher.Add(path.Dir(z.file)) if err != nil { return err } go func() { // TODO(miek): needs to be killed on reload. for { select { case event := <-watcher.Events: if event.Op == fsnotify.Write && path.Clean(event.Name) == z.file { reader, err := os.Open(z.file) if err != nil { log.Printf("[ERROR] Failed to open `%s' for `%s': %v", z.file, z.origin, err) continue } zone, err := Parse(reader, z.origin, z.file) if err != nil { log.Printf("[ERROR] Failed to parse `%s': %v", z.origin, err) continue } // copy elements we need z.reloadMu.Lock() z.Apex = zone.Apex z.Tree = zone.Tree z.reloadMu.Unlock() log.Printf("[INFO] Successfully reloaded zone `%s'", z.origin) z.Notify() } case <-z.ReloadShutdown: watcher.Close() return } } }() return nil }