mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-02-23 07:03:01 +08:00
Co-authored-by: nyanotech <33802077+nyanotech@users.noreply.github.com> Co-authored-by: Jakob Ackermann <das7pad@outlook.com> Co-authored-by: Brice Figureau <brice@figureau.eu> Co-authored-by: Jeffrey Cafferata <jeffrey@jcid.nl> Co-authored-by: imlonghao <git@imlonghao.com>
151 lines
5.3 KiB
Go
151 lines
5.3 KiB
Go
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// originDotWarning is set to true after the warning is printed. We don't want to print it multiple times.
|
|
var originDotWarning = false
|
|
|
|
// PopulateFromStringFunc populates a RecordConfig by parsing a common RFC1035-like format.
|
|
//
|
|
// rtype: the resource record type (rtype)
|
|
// contents: a string that contains all parameters of the record's rdata (see below)
|
|
// txtFn: If rtype == "TXT", this function is used pre-process the contents. nil will simply store the contents unchanged.
|
|
//
|
|
// The "contents" field is the format used in RFC1035 zonefiles. It is the text
|
|
// after the rtype in a line of a zonefile.
|
|
// For example, in the line: foo IN MX 10 mx.example.com.
|
|
// "contents" includes everything after the "MX" and any whitespace.
|
|
//
|
|
// Typical values for txtFn include:
|
|
//
|
|
// nil: no parsing required.
|
|
// txtutil.ParseQuoted: Parse using @TomOnTime's interpretation of RFC1035.
|
|
// txtutil.ParseCombined: Parse using @miekg's interpretation of RFC1035.
|
|
//
|
|
// Many providers deliver record data in this format or something close to it.
|
|
// This function is provided to reduce the amount of duplicate code across
|
|
// providers. If a particular rtype is not handled as a particular provider
|
|
// expects, simply handle it beforehand as a special case.
|
|
//
|
|
// Example 1: Normal use.
|
|
//
|
|
// rtype := FILL_IN_RTYPE
|
|
// rc := &models.RecordConfig{Type: rtype, TTL: FILL_IN_TTL}
|
|
// rc.SetLabelFromFQDN(FILL_IN_NAME, origin)
|
|
// rc.Original = FILL_IN_ORIGINAL // The raw data received from provider (if needed later)
|
|
// if err = rc.PopulateFromStringFunc(rtype, target, origin, nil); err != nil {
|
|
// return nil, fmt.Errorf("unparsable record type=%q received from PROVDER_NAME: %w", rtype, err)
|
|
// }
|
|
// return rc, nil
|
|
//
|
|
// Example 2: Use your own MX parser.
|
|
//
|
|
// rtype := FILL_IN_RTYPE
|
|
// rc := &models.RecordConfig{Type: rtype, TTL: FILL_IN_TTL}
|
|
// rc.SetLabelFromFQDN(FILL_IN_NAME, origin)
|
|
// rc.Original = FILL_IN_ORIGINAL // The raw data received from provider (if needed later)
|
|
// switch rtype {
|
|
// case "MX":
|
|
// // MX priority in a separate field.
|
|
// err = rc.SetTargetMX(cr.Priority, target)
|
|
// default:
|
|
// err = rc.PopulateFromString(rtype, target, origin)
|
|
// }
|
|
// if err != nil {
|
|
// return nil, fmt.Errorf("unparsable record type=%q received from PROVDER_NAME: %w", rtype, err)
|
|
// }
|
|
// return rc, nil
|
|
func (rc *RecordConfig) PopulateFromStringFunc(rtype, contents, origin string, txtFn func(s string) (string, error)) error {
|
|
//fmt.Printf("DEBUG: PopulateFromStringFunc(%q, %q, %q)\n", rtype, contents, origin)
|
|
if rc.Type != "" && rc.Type != rtype {
|
|
return fmt.Errorf("assertion failed: rtype already set (%s) (%s)", rtype, rc.Type)
|
|
}
|
|
|
|
if strings.HasSuffix(origin, ".") {
|
|
origin = origin[:len(origin)-1]
|
|
if !originDotWarning {
|
|
fmt.Printf("WARNING: origin %q ends with a dot. This indicates a bug in the provider. Please file a bug report\n", origin)
|
|
}
|
|
originDotWarning = true
|
|
// "origin" shouldn't end in a "." but older code would allow it.
|
|
// Hopefully this warning will help us find broken providers.
|
|
}
|
|
|
|
if IsTypeUpgraded(rtype) {
|
|
var err error
|
|
switch rtype {
|
|
case "A":
|
|
if rdata, err := ParseA([]string{contents}, origin); err == nil {
|
|
return RecordUpdateFields(rc, rdata, nil)
|
|
}
|
|
case "MX":
|
|
if rdata, err := ParseMX(strings.Fields(contents), origin); err == nil {
|
|
return RecordUpdateFields(rc, rdata, nil)
|
|
}
|
|
case "CNAME":
|
|
if rdata, err := ParseCNAME(strings.Fields(contents), origin); err == nil {
|
|
return RecordUpdateFields(rc, rdata, nil)
|
|
}
|
|
case "SRV":
|
|
if rdata, err := ParseSRV(strings.Fields(contents), origin); err == nil {
|
|
return RecordUpdateFields(rc, rdata, nil)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch rc.Type = rtype; rtype { // #rtype_variations
|
|
case "AAAA":
|
|
ip := net.ParseIP(contents)
|
|
if ip == nil || ip.To16() == nil {
|
|
return fmt.Errorf("invalid IP in AAAA record: %s", contents)
|
|
}
|
|
return rc.SetTargetIP(ip) // Reformat to canonical form.
|
|
case "AKAMAICDN", "ALIAS", "ANAME", "CNAME", "NS", "PTR":
|
|
return rc.SetTarget(contents)
|
|
case "CAA":
|
|
return rc.SetTargetCAAString(contents)
|
|
case "DS":
|
|
return rc.SetTargetDSString(contents)
|
|
case "DNSKEY":
|
|
return rc.SetTargetDNSKEYString(contents)
|
|
case "DHCID":
|
|
return rc.SetTarget(contents)
|
|
case "DNAME":
|
|
return rc.SetTarget(contents)
|
|
case "LOC":
|
|
return rc.SetTargetLOCString(origin, contents)
|
|
case "NAPTR":
|
|
return rc.SetTargetNAPTRString(contents)
|
|
case "SOA":
|
|
return rc.SetTargetSOAString(contents)
|
|
case "SPF", "TXT":
|
|
if txtFn == nil {
|
|
return rc.SetTargetTXT(contents)
|
|
}
|
|
t, err := txtFn(contents)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid TXT record: %s", contents)
|
|
}
|
|
return rc.SetTargetTXT(t)
|
|
case "SSHFP":
|
|
return rc.SetTargetSSHFPString(contents)
|
|
case "SVCB", "HTTPS":
|
|
return rc.SetTargetSVCBString(origin, contents)
|
|
case "TLSA":
|
|
return rc.SetTargetTLSAString(contents)
|
|
default:
|
|
// return fmt.Errorf("unknown (def) rtype (%s) when parsing (%s) domain=(%s)", rtype, contents, origin)
|
|
return MakeUnknown(rc, rtype, contents, origin)
|
|
}
|
|
}
|
|
|
|
// PopulateFromString populates a RecordConfig given a type and string. It is equivalent to
|
|
// rc.PopulateFromStringFunc(rtype, contents, origin, nil)
|
|
func (rc *RecordConfig) PopulateFromString(rtype, contents, origin string) error {
|
|
return rc.PopulateFromStringFunc(rtype, contents, origin, nil)
|
|
}
|