2016-08-23 08:31:50 +08:00
package normalize
import (
"net"
"strings"
"github.com/StackExchange/dnscontrol/models"
2017-05-26 02:25:39 +08:00
"github.com/StackExchange/dnscontrol/pkg/transform"
2017-04-20 03:13:28 +08:00
"github.com/StackExchange/dnscontrol/providers"
2016-09-28 02:28:09 +08:00
"github.com/miekg/dns"
"github.com/miekg/dns/dnsutil"
2017-06-11 21:30:12 +08:00
"github.com/pkg/errors"
2016-08-23 08:31:50 +08:00
)
// Returns false if target does not validate.
2017-03-21 04:20:02 +08:00
func checkIPv4 ( label string ) error {
2016-08-23 08:31:50 +08:00
if net . ParseIP ( label ) . To4 ( ) == nil {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "WARNING: target (%v) is not an IPv4 address" , label )
2016-08-23 08:31:50 +08:00
}
return nil
}
// Returns false if target does not validate.
2017-03-21 04:20:02 +08:00
func checkIPv6 ( label string ) error {
2016-08-23 08:31:50 +08:00
if net . ParseIP ( label ) . To16 ( ) == nil {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "WARNING: target (%v) is not an IPv6 address" , label )
2016-08-23 08:31:50 +08:00
}
return nil
}
2017-03-21 04:20:02 +08:00
// make sure target is valid reference for cnames, mx, etc.
func checkTarget ( target string ) error {
2016-12-17 04:10:27 +08:00
if target == "@" {
2016-08-23 08:31:50 +08:00
return nil
}
2016-12-17 04:10:27 +08:00
if len ( target ) < 1 {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "empty target" )
2016-08-23 08:31:50 +08:00
}
2017-06-11 21:31:26 +08:00
if strings . ContainsAny ( target , ` '" +,|!£$%&/()=?^*ç°§;:<>[]()@ ` ) {
2017-06-11 21:30:12 +08:00
return errors . Errorf ( "target (%v) includes invalid char" , target )
}
2016-08-23 08:31:50 +08:00
// If it containts a ".", it must end in a ".".
2016-12-17 04:10:27 +08:00
if strings . ContainsRune ( target , '.' ) && target [ len ( target ) - 1 ] != '.' {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "target (%v) must end with a (.) [https://stackexchange.github.io/dnscontrol/why-the-dot]" , target )
2016-08-23 08:31:50 +08:00
}
return nil
}
// validateRecordTypes list of valid rec.Type values. Returns true if this is a real DNS record type, false means it is a pseudo-type used internally.
2017-05-20 02:15:57 +08:00
func validateRecordTypes ( rec * models . RecordConfig , domain string , pTypes [ ] string ) error {
2017-03-21 04:20:02 +08:00
var validTypes = map [ string ] bool {
2016-08-23 08:31:50 +08:00
"A" : true ,
"AAAA" : true ,
"CNAME" : true ,
2017-07-26 02:59:40 +08:00
"CAA" : true ,
2017-09-15 21:03:29 +08:00
"TLSA" : true ,
2016-08-23 08:31:50 +08:00
"IMPORT_TRANSFORM" : false ,
"MX" : true ,
2017-07-20 03:53:40 +08:00
"SRV" : true ,
2016-08-23 08:31:50 +08:00
"TXT" : true ,
"NS" : true ,
2017-07-06 22:18:15 +08:00
"PTR" : true ,
2017-04-20 03:13:28 +08:00
"ALIAS" : false ,
2016-08-23 08:31:50 +08:00
}
2017-05-20 02:15:57 +08:00
_ , ok := validTypes [ rec . Type ]
if ! ok {
cType := providers . GetCustomRecordType ( rec . Type )
if cType == nil {
2018-03-20 05:18:58 +08:00
return errors . Errorf ( "Unsupported record type (%v) domain=%v name=%v" , rec . Type , domain , rec . GetLabel ( ) )
2017-05-20 02:15:57 +08:00
}
for _ , providerType := range pTypes {
if providerType != cType . Provider {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "Custom record type %s is not compatible with provider type %s" , rec . Type , providerType )
2017-05-20 02:15:57 +08:00
}
}
2018-01-10 01:53:16 +08:00
// it is ok. Lets replace the type with real type and add metadata to say we checked it
2017-05-20 02:15:57 +08:00
rec . Metadata [ "orig_custom_type" ] = rec . Type
if cType . RealType != "" {
rec . Type = cType . RealType
}
2016-08-23 08:31:50 +08:00
}
return nil
}
2017-03-21 04:20:02 +08:00
// underscores in names are often used erroneously. They are valid for dns records, but invalid for urls.
// here we list common records expected to have underscores. Anything else containing an underscore will print a warning.
2017-11-14 22:56:15 +08:00
var labelUnderscores = [ ] string { "_domainkey" , "_dmarc" , "_amazonses" , "_acme-challenge" }
2017-09-15 21:03:29 +08:00
2018-01-10 01:53:16 +08:00
// these record types may contain underscores
2017-09-15 21:03:29 +08:00
var rTypeUnderscores = [ ] string { "SRV" , "TLSA" , "TXT" }
2017-03-21 04:20:02 +08:00
2017-11-15 12:13:50 +08:00
func checkLabel ( label string , rType string , domain string , meta map [ string ] string ) error {
2017-03-21 04:20:02 +08:00
if label == "@" {
return nil
}
if len ( label ) < 1 {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "empty %s label in %s" , rType , domain )
2017-03-21 04:20:02 +08:00
}
if label [ len ( label ) - 1 ] == '.' {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "label %s.%s ends with a (.)" , label , domain )
2017-03-21 04:20:02 +08:00
}
2017-11-15 12:13:50 +08:00
if strings . HasSuffix ( label , domain ) {
if m := meta [ "skip_fqdn_check" ] ; m != "true" {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( ` label %s ends with domain name %s. Record names should not be fully qualified. Add { skip_fqdn_check:"true"} to this record if you really want to make %s.%s ` , label , domain , label , domain )
2017-11-15 12:13:50 +08:00
}
}
// check for underscores last
2017-09-15 21:03:29 +08:00
for _ , ex := range rTypeUnderscores {
if rType == ex {
return nil
2017-03-21 04:20:02 +08:00
}
2017-09-15 21:03:29 +08:00
}
for _ , ex := range labelUnderscores {
if strings . Contains ( label , ex ) {
return nil
2017-03-21 04:20:02 +08:00
}
}
2018-01-10 01:53:16 +08:00
// underscores are warnings
2017-09-15 21:03:29 +08:00
if strings . ContainsRune ( label , '_' ) {
2018-02-06 05:17:20 +08:00
return Warning { errors . Errorf ( "label %s.%s contains an underscore" , label , domain ) }
2017-09-15 21:03:29 +08:00
}
2017-11-15 12:13:50 +08:00
2017-03-21 04:20:02 +08:00
return nil
}
// checkTargets returns true if rec.Target is valid for the rec.Type.
func checkTargets ( rec * models . RecordConfig , domain string ) ( errs [ ] error ) {
2018-03-20 05:18:58 +08:00
label := rec . GetLabel ( )
target := rec . GetTargetField ( )
2016-08-23 08:31:50 +08:00
check := func ( e error ) {
if e != nil {
2018-03-20 05:18:58 +08:00
err := errors . Errorf ( "In %s %s.%s: %s" , rec . Type , rec . GetLabel ( ) , domain , e . Error ( ) )
2017-03-21 04:20:02 +08:00
if _ , ok := e . ( Warning ) ; ok {
err = Warning { err }
}
errs = append ( errs , err )
2016-08-23 08:31:50 +08:00
}
}
2017-08-05 03:26:29 +08:00
switch rec . Type { // #rtype_variations
2016-08-23 08:31:50 +08:00
case "A" :
2017-03-21 04:20:02 +08:00
check ( checkIPv4 ( target ) )
2016-08-23 08:31:50 +08:00
case "AAAA" :
2017-03-21 04:20:02 +08:00
check ( checkIPv6 ( target ) )
2016-08-23 08:31:50 +08:00
case "CNAME" :
2017-03-21 04:20:02 +08:00
check ( checkTarget ( target ) )
2017-04-20 03:13:28 +08:00
if label == "@" {
2018-02-06 05:17:20 +08:00
check ( errors . Errorf ( "cannot create CNAME record for bare domain" ) )
2017-04-20 03:13:28 +08:00
}
2016-08-23 08:31:50 +08:00
case "MX" :
2017-03-21 04:20:02 +08:00
check ( checkTarget ( target ) )
2016-08-23 08:31:50 +08:00
case "NS" :
2017-03-21 04:20:02 +08:00
check ( checkTarget ( target ) )
2016-12-17 04:10:27 +08:00
if label == "@" {
2018-02-06 05:17:20 +08:00
check ( errors . Errorf ( "cannot create NS record for bare domain. Use NAMESERVER instead" ) )
2016-12-17 04:10:27 +08:00
}
2017-07-06 22:18:15 +08:00
case "PTR" :
check ( checkTarget ( target ) )
2017-04-20 03:13:28 +08:00
case "ALIAS" :
check ( checkTarget ( target ) )
2017-07-20 03:53:40 +08:00
case "SRV" :
check ( checkTarget ( target ) )
2017-09-15 21:03:29 +08:00
case "TXT" , "IMPORT_TRANSFORM" , "CAA" , "TLSA" :
2016-08-23 08:31:50 +08:00
default :
2017-05-20 02:15:57 +08:00
if rec . Metadata [ "orig_custom_type" ] != "" {
2018-01-10 01:53:16 +08:00
// it is a valid custom type. We perform no validation on target
2017-05-20 02:15:57 +08:00
return
}
2018-02-06 05:17:20 +08:00
errs = append ( errs , errors . Errorf ( "checkTargets: Unimplemented record type (%v) domain=%v name=%v" ,
2018-03-20 05:18:58 +08:00
rec . Type , domain , rec . GetLabel ( ) ) )
2016-08-23 08:31:50 +08:00
}
return
}
2017-03-21 04:20:02 +08:00
func transformCNAME ( target , oldDomain , newDomain string ) string {
// Canonicalize. If it isn't a FQDN, add the newDomain.
result := dnsutil . AddOrigin ( target , oldDomain )
2016-08-23 08:31:50 +08:00
if dns . IsFqdn ( result ) {
result = result [ : len ( result ) - 1 ]
}
2017-03-21 04:20:02 +08:00
return dnsutil . AddOrigin ( result , newDomain ) + "."
2016-08-23 08:31:50 +08:00
}
// import_transform imports the records of one zone into another, modifying records along the way.
2017-03-21 04:20:02 +08:00
func importTransform ( srcDomain , dstDomain * models . DomainConfig , transforms [ ] transform . IpConversion , ttl uint32 ) error {
// Read srcDomain.Records, transform, and append to dstDomain.Records:
2016-08-23 08:31:50 +08:00
// 1. Skip any that aren't A or CNAMEs.
2017-03-21 04:20:02 +08:00
// 2. Append destDomainname to the end of the label.
// 3. For CNAMEs, append destDomainname to the end of the target.
2016-08-23 08:31:50 +08:00
// 4. For As, change the target as described the transforms.
2017-03-21 04:20:02 +08:00
for _ , rec := range srcDomain . Records {
2018-03-20 05:18:58 +08:00
if dstDomain . HasRecordTypeName ( rec . Type , rec . GetLabelFQDN ( ) ) {
2017-02-08 03:42:11 +08:00
continue
}
2016-09-28 02:28:09 +08:00
newRec := func ( ) * models . RecordConfig {
rec2 , _ := rec . Copy ( )
2018-03-20 05:18:58 +08:00
newlabel := rec2 . GetLabelFQDN ( )
rec2 . SetLabelFromFQDN ( newlabel , dstDomain . Name )
2016-09-29 02:45:59 +08:00
if ttl != 0 {
rec2 . TTL = ttl
}
2016-09-28 02:28:09 +08:00
return rec2
}
2017-08-05 03:26:29 +08:00
switch rec . Type { // #rtype_variations
2016-08-23 08:31:50 +08:00
case "A" :
2018-03-20 05:18:58 +08:00
trs , err := transform . TransformIPToList ( net . ParseIP ( rec . GetTargetField ( ) ) , transforms )
2016-08-23 08:31:50 +08:00
if err != nil {
2018-03-20 05:18:58 +08:00
return errors . Errorf ( "import_transform: TransformIP(%v, %v) returned err=%s" , rec . GetTargetField ( ) , transforms , err )
2016-09-28 02:28:09 +08:00
}
for _ , tr := range trs {
r := newRec ( )
2018-03-20 05:18:58 +08:00
r . SetTarget ( tr . String ( ) )
2017-03-21 04:20:02 +08:00
dstDomain . Records = append ( dstDomain . Records , r )
2016-08-23 08:31:50 +08:00
}
case "CNAME" :
2016-09-28 02:28:09 +08:00
r := newRec ( )
2018-03-20 05:18:58 +08:00
r . SetTarget ( transformCNAME ( r . GetTargetField ( ) , srcDomain . Name , dstDomain . Name ) )
2017-03-21 04:20:02 +08:00
dstDomain . Records = append ( dstDomain . Records , r )
2017-09-15 21:03:29 +08:00
case "MX" , "NS" , "SRV" , "TXT" , "CAA" , "TLSA" :
2016-08-23 08:31:50 +08:00
// Not imported.
continue
default :
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "import_transform: Unimplemented record type %v (%v)" ,
2018-03-20 05:18:58 +08:00
rec . Type , rec . GetLabel ( ) )
2016-08-23 08:31:50 +08:00
}
}
return nil
}
// deleteImportTransformRecords deletes any IMPORT_TRANSFORM records from a domain.
func deleteImportTransformRecords ( domain * models . DomainConfig ) {
for i := len ( domain . Records ) - 1 ; i >= 0 ; i -- {
rec := domain . Records [ i ]
if rec . Type == "IMPORT_TRANSFORM" {
domain . Records = append ( domain . Records [ : i ] , domain . Records [ i + 1 : ] ... )
}
}
}
2017-03-21 04:20:02 +08:00
// Warning is a wrapper around error that can be used to indicate it should not
// stop execution, but is still likely a problem.
type Warning struct {
error
}
2018-01-10 01:53:16 +08:00
// NormalizeAndValidateConfig performs and normalization and/or validation of the IR.
2016-08-23 08:31:50 +08:00
func NormalizeAndValidateConfig ( config * models . DNSConfig ) ( errs [ ] error ) {
2017-03-21 04:20:02 +08:00
for _ , domain := range config . Domains {
2017-05-20 02:15:57 +08:00
pTypes := [ ] string { }
2018-01-05 08:19:35 +08:00
txtMultiDissenters := [ ] string { }
2018-02-02 00:45:53 +08:00
for _ , provider := range domain . DNSProviderInstances {
pType := provider . ProviderType
2018-01-10 01:53:16 +08:00
// If NO_PURGE is in use, make sure this *isn't* a provider that *doesn't* support NO_PURGE.
2017-08-12 03:43:06 +08:00
if domain . KeepUnknown && providers . ProviderHasCabability ( pType , providers . CantUseNOPURGE ) {
2018-02-06 05:17:20 +08:00
errs = append ( errs , errors . Errorf ( "%s uses NO_PURGE which is not supported by %s(%s)" , domain . Name , provider . Name , pType ) )
2017-08-12 03:43:06 +08:00
}
2018-01-05 08:19:35 +08:00
// Record if any providers do not support TXTMulti:
if ! providers . ProviderHasCabability ( pType , providers . CanUseTXTMulti ) {
2018-02-02 00:45:53 +08:00
txtMultiDissenters = append ( txtMultiDissenters , provider . Name )
2018-01-05 08:19:35 +08:00
}
2017-05-20 02:15:57 +08:00
}
2016-08-23 08:31:50 +08:00
// Normalize Nameservers.
for _ , ns := range domain . Nameservers {
ns . Name = dnsutil . AddOrigin ( ns . Name , domain . Name )
ns . Name = strings . TrimRight ( ns . Name , "." )
}
// Normalize Records.
2018-01-05 08:19:35 +08:00
models . PostProcessRecords ( domain . Records )
2016-08-23 08:31:50 +08:00
for _ , rec := range domain . Records {
2017-01-12 03:38:07 +08:00
if rec . TTL == 0 {
rec . TTL = models . DefaultTTL
}
2016-08-23 08:31:50 +08:00
// Validate the unmodified inputs:
2017-05-20 02:15:57 +08:00
if err := validateRecordTypes ( rec , domain . Name , pTypes ) ; err != nil {
2016-08-23 08:31:50 +08:00
errs = append ( errs , err )
}
2018-03-20 05:18:58 +08:00
if err := checkLabel ( rec . GetLabel ( ) , rec . Type , domain . Name , rec . Metadata ) ; err != nil {
2017-03-21 04:20:02 +08:00
errs = append ( errs , err )
}
if errs2 := checkTargets ( rec , domain . Name ) ; errs2 != nil {
2016-08-23 08:31:50 +08:00
errs = append ( errs , errs2 ... )
}
// Canonicalize Targets.
if rec . Type == "CNAME" || rec . Type == "MX" || rec . Type == "NS" {
2018-03-20 05:18:58 +08:00
rec . SetTarget ( dnsutil . AddOrigin ( rec . GetTargetField ( ) , domain . Name + "." ) )
2017-06-06 02:57:32 +08:00
} else if rec . Type == "A" || rec . Type == "AAAA" {
2018-03-20 05:18:58 +08:00
rec . SetTarget ( net . ParseIP ( rec . GetTargetField ( ) ) . String ( ) )
2017-07-08 01:59:29 +08:00
} else if rec . Type == "PTR" {
var err error
2018-03-20 05:18:58 +08:00
var name string
if name , err = transform . PtrNameMagic ( rec . GetLabel ( ) , domain . Name ) ; err != nil {
2017-07-08 01:59:29 +08:00
errs = append ( errs , err )
}
2018-03-20 05:18:58 +08:00
rec . SetLabel ( name , domain . Name )
2017-07-26 02:59:40 +08:00
} else if rec . Type == "CAA" {
if rec . CaaTag != "issue" && rec . CaaTag != "issuewild" && rec . CaaTag != "iodef" {
2018-02-06 05:17:20 +08:00
errs = append ( errs , errors . Errorf ( "CAA tag %s is invalid" , rec . CaaTag ) )
2017-07-26 02:59:40 +08:00
}
2017-09-15 21:03:29 +08:00
} else if rec . Type == "TLSA" {
if rec . TlsaUsage < 0 || rec . TlsaUsage > 3 {
2018-02-06 05:17:20 +08:00
errs = append ( errs , errors . Errorf ( "TLSA Usage %d is invalid in record %s (domain %s)" ,
2018-03-20 05:18:58 +08:00
rec . TlsaUsage , rec . GetLabel ( ) , domain . Name ) )
2017-09-15 21:03:29 +08:00
}
if rec . TlsaSelector < 0 || rec . TlsaSelector > 1 {
2018-02-06 05:17:20 +08:00
errs = append ( errs , errors . Errorf ( "TLSA Selector %d is invalid in record %s (domain %s)" ,
2018-03-20 05:18:58 +08:00
rec . TlsaSelector , rec . GetLabel ( ) , domain . Name ) )
2017-09-15 21:03:29 +08:00
}
if rec . TlsaMatchingType < 0 || rec . TlsaMatchingType > 2 {
2018-02-06 05:17:20 +08:00
errs = append ( errs , errors . Errorf ( "TLSA MatchingType %d is invalid in record %s (domain %s)" ,
2018-03-20 05:18:58 +08:00
rec . TlsaMatchingType , rec . GetLabel ( ) , domain . Name ) )
2017-09-15 21:03:29 +08:00
}
2018-01-05 08:19:35 +08:00
} else if rec . Type == "TXT" && len ( txtMultiDissenters ) != 0 && len ( rec . TxtStrings ) > 1 {
// There are providers that don't support TXTMulti yet there is
// a TXT record with multiple strings:
errs = append ( errs ,
2018-02-06 05:17:20 +08:00
errors . Errorf ( "TXT records with multiple strings (label %v domain: %v) not supported by %s" ,
2018-03-20 05:18:58 +08:00
rec . GetLabel ( ) , domain . Name , strings . Join ( txtMultiDissenters , "," ) ) )
2016-08-23 08:31:50 +08:00
}
2017-09-15 21:03:29 +08:00
2016-08-23 08:31:50 +08:00
// Populate FQDN:
2018-03-22 21:29:55 +08:00
rec . SetLabel ( rec . GetLabel ( ) , domain . Name )
2016-08-23 08:31:50 +08:00
}
}
2017-09-30 03:30:36 +08:00
// SPF flattening
if ers := flattenSPFs ( config ) ; len ( ers ) > 0 {
errs = append ( errs , ers ... )
}
// Process IMPORT_TRANSFORM
2016-08-23 08:31:50 +08:00
for _ , domain := range config . Domains {
for _ , rec := range domain . Records {
if rec . Type == "IMPORT_TRANSFORM" {
table , err := transform . DecodeTransformTable ( rec . Metadata [ "transform_table" ] )
if err != nil {
errs = append ( errs , err )
continue
}
2018-03-20 05:18:58 +08:00
err = importTransform ( config . FindDomain ( rec . GetTargetField ( ) ) , domain , table , rec . TTL )
2016-08-23 08:31:50 +08:00
if err != nil {
errs = append ( errs , err )
}
}
}
}
// Clean up:
for _ , domain := range config . Domains {
deleteImportTransformRecords ( domain )
}
// Run record transforms
for _ , domain := range config . Domains {
if err := applyRecordTransforms ( domain ) ; err != nil {
errs = append ( errs , err )
}
}
2017-04-20 03:13:28 +08:00
2018-01-10 01:53:16 +08:00
// Check that CNAMES don't have to co-exist with any other records
2017-04-14 01:10:15 +08:00
for _ , d := range config . Domains {
errs = append ( errs , checkCNAMEs ( d ) ... )
}
2017-04-20 03:13:28 +08:00
2018-01-10 01:53:16 +08:00
// Check that if any aliases / ptr / etc.. are used in a domain, every provider for that domain supports them
2017-04-20 03:13:28 +08:00
for _ , d := range config . Domains {
2018-02-02 00:45:53 +08:00
err := checkProviderCapabilities ( d )
2017-04-20 03:13:28 +08:00
if err != nil {
2017-05-03 23:56:08 +08:00
errs = append ( errs , err )
2017-04-20 03:13:28 +08:00
}
}
2016-08-23 08:31:50 +08:00
return errs
}
2017-04-14 01:10:15 +08:00
func checkCNAMEs ( dc * models . DomainConfig ) ( errs [ ] error ) {
cnames := map [ string ] bool { }
for _ , r := range dc . Records {
if r . Type == "CNAME" {
2018-03-20 05:18:58 +08:00
if cnames [ r . GetLabel ( ) ] {
errs = append ( errs , errors . Errorf ( "Cannot have multiple CNAMEs with same name: %s" , r . GetLabelFQDN ( ) ) )
2017-04-14 01:10:15 +08:00
}
2018-03-20 05:18:58 +08:00
cnames [ r . GetLabel ( ) ] = true
2017-04-14 01:10:15 +08:00
}
}
for _ , r := range dc . Records {
2018-03-20 05:18:58 +08:00
if cnames [ r . GetLabel ( ) ] && r . Type != "CNAME" {
errs = append ( errs , errors . Errorf ( "Cannot have CNAME and %s record with same name: %s" , r . Type , r . GetLabelFQDN ( ) ) )
2017-04-14 01:10:15 +08:00
}
}
return
}
2018-02-02 00:45:53 +08:00
func checkProviderCapabilities ( dc * models . DomainConfig ) error {
2017-07-06 22:24:21 +08:00
types := [ ] struct {
rType string
cap providers . Capability
} {
{ "ALIAS" , providers . CanUseAlias } ,
{ "PTR" , providers . CanUsePTR } ,
2017-07-21 03:55:26 +08:00
{ "SRV" , providers . CanUseSRV } ,
2017-07-26 02:59:40 +08:00
{ "CAA" , providers . CanUseCAA } ,
2017-09-15 21:03:29 +08:00
{ "TLSA" , providers . CanUseTLSA } ,
2017-04-20 03:13:28 +08:00
}
2017-07-06 22:24:21 +08:00
for _ , ty := range types {
hasAny := false
for _ , r := range dc . Records {
if r . Type == ty . rType {
hasAny = true
2017-04-20 03:13:28 +08:00
break
}
}
2017-07-06 22:24:21 +08:00
if ! hasAny {
continue
}
2018-02-02 00:45:53 +08:00
for _ , provider := range dc . DNSProviderInstances {
if ! providers . ProviderHasCabability ( provider . ProviderType , ty . cap ) {
2018-02-06 05:17:20 +08:00
return errors . Errorf ( "Domain %s uses %s records, but DNS provider type %s does not support them" , dc . Name , ty . rType , provider . ProviderType )
2017-07-06 22:24:21 +08:00
}
}
2017-04-20 03:13:28 +08:00
}
return nil
}
2016-08-23 08:31:50 +08:00
func applyRecordTransforms ( domain * models . DomainConfig ) error {
for _ , rec := range domain . Records {
if rec . Type != "A" {
continue
}
tt , ok := rec . Metadata [ "transform" ]
if ! ok {
continue
}
table , err := transform . DecodeTransformTable ( tt )
if err != nil {
return err
}
2018-03-20 05:18:58 +08:00
ip := net . ParseIP ( rec . GetTargetField ( ) ) // ip already validated above
newIPs , err := transform . TransformIPToList ( net . ParseIP ( rec . GetTargetField ( ) ) , table )
2016-08-23 08:31:50 +08:00
if err != nil {
return err
}
for i , newIP := range newIPs {
if i == 0 && ! newIP . Equal ( ip ) {
2018-03-20 05:18:58 +08:00
rec . SetTarget ( newIP . String ( ) ) // replace target of first record if different
2016-08-23 08:31:50 +08:00
} else if i > 0 {
// any additional ips need identical records with the alternate ip added to the domain
copy , err := rec . Copy ( )
if err != nil {
return err
}
2018-03-20 05:18:58 +08:00
copy . SetTarget ( newIP . String ( ) )
2016-08-23 08:31:50 +08:00
domain . Records = append ( domain . Records , copy )
}
}
}
return nil
}