2016-08-23 08:31:50 +08:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/gob"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
2017-07-20 03:53:40 +08:00
|
|
|
"strings"
|
2016-08-23 08:31:50 +08:00
|
|
|
|
2017-05-26 02:25:39 +08:00
|
|
|
"github.com/StackExchange/dnscontrol/pkg/transform"
|
2016-12-17 04:10:27 +08:00
|
|
|
"github.com/miekg/dns"
|
2017-03-17 13:42:53 +08:00
|
|
|
"golang.org/x/net/idna"
|
2016-08-23 08:31:50 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
const DefaultTTL = uint32(300)
|
|
|
|
|
|
|
|
type DNSConfig struct {
|
|
|
|
Registrars []*RegistrarConfig `json:"registrars"`
|
2016-12-17 04:10:27 +08:00
|
|
|
DNSProviders []*DNSProviderConfig `json:"dns_providers"`
|
2016-08-23 08:31:50 +08:00
|
|
|
Domains []*DomainConfig `json:"domains"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (config *DNSConfig) FindDomain(query string) *DomainConfig {
|
|
|
|
for _, b := range config.Domains {
|
|
|
|
if b.Name == query {
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type RegistrarConfig struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Metadata json.RawMessage `json:"meta,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type DNSProviderConfig struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Metadata json.RawMessage `json:"meta,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// RecordConfig stores a DNS record.
|
|
|
|
// Providers are responsible for validating or normalizing the data
|
|
|
|
// that goes into a RecordConfig.
|
|
|
|
// If you update Name, you have to update NameFQDN and vice-versa.
|
|
|
|
//
|
|
|
|
// Name:
|
|
|
|
// This is the shortname i.e. the NameFQDN without the origin suffix.
|
|
|
|
// It should never have a trailing "."
|
|
|
|
// It should never be null. It should store It "@", not the apex domain, not null, etc.
|
|
|
|
// It shouldn't end with the domain origin. If the origin is "foo.com." then
|
|
|
|
// if Name == "foo.com" then that literally means "foo.com.foo.com." is
|
|
|
|
// the intended FQDN.
|
|
|
|
// NameFQDN:
|
|
|
|
// This is the FQDN version of Name.
|
|
|
|
// It should never have a trailiing ".".
|
2017-12-20 23:25:23 +08:00
|
|
|
// Valid types:
|
2017-12-21 22:43:21 +08:00
|
|
|
// Official:
|
|
|
|
// A
|
|
|
|
// AAAA
|
|
|
|
// ANAME
|
|
|
|
// CAA
|
|
|
|
// CNAME
|
|
|
|
// MX
|
|
|
|
// NS
|
|
|
|
// PTR
|
|
|
|
// SRV
|
|
|
|
// TLSA
|
|
|
|
// TXT
|
|
|
|
// Pseudo-Types:
|
|
|
|
// ALIAs
|
|
|
|
// CF_REDIRECT
|
|
|
|
// CF_TEMP_REDIRECT
|
|
|
|
// FRAME
|
|
|
|
// IMPORT_TRANSFORM
|
|
|
|
// NAMESERVER
|
|
|
|
// NO_PURGE
|
|
|
|
// PAGE_RULE
|
|
|
|
// PURGE
|
|
|
|
// URL
|
|
|
|
// URL301
|
2016-08-23 08:31:50 +08:00
|
|
|
type RecordConfig struct {
|
2017-09-15 21:03:29 +08:00
|
|
|
Type string `json:"type"`
|
|
|
|
Name string `json:"name"` // The short name. See below.
|
|
|
|
Target string `json:"target"` // If a name, must end with "."
|
|
|
|
TTL uint32 `json:"ttl,omitempty"`
|
|
|
|
Metadata map[string]string `json:"meta,omitempty"`
|
2017-12-21 22:43:21 +08:00
|
|
|
NameFQDN string `json:"-"` // Must end with ".$origin". See below.
|
|
|
|
MxPreference uint16 `json:"mxpreference,omitempty"`
|
2017-09-15 21:03:29 +08:00
|
|
|
SrvPriority uint16 `json:"srvpriority,omitempty"`
|
|
|
|
SrvWeight uint16 `json:"srvweight,omitempty"`
|
|
|
|
SrvPort uint16 `json:"srvport,omitempty"`
|
|
|
|
CaaTag string `json:"caatag,omitempty"`
|
|
|
|
CaaFlag uint8 `json:"caaflag,omitempty"`
|
|
|
|
TlsaUsage uint8 `json:"tlsausage,omitempty"`
|
|
|
|
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
|
|
|
|
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
|
2018-01-05 08:19:35 +08:00
|
|
|
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
|
2016-08-23 08:31:50 +08:00
|
|
|
|
2017-07-26 02:59:40 +08:00
|
|
|
CombinedTarget bool `json:"-"`
|
2017-07-21 06:59:09 +08:00
|
|
|
|
2017-01-12 03:38:07 +08:00
|
|
|
Original interface{} `json:"-"` // Store pointer to provider-specific record object. Used in diffing.
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
2017-01-12 03:38:07 +08:00
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
func (r *RecordConfig) String() (content string) {
|
2017-07-21 06:59:09 +08:00
|
|
|
if r.CombinedTarget {
|
|
|
|
return r.Target
|
|
|
|
}
|
|
|
|
|
2017-07-22 04:52:54 +08:00
|
|
|
content = fmt.Sprintf("%s %s %s %d", r.Type, r.NameFQDN, r.Target, r.TTL)
|
2017-08-05 03:26:29 +08:00
|
|
|
switch r.Type { // #rtype_variations
|
2017-11-08 06:12:17 +08:00
|
|
|
case "A", "AAAA", "CNAME", "NS", "PTR", "TXT":
|
2017-08-05 03:26:29 +08:00
|
|
|
// Nothing special.
|
2017-07-20 03:53:40 +08:00
|
|
|
case "MX":
|
2017-11-08 06:12:17 +08:00
|
|
|
content += fmt.Sprintf(" pref=%d", r.MxPreference)
|
2017-07-20 03:53:40 +08:00
|
|
|
case "SOA":
|
|
|
|
content = fmt.Sprintf("%s %s %s %d", r.Type, r.Name, r.Target, r.TTL)
|
2017-11-08 06:12:17 +08:00
|
|
|
case "SRV":
|
|
|
|
content += fmt.Sprintf(" srvpriority=%d srvweight=%d srvport=%d", r.SrvPriority, r.SrvWeight, r.SrvPort)
|
|
|
|
case "TLSA":
|
|
|
|
content += fmt.Sprintf(" tlsausage=%d tlsaselector=%d tlsamatchingtype=%d", r.TlsaUsage, r.TlsaSelector, r.TlsaMatchingType)
|
2017-07-26 02:59:40 +08:00
|
|
|
case "CAA":
|
|
|
|
content += fmt.Sprintf(" caatag=%s caaflag=%d", r.CaaTag, r.CaaFlag)
|
2017-07-20 03:53:40 +08:00
|
|
|
default:
|
2017-08-05 03:26:29 +08:00
|
|
|
msg := fmt.Sprintf("rc.String rtype %v unimplemented", r.Type)
|
|
|
|
panic(msg)
|
|
|
|
// We panic so that we quickly find any switch statements
|
|
|
|
// that have not been updated for a new RR type.
|
2017-01-12 03:38:07 +08:00
|
|
|
}
|
|
|
|
for k, v := range r.Metadata {
|
|
|
|
content += fmt.Sprintf(" %s=%s", k, v)
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
2017-01-12 03:38:07 +08:00
|
|
|
return content
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
// Content combines Target and other fields into one string.
|
|
|
|
func (r *RecordConfig) Content() string {
|
2017-07-21 06:59:09 +08:00
|
|
|
if r.CombinedTarget {
|
|
|
|
return r.Target
|
|
|
|
}
|
2017-07-20 03:53:40 +08:00
|
|
|
|
|
|
|
// If this is a pseudo record, just return the target.
|
|
|
|
if _, ok := dns.StringToType[r.Type]; !ok {
|
|
|
|
return r.Target
|
|
|
|
}
|
|
|
|
|
|
|
|
// We cheat by converting to a dns.RR and use the String() function.
|
|
|
|
// Sadly that function always includes a header, which we must strip out.
|
|
|
|
// TODO(tlim): Request the dns project add a function that returns
|
|
|
|
// the string without the header.
|
|
|
|
rr := r.ToRR()
|
|
|
|
header := rr.Header().String()
|
|
|
|
full := rr.String()
|
|
|
|
if !strings.HasPrefix(full, header) {
|
|
|
|
panic("dns.Hdr.String() not acting as we expect")
|
|
|
|
}
|
|
|
|
return full[len(header):]
|
|
|
|
}
|
|
|
|
|
|
|
|
// MergeToTarget combines "extra" fields into .Target, and zeros the merged fields.
|
|
|
|
func (r *RecordConfig) MergeToTarget() {
|
2017-07-21 06:59:09 +08:00
|
|
|
if r.CombinedTarget {
|
|
|
|
pm := strings.Join([]string{"MergeToTarget: Already collapsed: ", r.Name, r.Target}, " ")
|
|
|
|
panic(pm)
|
|
|
|
}
|
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
// Merge "extra" fields into the Target.
|
|
|
|
r.Target = r.Content()
|
2016-08-23 08:31:50 +08:00
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
// Zap any fields that may have been merged.
|
|
|
|
r.MxPreference = 0
|
|
|
|
r.SrvPriority = 0
|
|
|
|
r.SrvWeight = 0
|
|
|
|
r.SrvPort = 0
|
2017-07-26 02:59:40 +08:00
|
|
|
r.CaaFlag = 0
|
|
|
|
r.CaaTag = ""
|
2017-09-15 21:03:29 +08:00
|
|
|
r.TlsaUsage = 0
|
|
|
|
r.TlsaMatchingType = 0
|
|
|
|
r.TlsaSelector = 0
|
2017-07-21 06:59:09 +08:00
|
|
|
|
|
|
|
r.CombinedTarget = true
|
2017-07-20 03:53:40 +08:00
|
|
|
}
|
2016-08-23 08:31:50 +08:00
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
/// Convert RecordConfig -> dns.RR.
|
|
|
|
func (rc *RecordConfig) ToRR() dns.RR {
|
2016-08-23 08:31:50 +08:00
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
// Don't call this on fake types.
|
|
|
|
rdtype, ok := dns.StringToType[rc.Type]
|
2016-08-23 08:31:50 +08:00
|
|
|
if !ok {
|
2017-07-20 03:53:40 +08:00
|
|
|
log.Fatalf("No such DNS type as (%#v)\n", rc.Type)
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
// Magicallly create an RR of the correct type.
|
|
|
|
rr := dns.TypeToRR[rdtype]()
|
|
|
|
|
|
|
|
// Fill in the header.
|
|
|
|
rr.Header().Name = rc.NameFQDN + "."
|
|
|
|
rr.Header().Rrtype = rdtype
|
|
|
|
rr.Header().Class = dns.ClassINET
|
|
|
|
rr.Header().Ttl = rc.TTL
|
|
|
|
if rc.TTL == 0 {
|
|
|
|
rr.Header().Ttl = DefaultTTL
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
// Fill in the data.
|
2017-08-05 03:26:29 +08:00
|
|
|
switch rdtype { // #rtype_variations
|
2017-07-20 03:53:40 +08:00
|
|
|
case dns.TypeA:
|
|
|
|
rr.(*dns.A).A = net.ParseIP(rc.Target)
|
2017-07-21 04:08:28 +08:00
|
|
|
case dns.TypeAAAA:
|
|
|
|
rr.(*dns.AAAA).AAAA = net.ParseIP(rc.Target)
|
2017-07-20 03:53:40 +08:00
|
|
|
case dns.TypeCNAME:
|
|
|
|
rr.(*dns.CNAME).Target = rc.Target
|
|
|
|
case dns.TypePTR:
|
|
|
|
rr.(*dns.PTR).Ptr = rc.Target
|
2016-08-23 08:31:50 +08:00
|
|
|
case dns.TypeMX:
|
2017-07-20 03:53:40 +08:00
|
|
|
rr.(*dns.MX).Preference = rc.MxPreference
|
|
|
|
rr.(*dns.MX).Mx = rc.Target
|
|
|
|
case dns.TypeNS:
|
|
|
|
rr.(*dns.NS).Ns = rc.Target
|
|
|
|
case dns.TypeSOA:
|
|
|
|
t := strings.Replace(rc.Target, `\ `, ` `, -1)
|
|
|
|
parts := strings.Fields(t)
|
|
|
|
rr.(*dns.SOA).Ns = parts[0]
|
|
|
|
rr.(*dns.SOA).Mbox = parts[1]
|
|
|
|
rr.(*dns.SOA).Serial = atou32(parts[2])
|
|
|
|
rr.(*dns.SOA).Refresh = atou32(parts[3])
|
|
|
|
rr.(*dns.SOA).Retry = atou32(parts[4])
|
|
|
|
rr.(*dns.SOA).Expire = atou32(parts[5])
|
|
|
|
rr.(*dns.SOA).Minttl = atou32(parts[6])
|
|
|
|
case dns.TypeSRV:
|
|
|
|
rr.(*dns.SRV).Priority = rc.SrvPriority
|
|
|
|
rr.(*dns.SRV).Weight = rc.SrvWeight
|
|
|
|
rr.(*dns.SRV).Port = rc.SrvPort
|
|
|
|
rr.(*dns.SRV).Target = rc.Target
|
2017-07-26 02:59:40 +08:00
|
|
|
case dns.TypeCAA:
|
|
|
|
rr.(*dns.CAA).Flag = rc.CaaFlag
|
|
|
|
rr.(*dns.CAA).Tag = rc.CaaTag
|
|
|
|
rr.(*dns.CAA).Value = rc.Target
|
2017-09-15 21:03:29 +08:00
|
|
|
case dns.TypeTLSA:
|
|
|
|
rr.(*dns.TLSA).Usage = rc.TlsaUsage
|
|
|
|
rr.(*dns.TLSA).MatchingType = rc.TlsaMatchingType
|
|
|
|
rr.(*dns.TLSA).Selector = rc.TlsaSelector
|
|
|
|
rr.(*dns.TLSA).Certificate = rc.Target
|
2016-08-23 08:31:50 +08:00
|
|
|
case dns.TypeTXT:
|
2018-01-05 08:19:35 +08:00
|
|
|
rr.(*dns.TXT).Txt = rc.TxtStrings
|
2016-08-23 08:31:50 +08:00
|
|
|
default:
|
2017-07-20 03:53:40 +08:00
|
|
|
panic(fmt.Sprintf("ToRR: Unimplemented rtype %v", rc.Type))
|
2017-08-05 03:26:29 +08:00
|
|
|
// We panic so that we quickly find any switch statements
|
|
|
|
// that have not been updated for a new RR type.
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
return rr
|
|
|
|
}
|
2016-08-23 08:31:50 +08:00
|
|
|
|
2017-07-20 03:53:40 +08:00
|
|
|
func atou32(s string) uint32 {
|
|
|
|
i64, err := strconv.ParseInt(s, 10, 32)
|
2016-08-23 08:31:50 +08:00
|
|
|
if err != nil {
|
2017-07-20 03:53:40 +08:00
|
|
|
panic(fmt.Sprintf("atou32 failed (%v) (err=%v", s, err))
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
2017-07-20 03:53:40 +08:00
|
|
|
return uint32(i64)
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
|
|
|
|
2017-09-13 23:49:15 +08:00
|
|
|
type Records []*RecordConfig
|
|
|
|
|
|
|
|
func (r Records) Grouped() map[RecordKey]Records {
|
|
|
|
groups := map[RecordKey]Records{}
|
|
|
|
for _, rec := range r {
|
|
|
|
groups[rec.Key()] = append(groups[rec.Key()], rec)
|
|
|
|
}
|
|
|
|
return groups
|
|
|
|
}
|
|
|
|
|
2018-01-05 08:19:35 +08:00
|
|
|
// PostProcessRecords does any post-processing of the downloaded DNS records.
|
|
|
|
func PostProcessRecords(recs []*RecordConfig) {
|
|
|
|
Downcase(recs)
|
|
|
|
fixTxt(recs)
|
|
|
|
}
|
|
|
|
|
2017-11-08 06:12:17 +08:00
|
|
|
// Downcase converts all labels and targets to lowercase in a list of RecordConfig.
|
|
|
|
func Downcase(recs []*RecordConfig) {
|
|
|
|
for _, r := range recs {
|
|
|
|
r.Name = strings.ToLower(r.Name)
|
|
|
|
r.NameFQDN = strings.ToLower(r.NameFQDN)
|
|
|
|
switch r.Type {
|
|
|
|
case "ANAME", "CNAME", "MX", "NS", "PTR":
|
|
|
|
r.Target = strings.ToLower(r.Target)
|
2017-11-08 23:25:10 +08:00
|
|
|
case "A", "AAAA", "ALIAS", "CAA", "IMPORT_TRANSFORM", "SRV", "TLSA", "TXT", "SOA", "CF_REDIRECT", "CF_TEMP_REDIRECT":
|
2017-11-08 06:12:17 +08:00
|
|
|
// Do nothing.
|
|
|
|
default:
|
2017-11-08 23:25:10 +08:00
|
|
|
// TODO: we'd like to panic here, but custom record types complicate things.
|
2017-11-08 06:12:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-01-05 08:19:35 +08:00
|
|
|
// fixTxt fixes TXT records generated by providers that do not understand CanUseTXTMulti.
|
|
|
|
func fixTxt(recs []*RecordConfig) {
|
|
|
|
for _, r := range recs {
|
|
|
|
if r.Type == "TXT" {
|
|
|
|
if len(r.TxtStrings) == 0 {
|
|
|
|
r.TxtStrings = []string{r.Target}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-13 23:49:15 +08:00
|
|
|
type RecordKey struct {
|
|
|
|
Name string
|
|
|
|
Type string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RecordConfig) Key() RecordKey {
|
|
|
|
return RecordKey{r.Name, r.Type}
|
|
|
|
}
|
|
|
|
|
2016-08-23 08:31:50 +08:00
|
|
|
type Nameserver struct {
|
|
|
|
Name string `json:"name"` // Normalized to a FQDN with NO trailing "."
|
|
|
|
Target string `json:"target"`
|
|
|
|
}
|
|
|
|
|
2016-12-17 04:10:27 +08:00
|
|
|
func StringsToNameservers(nss []string) []*Nameserver {
|
|
|
|
nservers := []*Nameserver{}
|
|
|
|
for _, ns := range nss {
|
|
|
|
nservers = append(nservers, &Nameserver{Name: ns})
|
|
|
|
}
|
|
|
|
return nservers
|
|
|
|
}
|
|
|
|
|
2016-08-23 08:31:50 +08:00
|
|
|
type DomainConfig struct {
|
2016-12-17 04:10:27 +08:00
|
|
|
Name string `json:"name"` // NO trailing "."
|
|
|
|
Registrar string `json:"registrar"`
|
|
|
|
DNSProviders map[string]int `json:"dnsProviders"`
|
|
|
|
Metadata map[string]string `json:"meta,omitempty"`
|
2017-09-13 23:49:15 +08:00
|
|
|
Records Records `json:"records"`
|
2016-12-17 04:10:27 +08:00
|
|
|
Nameservers []*Nameserver `json:"nameservers,omitempty"`
|
2017-07-21 05:41:15 +08:00
|
|
|
KeepUnknown bool `json:"keepunknown,omitempty"`
|
2016-08-23 08:31:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (dc *DomainConfig) Copy() (*DomainConfig, error) {
|
|
|
|
newDc := &DomainConfig{}
|
|
|
|
err := copyObj(dc, newDc)
|
|
|
|
return newDc, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RecordConfig) Copy() (*RecordConfig, error) {
|
|
|
|
newR := &RecordConfig{}
|
|
|
|
err := copyObj(r, newR)
|
|
|
|
return newR, err
|
|
|
|
}
|
|
|
|
|
2017-03-17 13:42:53 +08:00
|
|
|
//Punycode will convert all records to punycode format.
|
|
|
|
//It will encode:
|
|
|
|
//- Name
|
|
|
|
//- NameFQDN
|
|
|
|
//- Target (CNAME and MX only)
|
|
|
|
func (dc *DomainConfig) Punycode() error {
|
|
|
|
var err error
|
|
|
|
for _, rec := range dc.Records {
|
|
|
|
rec.Name, err = idna.ToASCII(rec.Name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rec.NameFQDN, err = idna.ToASCII(rec.NameFQDN)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-08-05 03:26:29 +08:00
|
|
|
switch rec.Type { // #rtype_variations
|
2017-10-24 00:54:31 +08:00
|
|
|
case "ALIAS", "MX", "NS", "CNAME", "PTR", "SRV", "URL", "URL301", "FRAME":
|
2017-03-17 13:42:53 +08:00
|
|
|
rec.Target, err = idna.ToASCII(rec.Target)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-09-15 21:03:29 +08:00
|
|
|
case "A", "AAAA", "CAA", "TXT", "TLSA":
|
2017-08-05 03:26:29 +08:00
|
|
|
// Nothing to do.
|
|
|
|
default:
|
|
|
|
msg := fmt.Sprintf("Punycode rtype %v unimplemented", rec.Type)
|
|
|
|
panic(msg)
|
|
|
|
// We panic so that we quickly find any switch statements
|
|
|
|
// that have not been updated for a new RR type.
|
2017-03-17 13:42:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-28 06:03:01 +08:00
|
|
|
// CombineMXs will merge the priority into the target field for all mx records.
|
|
|
|
// Useful for providers that desire them as one field.
|
|
|
|
func (dc *DomainConfig) CombineMXs() {
|
|
|
|
for _, rec := range dc.Records {
|
|
|
|
if rec.Type == "MX" {
|
2017-07-21 06:59:09 +08:00
|
|
|
if rec.CombinedTarget {
|
|
|
|
pm := strings.Join([]string{"CombineMXs: Already collapsed: ", rec.Name, rec.Target}, " ")
|
|
|
|
panic(pm)
|
|
|
|
}
|
2017-07-20 03:53:40 +08:00
|
|
|
rec.Target = fmt.Sprintf("%d %s", rec.MxPreference, rec.Target)
|
|
|
|
rec.MxPreference = 0
|
2017-07-21 06:59:09 +08:00
|
|
|
rec.CombinedTarget = true
|
2017-03-28 06:03:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-06 01:21:57 +08:00
|
|
|
// SplitCombinedMxValue splits a combined MX preference and target into
|
|
|
|
// separate entities, i.e. splitting "10 aspmx2.googlemail.com."
|
|
|
|
// into "10" and "aspmx2.googlemail.com.".
|
|
|
|
func SplitCombinedMxValue(s string) (preference uint16, target string, err error) {
|
|
|
|
parts := strings.Fields(s)
|
|
|
|
|
|
|
|
if len(parts) != 2 {
|
|
|
|
return 0, "", fmt.Errorf("MX value %#v contains too many fields", s)
|
|
|
|
}
|
|
|
|
|
|
|
|
n64, err := strconv.ParseUint(parts[0], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return 0, "", fmt.Errorf("MX preference %#v does not fit into a uint16", parts[0])
|
|
|
|
}
|
|
|
|
return uint16(n64), parts[1], nil
|
|
|
|
}
|
|
|
|
|
2017-08-29 22:10:50 +08:00
|
|
|
// CombineSRVs will merge the priority, weight, and port into the target field for all srv records.
|
|
|
|
// Useful for providers that desire them as one field.
|
|
|
|
func (dc *DomainConfig) CombineSRVs() {
|
|
|
|
for _, rec := range dc.Records {
|
|
|
|
if rec.Type == "SRV" {
|
|
|
|
if rec.CombinedTarget {
|
|
|
|
pm := strings.Join([]string{"CombineSRVs: Already collapsed: ", rec.Name, rec.Target}, " ")
|
|
|
|
panic(pm)
|
|
|
|
}
|
|
|
|
rec.Target = fmt.Sprintf("%d %d %d %s", rec.SrvPriority, rec.SrvWeight, rec.SrvPort, rec.Target)
|
|
|
|
rec.CombinedTarget = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//SplitCombinedSrvValue splits a combined SRV priority, weight, port and target into
|
|
|
|
//separate entities, some DNS providers want "5" "10" 15" and "foo.com.",
|
|
|
|
//while other providers want "5 10 15 foo.com.".
|
|
|
|
func SplitCombinedSrvValue(s string) (priority, weight, port uint16, target string, err error) {
|
|
|
|
parts := strings.Fields(s)
|
|
|
|
|
|
|
|
if len(parts) != 4 {
|
|
|
|
return 0, 0, 0, "", fmt.Errorf("SRV value %#v contains too many fields", s)
|
|
|
|
}
|
|
|
|
|
|
|
|
priorityconv, err := strconv.ParseInt(parts[0], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, 0, "", fmt.Errorf("Priority %#v does not fit into a uint16", parts[0])
|
|
|
|
}
|
|
|
|
weightconv, err := strconv.ParseInt(parts[1], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, 0, "", fmt.Errorf("Weight %#v does not fit into a uint16", parts[0])
|
|
|
|
}
|
|
|
|
portconv, err := strconv.ParseInt(parts[2], 10, 16)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, 0, "", fmt.Errorf("Port %#v does not fit into a uint16", parts[0])
|
|
|
|
}
|
|
|
|
return uint16(priorityconv), uint16(weightconv), uint16(portconv), parts[3], nil
|
|
|
|
}
|
|
|
|
|
2017-12-22 20:10:29 +08:00
|
|
|
// CombineCAAs will merge the tags and flags into the target field for all CAA records.
|
|
|
|
// Useful for providers that desire them as one field.
|
|
|
|
func (dc *DomainConfig) CombineCAAs() {
|
|
|
|
for _, rec := range dc.Records {
|
|
|
|
if rec.Type == "CAA" {
|
|
|
|
if rec.CombinedTarget {
|
|
|
|
pm := strings.Join([]string{"CombineCAAs: Already collapsed: ", rec.Name, rec.Target}, " ")
|
|
|
|
panic(pm)
|
|
|
|
}
|
|
|
|
rec.Target = rec.Content()
|
|
|
|
rec.CombinedTarget = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-20 23:25:23 +08:00
|
|
|
func SplitCombinedCaaValue(s string) (tag string, flag uint8, value string, err error) {
|
|
|
|
|
|
|
|
splitData := strings.SplitN(s, " ", 3)
|
|
|
|
if len(splitData) != 3 {
|
|
|
|
err = fmt.Errorf("Unexpected data for CAA record returned by Vultr")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lflag, err := strconv.ParseUint(splitData[0], 10, 8)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
flag = uint8(lflag)
|
|
|
|
|
|
|
|
tag = splitData[1]
|
|
|
|
|
|
|
|
value = splitData[2]
|
|
|
|
if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
|
|
|
|
value = value[1 : len(value)-1]
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`) {
|
|
|
|
value = value[1 : len(value)-1]
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-23 08:31:50 +08:00
|
|
|
func copyObj(input interface{}, output interface{}) error {
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
enc := gob.NewEncoder(buf)
|
|
|
|
dec := gob.NewDecoder(buf)
|
|
|
|
if err := enc.Encode(input); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := dec.Decode(output); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dc *DomainConfig) HasRecordTypeName(rtype, name string) bool {
|
|
|
|
for _, r := range dc.Records {
|
|
|
|
if r.Type == rtype && r.Name == name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-04-14 00:19:51 +08:00
|
|
|
func (dc *DomainConfig) Filter(f func(r *RecordConfig) bool) {
|
|
|
|
recs := []*RecordConfig{}
|
|
|
|
for _, r := range dc.Records {
|
|
|
|
if f(r) {
|
|
|
|
recs = append(recs, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dc.Records = recs
|
|
|
|
}
|
|
|
|
|
2016-08-23 08:31:50 +08:00
|
|
|
func InterfaceToIP(i interface{}) (net.IP, error) {
|
|
|
|
switch v := i.(type) {
|
|
|
|
case float64:
|
|
|
|
u := uint32(v)
|
|
|
|
return transform.UintToIP(u), nil
|
|
|
|
case string:
|
|
|
|
if ip := net.ParseIP(v); ip != nil {
|
|
|
|
return ip, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("%s is not a valid ip address", v)
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("Cannot convert type %s to ip.", reflect.TypeOf(i))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Correction is anything that can be run. Implementation is up to the specific provider.
|
|
|
|
type Correction struct {
|
|
|
|
F func() error `json:"-"`
|
|
|
|
Msg string
|
|
|
|
}
|