175 lines
4.1 KiB
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
|
|
}
|