package main import ( "context" "errors" "fmt" "strings" "time" "github.com/pyke369/golang-support/rcache" ) type ( // DNSZones is a list of DNSZone DNSZones []*DNSZone // DNSZone is the json structure of a DNS zone in the PDNS API DNSZone struct { DNSQuery Name string `json:"name"` Kind string `json:"kind"` NameServers []string `json:"nameservers"` SOAEditAPI string `json:"soa_edit_api"` } ) type listCache struct { result []*DNSZone expire time.Time } // List all zones in the DNSZones struct as string func (z DNSZones) List(sep string) string { ret := []string{} for _, l := range z { ret = append(ret, trimPoint(l.Name)) } domainSort(ret) return strings.Join(ret, sep) } // NewZone returns a new DNSZone struct, use for domain creation func (p *PowerDNS) NewZone(name, zoneType, soa, user, comment string, ttl int, nameServers []string, autoInc bool) (*DNSZone, error) { z := &DNSZone{ Name: name, Kind: zoneType, NameServers: nameServers, } if autoInc { z.SOAEditAPI = "INCEPTION-INCREMENT" } // create SOA record soaRec, err := p.GetRecord(name, "SOA", true) if err != nil { return nil, err } soaRec.ChangeValue(name, soa, "SOA", false, false) soaRec.ChangeDomain(name) soaRec.AddCommentAndTTL(user, comment, ttl) // merge it into z z.RRSets = soaRec.RRSets return z, nil } func (z *DNSZone) String() string { return string(printJSON(z)) } // AddEntries put all RRSets in q into z func (z *DNSZone) AddEntries(q *DNSQuery) bool { if q.Domain != z.Name { return false } z.RRSets = append(z.RRSets, q.RRSets...) return true } // TransformIntoDNSQuery converts a DNSZone into a DNSQuery func (z *DNSZone) TransformIntoDNSQuery() *DNSQuery { return &DNSQuery{ Domain: z.Name, RRSets: z.RRSets, } } // ListZones list every zone matchin the re regexp if not empty func (p *PowerDNS) ListZones(re string) (DNSZones, error) { now := time.Now() if cache, ok := p.listCache[re]; ok && len(cache.result) > 0 && now.Before(cache.expire) { p.lock() cache.expire = now.Add(3 * time.Minute) p.unlock() return cache.result, nil } list, err := p.listZones(re) if err == nil && len(list) > 0 { p.lock() p.listCache[re] = &listCache{ result: list, expire: now.Add(3 * time.Minute), } p.unlock() } return list, err } // ListZones list every zone matchin the re regexp if not empty func (p *PowerDNS) listZones(re string) (DNSZones, error) { domains := DNSZones{} ret := DNSZones{} if _, _, err := p.sendQuery(context.Background(), fmt.Sprintf("%s/%s", p.apiURL, "zones"), "GET", nil, &domains); err != nil { return nil, err } if re == "" || re == "." { re = ".*" } matcher := rcache.Get(fmt.Sprintf("^%s$", re)) if matcher == nil { return nil, errors.New("invalid regexp") } for _, domain := range domains { if !matcher.MatchString(domain.Name) { continue } ret = append(ret, domain) } return ret, nil } // GetDomain find the domain where the record r should be // ex : www.example.org should be in the domain example.org, or org, or doesn't // exist and we return an error func (p *PowerDNS) GetDomain(r string) (string, error) { domains, err := p.ListZones("") if err != nil { return "", err } // Remove the final point of the record if necessary r = trimPoint(r) best := "" for _, domain := range domains { possible := trimPoint(domain.Name) if possible == r { return r, nil } if r == possible || strings.HasSuffix(r, "."+possible) && len(possible) > len(best) { best = possible } } if len(best) == 0 { return "", errors.New("Unknown domain") } // check there is no delegation down the road subs := strings.Split(strings.TrimSuffix(r, "."+best), ".") current := best rp := addPoint(r) for i := len(subs) - 1; i > 0; i-- { current = subs[i] + "." + current search, err := p.Search(fmt.Sprintf(current)) if err != nil { return best, nil } for _, entry := range search { if entry.IsRecord() && // ignore non record entry.Type == "NS" && // need a NS strings.HasSuffix(rp, "."+entry.Name) { return best, fmt.Errorf("%s is delegated to another server", r) } } } return best, nil }