From 4b7da682a42d1281d41dacec0a301e08022a25d4 Mon Sep 17 00:00:00 2001 From: Thomas Limoncelli Date: Mon, 24 Nov 2025 13:24:51 -0500 Subject: [PATCH] wip! --- models/record.go | 15 +++- models/t_caa.go | 2 +- pkg/js/helpers.js | 89 ++++++++++--------- pkg/rtypecontrol/rtypecontrol.go | 42 ++++++--- pkg/rtypes/postprocess.go | 69 ++++++++------ .../cfsingleredirect/cfsingleredirect.go | 88 +++++++++++++----- 6 files changed, 197 insertions(+), 108 deletions(-) diff --git a/models/record.go b/models/record.go index fed09976d..51e17e2e8 100644 --- a/models/record.go +++ b/models/record.go @@ -107,9 +107,13 @@ type RecordConfig struct { FieldsAsRaw []string // Fields as received from the dnsconfig.js file. FieldsAsUnicode []string // fields with IDN fields converted to Unicode. + // Legacy fields we hope to remove someday + + SubDomain string `json:"subdomain,omitempty"` + // Cloudflare-specific fields: // When these are used, .target is set to a human-readable version (only to be used for display purposes). - //CloudflareRedirect *CloudflareSingleRedirectConfig `json:"cloudflareapi_redirect,omitempty"` + CloudflareRedirect *CloudflareSingleRedirectConfig `json:"cloudflareapi_redirect,omitempty"` Metadata map[string]string `json:"meta,omitempty"` // Metadata added to record via dnsconfig.js FilePos string `json:"filepos"` // filename:line:char source @@ -166,7 +170,14 @@ type RecordConfig struct { UnknownTypeName string `json:"unknown_type_name,omitempty"` } -// func NewRecordConfig(argsRaw): +func NewRecordConfig(rtype string, ttl uint32, argsRaw []string) *RecordConfig { + rc := &RecordConfig{ + Type: rtype, + TTL: ttl, + } + return rc +} + // .Type = type // .TTL = ttl diff --git a/models/t_caa.go b/models/t_caa.go index 73ced3a13..53e94a137 100644 --- a/models/t_caa.go +++ b/models/t_caa.go @@ -23,7 +23,7 @@ func (rc *RecordConfig) SetTargetCAA(flag uint8, tag string, target string) erro // Per: https://www.iana.org/assignments/pkix-parameters/pkix-parameters.xhtml#caa-properties excluding reserved tags allowedTags := []string{"issue", "issuewild", "iodef", "contactemail", "contactphone", "issuemail", "issuevmc"} if !slices.Contains(allowedTags, tag) { - return fmt.Errorf("CAA tag (%v) is not one of the valid types.", tag) + return fmt.Errorf("CAA tag (%v) is not one of the valid types", tag) } return nil diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js index 7c00479bf..db98f08ce 100644 --- a/pkg/js/helpers.js +++ b/pkg/js/helpers.js @@ -333,16 +333,16 @@ var AKAMAICDN = recordBuilder('AKAMAICDN'); // AKAMAITLC(name, answer_type, target, recordModifiers...) var AKAMAITLC = recordBuilder('AKAMAITLC', { - args: [ - ['name', _.isString], - ['answer_type', function(value) { return _.isString(value) && ['DUAL', 'A', 'AAAA'].indexOf(value) !== -1; }], - ['target', _.isString], - ], - transform: function (record, args, modifier) { - record.name = args.name; - record.answer_type = args.answer_type; - record.target = args.target; - }, + args: [ + ['name', _.isString], + ['answer_type', function (value) { return _.isString(value) && ['DUAL', 'A', 'AAAA'].indexOf(value) !== -1; }], + ['target', _.isString], + ], + transform: function (record, args, modifier) { + record.name = args.name; + record.answer_type = args.answer_type; + record.target = args.target; + }, }); // ALIAS(name,target, recordModifiers...) @@ -683,20 +683,20 @@ var TXT = recordBuilder('TXT', { }); var LUA = recordBuilder('LUA', { - args: [ - ['name', _.isString], - ['rtype', _.isString], - ['target', isStringOrArray], - ], - transform: function (record, args, modifiers) { - record.name = args.name; - record.luartype = args.rtype.toUpperCase(); - if (_.isString(args.target)) { - record.target = args.target; - } else { - record.target = args.target.join(''); - } - }, + args: [ + ['name', _.isString], + ['rtype', _.isString], + ['target', isStringOrArray], + ], + transform: function (record, args, modifiers) { + record.name = args.name; + record.luartype = args.rtype.toUpperCase(); + if (_.isString(args.target)) { + record.target = args.target; + } else { + record.target = args.target.join(''); + } + }, }); // Parses coordinates of the form 41°24'12.2"N 2°10'26.5"E @@ -858,15 +858,15 @@ function locStringBuilder(record, args) { (args.alt < -100000 ? -100000 : args.alt > 42849672.95 - ? 42849672.95 - : args.alt.toString()) + 'm'; + ? 42849672.95 + : args.alt.toString()) + 'm'; precisionbuffer += ' ' + (args.siz > 90000000 ? 90000000 : args.siz < 0 - ? 0 - : args.siz.toString()) + + ? 0 + : args.siz.toString()) + 'm'; precisionbuffer += ' ' + @@ -906,8 +906,8 @@ function locDMSBuilder(record, args) { record.localtitude > 4294967295 ? 4294967295 : record.localtitude < 0 - ? 0 - : record.localtitude; + ? 0 + : record.localtitude; // Size record.locsize = getENotationInt(args.siz); // Horizontal Precision @@ -1785,7 +1785,7 @@ function CAA_BUILDER(value) { throw 'CAA_BUILDER requires at least one entry at issue, issuewild, issuevmc or issuemail'; } - var CAA_TTL = function () {}; + var CAA_TTL = function () { }; if (value.ttl) { CAA_TTL = TTL(value.ttl); } @@ -1802,7 +1802,7 @@ function CAA_BUILDER(value) { } if (value.issue) { - var flag = function () {}; + var flag = function () { }; if (value.issue_critical) { flag = CAA_CRITICAL; } @@ -1811,7 +1811,7 @@ function CAA_BUILDER(value) { } if (value.issuewild) { - var flag = function () {}; + var flag = function () { }; if (value.issuewild_critical) { flag = CAA_CRITICAL; } @@ -1822,7 +1822,7 @@ function CAA_BUILDER(value) { } if (value.issuevmc) { - var flag = function () {}; + var flag = function () { }; if (value.issuevmc_critical) { flag = CAA_CRITICAL; } @@ -1833,7 +1833,7 @@ function CAA_BUILDER(value) { } if (value.issuemail) { - var flag = function () {}; + var flag = function () { }; if (value.issuemail_critical) { flag = CAA_CRITICAL; } @@ -2052,7 +2052,7 @@ function DKIM_BUILDER(value) { } // Handle TTL - var DKIM_TTL = value.ttl ? TTL(value.ttl) : function () {}; + var DKIM_TTL = value.ttl ? TTL(value.ttl) : function () { }; return TXT(fullLabel, record.join('; '), DKIM_TTL); } @@ -2284,20 +2284,20 @@ function M365_BUILDER(name, value) { CNAME( 'selector1._domainkey', 'selector1-' + - value.domainGUID + - '._domainkey.' + - value.initialDomain + - '.' + value.domainGUID + + '._domainkey.' + + value.initialDomain + + '.' ) ); r.push( CNAME( 'selector2._domainkey', 'selector2-' + - value.domainGUID + - '._domainkey.' + - value.initialDomain + - '.' + value.domainGUID + + '._domainkey.' + + value.initialDomain + + '.' ) ); } @@ -2477,3 +2477,4 @@ function rawrecordBuilder(type) { // CLOUDFLAREAPI: var CF_SINGLE_REDIRECT = rawrecordBuilder('CLOUDFLAREAPI_SINGLE_REDIRECT'); +var RP = rawrecordBuilder('RP'); diff --git a/pkg/rtypecontrol/rtypecontrol.go b/pkg/rtypecontrol/rtypecontrol.go index f40fdf48b..3f94138b0 100644 --- a/pkg/rtypecontrol/rtypecontrol.go +++ b/pkg/rtypecontrol/rtypecontrol.go @@ -1,21 +1,43 @@ package rtypecontrol -import "github.com/StackExchange/dnscontrol/v4/providers" +import ( + "fmt" + "github.com/StackExchange/dnscontrol/v4/models" + "github.com/StackExchange/dnscontrol/v4/providers" +) + +// backwards compatibility: var validTypes = map[string]struct{}{} -func Register(t string) { - // Does this already exist? - if _, ok := validTypes[t]; ok { - panic("rtype %q already registered. Can't register it a second time!") - } +type RType interface { + // Returns the name of the rtype ("A", "MX", etc.) + Name() string - validTypes[t] = struct{}{} + // RecordConfig factory. Updates a RecordConfig's fields based on args. + FromArgs(*models.RecordConfig, args []any) (*models.RecordConfig, error) // - providers.RegisterCustomRecordType(t, "", "") + // Returns a string representation of the record in RFC1038 format. + // AsRFC1038String([]string) (string, error) } -func IsValid(t string) bool { - _, ok := validTypes[t] +// Map of registered rtypes. +var Iface map[string]RType = map[string]RType{} + +func Register(typeName string, t RType) { + name := t.Name() + if _, ok := Iface[name]; ok { + panic(fmt.Sprintf("rtype %q already registered. Can't register it a second time!", name)) + } + // Store the interface + Iface[name] = t + + // For compatibility with legacy systems: + providers.RegisterCustomRecordType(name, "", "") + +} + +func IsValid(name string) bool { + _, ok := Iface[name] return ok } diff --git a/pkg/rtypes/postprocess.go b/pkg/rtypes/postprocess.go index 1631ab42d..d08f8c5e1 100644 --- a/pkg/rtypes/postprocess.go +++ b/pkg/rtypes/postprocess.go @@ -1,10 +1,8 @@ package rtypes import ( - "fmt" - "github.com/StackExchange/dnscontrol/v4/models" - "github.com/StackExchange/dnscontrol/v4/providers/cloudflare/rtypes/cfsingleredirect" + "github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol" ) func PostProcess(domains []*models.DomainConfig) error { @@ -12,37 +10,54 @@ func PostProcess(domains []*models.DomainConfig) error { for _, dc := range domains { for _, rawRec := range dc.RawRecords { + + // Create as much of the RecordConfig as we can now. Allow New() to fill in the reset. rec := &models.RecordConfig{ Type: rawRec.Type, TTL: rawRec.TTL, - Name: rawRec.Args[0].(string), - Metadata: map[string]string{}, + Metadata: stringifyMetas(rawRec.Metas), } + //rec.Name, rec.NameRaw, rec.NameUnicode := normalizeName(rawRec.Args[0].(string), dc.Name, dc.SubDomain) + //rec.NameFQDN, rec.NameFQDNRaw, rec.NameFQDNUnicode := normalizeNameFQDN(rawRec.Args[0].(string), dc.Name, dc.SubDomain) + // Name: + // * Convert to lowercase. + // * Convert to IDN and UNI. + // + // IDN: name + subdomain + domain - // Copy the metadata (convert everything to string) - for _, m := range rawRec.Metas { - for mk, mv := range m { - if v, ok := mv.(string); ok { - rec.Metadata[mk] = v // Already a string. No new malloc. - } else { - rec.Metadata[mk] = fmt.Sprintf("%v", mv) - } - } - } + rtypecontrol.Iface[rawRec.Type].FromArgs(rec, rawRec.Args) - // Call the proper initialize function. - // TODO(tlim): Good candidate for an interface or a lookup table. - switch rawRec.Type { - case "CLOUDFLAREAPI_SINGLE_REDIRECT": - err = cfsingleredirect.FromRaw(rec, rawRec.Args) - rec.SetLabel("@", dc.Name) + // rec := &models.RecordConfig{ + // Type: rawRec.Type, + // TTL: rawRec.TTL, + // Name: rawRec.Args[0].(string), + // Metadata: map[string]string{}, + // } - default: - err = fmt.Errorf("unknown rawrec type=%q", rawRec.Type) - } - if err != nil { - return fmt.Errorf("%s (%q, %q) record error: %w", rawRec.Type, rec.Name, dc.Name, err) - } + // // Copy the metadata (convert everything to string) + // for _, m := range rawRec.Metas { + // for mk, mv := range m { + // if v, ok := mv.(string); ok { + // rec.Metadata[mk] = v // Already a string. No new malloc. + // } else { + // rec.Metadata[mk] = fmt.Sprintf("%v", mv) + // } + // } + // } + + // // Call the proper initialize function. + // // TODO(tlim): Good candidate for an interface or a lookup table. + // switch rawRec.Type { + // case "CLOUDFLAREAPI_SINGLE_REDIRECT": + // err = cfsingleredirect.FromRaw(rec, rawRec.Args) + // rec.SetLabel("@", dc.Name) + + // default: + // err = fmt.Errorf("unknown rawrec type=%q", rawRec.Type) + // } + // if err != nil { + // return fmt.Errorf("%s (%q, %q) record error: %w", rawRec.Type, rec.Name, dc.Name, err) + // } // Free memeory: clear(rawRec.Args) diff --git a/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go b/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go index a2ee6e19d..ff68a65fc 100644 --- a/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go +++ b/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go @@ -1,37 +1,77 @@ package cfsingleredirect import ( - "fmt" - "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol" ) -// SINGLEREDIRECT is the string name for this rType. -const SINGLEREDIRECT = "CLOUDFLAREAPI_SINGLE_REDIRECT" +// "github.com/StackExchange/dnscontrol/v4/models" +// "github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol" func init() { - rtypecontrol.Register(SINGLEREDIRECT) + rtypecontrol.Register(SingleRedirect{}) } -// FromRaw convert RecordConfig using data from a RawRecordConfig's parameters. -func FromRaw(rc *models.RecordConfig, items []any) error { - // Validate types. - if err := rtypecontrol.PaveArgs(items, "siss"); err != nil { - return err - } +type SingleRedirect struct{} - // Unpack the args: - var name, when, then string - var code uint16 - - name = items[0].(string) - code = items[1].(uint16) - if code != 301 && code != 302 && code != 303 && code != 307 && code != 308 { - return fmt.Errorf("%s: code (%03d) is not 301,302,303,307,308", rc.FilePos, code) - } - when = items[2].(string) - then = items[3].(string) - - return makeSingleRedirectFromRawRec(rc, code, name, when, then) +func (handle *SingleRedirect) Name() string { + return "CLOUDFLAREAPI_SINGLE_REDIRECT" } + +// func MakeSingleRedirect() SingleRedirect { return SingleRedirect{} } +func (handle *SingleRedirect) FromArgs([]any) (*models.RecordConfig, error) { + rec := &models.RecordConfig{ + Type: handle.Name(), + TTL: ttl, + + //FilePos = FixFilePos(handle.FilePos) + } + return rec, nil +} +// // Validate types. +// if err := rtypecontrol.PaveArgs(items, "siss"); err != nil { +// return err +// } + +// // Unpack the args: +// var name, when, then string +// var code uint16 + +// name = items[0].(string) +// code = items[1].(uint16) +// if code != 301 && code != 302 && code != 303 && code != 307 && code != 308 { +// return fmt.Errorf("%s: code (%03d) is not 301,302,303,307,308", rc.FilePos, code) +// } +// when = items[2].(string) +// then = items[3].(string) + +// return makeSingleRedirectFromRawRec(rc, code, name, when, then) + return &models.RecordConfig{}, nil +} + +//func (handle *SingleRedirect) IDNFields(argsRaw) (argsIDN, argsUnicode, error) {} +//func (handle *SingleRedirect) AsRFC1038String(*models.RecordConfig) string {} +//func (handle *SingleRedirect) CopyToLegacyFields(*models.RecordConfig) {} +//func (handle *SingleRedirect) CopyFromLegacyFields(*models.RecordConfig) {} + +// // FromRaw convert RecordConfig using data from a RawRecordConfig's parameters. +// func FromRaw(rc *models.RecordConfig, items []any) error { +// // Validate types. +// if err := rtypecontrol.PaveArgs(items, "siss"); err != nil { +// return err +// } + +// // Unpack the args: +// var name, when, then string +// var code uint16 + +// name = items[0].(string) +// code = items[1].(uint16) +// if code != 301 && code != 302 && code != 303 && code != 307 && code != 308 { +// return fmt.Errorf("%s: code (%03d) is not 301,302,303,307,308", rc.FilePos, code) +// } +// when = items[2].(string) +// then = items[3].(string) + +// return makeSingleRedirectFromRawRec(rc, code, name, when, then) +// }