pdns-auth-proxy/pdns_zones.go

175 lines
4.1 KiB
Go

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
}