Linked zones: PTR records follow each view, deleting an override stays deleted
Auto-PTR (and --ptr) changed PTR records in every linked view, whatever view the address record was in. ptrSync now compares each touched A/AAAA set before and after, per view, and changes only that view's reverse zone. PTR records in linked reverse zones are local (fill too); a shared PTR set is made local in each view before one view changes it. Deleting a linked zone's local records no longer brings the shared ones back into the view: the name and type are gone from that view and the other views keep theirs, local to each. Share still restores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e38390df7d
commit
65086e314a
9 files changed
+453
-238
No files matched your search
+250
-205
@@ -32,6 +32,12 @@ type expander struct {
|
||||
// exposures classifies the views as public or private (nil in tests:
|
||||
// every view private).
|
||||
exposures func(*model.State) []validate.Exposure
|
||||
// ptrForced are the address sets whose PTR records the request being
|
||||
// expanded asked to update (ptrForce).
|
||||
ptrForced map[rrKey]bool
|
||||
// sharing are the local sets the request deletes so that the shared
|
||||
// version reaches them (dropOverrides).
|
||||
sharing map[rrKey]bool
|
||||
}
|
||||
|
||||
func (x *expander) ids(n int) ([]int64, error) {
|
||||
@@ -136,13 +142,20 @@ func dateSerial(now time.Time) uint32 {
|
||||
}
|
||||
|
||||
func (x *expander) expand(req Request) ([]model.Op, error) {
|
||||
x.ptrForced, x.sharing = nil, nil
|
||||
ops, err := x.expandOne(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch req.Op {
|
||||
case OpRecordCreate, OpRecordUpdate, OpRecordDelete, OpRRSetReplace, OpRecordDisable, OpRecordEnable, OpZoneFillPTR:
|
||||
// Changes to linked zones reach the other zones of the link.
|
||||
case OpRecordCreate, OpRecordUpdate, OpRecordDelete, OpRRSetReplace, OpRecordDisable, OpRecordEnable:
|
||||
// Changes to linked zones reach the other zones of the link, then
|
||||
// PTR records follow the addresses in each view.
|
||||
if ops, err = x.linkSync(ops); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x.ptrSync(ops)
|
||||
case OpZoneFillPTR:
|
||||
return x.linkSync(ops)
|
||||
}
|
||||
return ops, nil
|
||||
@@ -757,65 +770,227 @@ func addrOf(p *rr.Parsed) (netip.Addr, bool) {
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
// ptrOps returns operations creating the PTR record for an address record.
|
||||
//
|
||||
// auto is set when the zone's auto_ptr asked rather than the request: a
|
||||
// missing reverse zone isn't worth a warning then (most addresses have
|
||||
// none), and an address whose PTR record names another host keeps it.
|
||||
func (x *expander) ptrCreate(z *model.Zone, owner string, p *rr.Parsed, auto bool) ([]model.Op, error) {
|
||||
addr, ok := addrOf(p)
|
||||
if !ok {
|
||||
return nil, errf(KindBadRequest, "ptr only applies to A and AAAA records")
|
||||
// PTR records follow address records per view (docs/reverse.md): after a
|
||||
// request's operations (and those keeping linked zones in step), ptrSync
|
||||
// compares each touched A or AAAA set before and after, in every view it
|
||||
// is in, and changes the PTR records of that view's reverse zone to match.
|
||||
// So a record local to one view of a linked zone changes PTR records in
|
||||
// that view only, and an address that goes from one view keeps its PTR
|
||||
// record in the views that still have it.
|
||||
|
||||
// ptrForce marks a name and type whose PTR records the request asked to
|
||||
// update (ptr: true), in its zone and the zones linked with it.
|
||||
func (x *expander) ptrForce(z *model.Zone, name, typ string) {
|
||||
if x.ptrForced == nil {
|
||||
x.ptrForced = map[rrKey]bool{}
|
||||
}
|
||||
var ops []model.Op
|
||||
found := false
|
||||
for _, vid := range ptrViews(x.st, z) {
|
||||
rz, ptrName := reverseZoneFor(x.st, vid, addr)
|
||||
if rz == nil {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
more, err := x.ptrCreateIn(rz, ptrName, owner, auto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, more...)
|
||||
x.ptrForced[rrKey{z.ID, name, typ}] = true
|
||||
for _, o := range x.st.LinkedWith(z) {
|
||||
x.ptrForced[rrKey{o.ID, name, typ}] = true
|
||||
}
|
||||
if !found && !auto {
|
||||
x.warnings = append(x.warnings, fmt.Sprintf("no reverse zone for %s in this view, so no PTR record was added (create one: jarl zone create %s/%d)", addr, addr, addr.BitLen()))
|
||||
}
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
// ptrViews are the views whose reverse zones get the PTR records of zone
|
||||
// z's address records: z's own, and, for a linked zone, the other views
|
||||
// of the link (the record is in all of them).
|
||||
func ptrViews(st *model.State, z *model.Zone) []int64 {
|
||||
out := []int64{z.ViewID}
|
||||
for _, o := range st.LinkedWith(z) {
|
||||
if !slices.Contains(out, o.ViewID) {
|
||||
out = append(out, o.ViewID)
|
||||
// Whether a set has an address: none, only in disabled records, enabled.
|
||||
const (
|
||||
addrNone = iota
|
||||
addrDisabled
|
||||
addrEnabled
|
||||
)
|
||||
|
||||
// addrStates returns the addresses of a zone's A or AAAA set.
|
||||
func addrStates(st *model.State, k rrKey) map[netip.Addr]int {
|
||||
out := map[netip.Addr]int{}
|
||||
for _, r := range st.RRset(k.zone, k.name, k.typ) {
|
||||
a, err := netip.ParseAddr(r.Data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s := addrDisabled
|
||||
if !r.Disabled {
|
||||
s = addrEnabled
|
||||
}
|
||||
out[a.Unmap()] = max(out[a.Unmap()], s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ptrSync returns ops followed by the PTR changes they call for: for the
|
||||
// zone's auto_ptr, or because the request asked (ptrForce).
|
||||
func (x *expander) ptrSync(ops []model.Op) ([]model.Op, error) {
|
||||
forced := x.ptrForced
|
||||
x.ptrForced = nil
|
||||
work := x.st.Clone()
|
||||
c := &applyCtx{p: x.p, now: x.now}
|
||||
for _, op := range ops {
|
||||
if err := applyOp(work, op, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
keys := affectedKeys(x.st, work, ops)
|
||||
for _, k := range sortedForced(forced) {
|
||||
if !slices.Contains(keys, k) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
var extra []model.Op
|
||||
for _, k := range keys {
|
||||
z := work.Zones[k.zone]
|
||||
if z == nil || (k.typ != "A" && k.typ != "AAAA") || strings.HasPrefix(k.name, "*.") {
|
||||
continue
|
||||
}
|
||||
force := forced[k]
|
||||
if !force && !z.AutoPTR {
|
||||
continue
|
||||
}
|
||||
before, after := addrStates(x.st, k), addrStates(work, k)
|
||||
var addrs []netip.Addr
|
||||
for _, m := range []map[netip.Addr]int{before, after} {
|
||||
for a := range m {
|
||||
if !slices.Contains(addrs, a) {
|
||||
addrs = append(addrs, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.SortFunc(addrs, func(a, b netip.Addr) int { return a.Compare(b) })
|
||||
for _, a := range addrs {
|
||||
was, is := before[a], after[a]
|
||||
if !force && (was == is || was == addrNone && is == addrDisabled) {
|
||||
continue
|
||||
}
|
||||
rz, ptrName := reverseZoneFor(work, z.ViewID, a)
|
||||
if rz == nil {
|
||||
if force && is == addrEnabled {
|
||||
x.warnings = append(x.warnings, fmt.Sprintf("no reverse zone for %s in %s, so no PTR record was added there (create one: jarl zone create %s/%d%s)",
|
||||
a, viewOrServer(work, z.ViewID), a, a.BitLen(), viewFlag(work, z.ViewID)))
|
||||
}
|
||||
continue
|
||||
}
|
||||
more, err := x.ptrChange(work, rz, ptrName, k.name, is, !force)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, op := range more {
|
||||
if err := applyOp(work, op, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
extra = append(extra, more...)
|
||||
}
|
||||
}
|
||||
if len(extra) == 0 {
|
||||
return ops, nil
|
||||
}
|
||||
// The PTR records are local (see ptrChange), so this only keeps a
|
||||
// linked reverse zone consistent.
|
||||
extra, err := x.linkSync(extra)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(ops, extra...), nil
|
||||
}
|
||||
|
||||
func sortedForced(m map[rrKey]bool) []rrKey {
|
||||
var out []rrKey
|
||||
for k := range m {
|
||||
out = append(out, k)
|
||||
}
|
||||
slices.SortFunc(out, func(a, b rrKey) int {
|
||||
if a.zone != b.zone {
|
||||
return int(a.zone - b.zone)
|
||||
}
|
||||
if a.name != b.name {
|
||||
return strings.Compare(a.name, b.name)
|
||||
}
|
||||
return strings.Compare(a.typ, b.typ)
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func viewFlag(st *model.State, viewID int64) string {
|
||||
if v := st.Views[viewID]; v != nil {
|
||||
return " --view " + v.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ptrChange returns the operations that give reverse zone rz's PTR
|
||||
// records at ptrName for host owner the state of its address: none
|
||||
// (deleted), disabled, or enabled (added when missing).
|
||||
//
|
||||
// In a linked reverse zone the PTR records are local to each view, since
|
||||
// each view's follow its own address records: a shared set at ptrName
|
||||
// is made local in every view of the link first, as each view has it.
|
||||
func (x *expander) ptrChange(st *model.State, rz *model.Zone, ptrName, owner string, state int, auto bool) ([]model.Op, error) {
|
||||
nwarn := len(x.warnings)
|
||||
ops, err := x.ptrChangeIn(st, rz, ptrName, owner, state, auto)
|
||||
if err != nil || len(ops) == 0 || rz.LinkGroup == 0 {
|
||||
return ops, err
|
||||
}
|
||||
var local []model.Op
|
||||
for _, m := range st.LinkMembers(rz.LinkGroup) {
|
||||
for _, r := range st.RRset(m.ID, ptrName, "PTR") {
|
||||
if r.SharedID != 0 {
|
||||
local = append(local, newOp(OpRecordUpdate, r.ID, r.Version, RecordInput{SharedID: model.Ptr(int64(0))},
|
||||
fmt.Sprintf("linked: %s PTR %s becomes local to %s (PTR records follow each view's own address records)", r.Name, r.Data, zoneDesc(st, m))))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(local) == 0 {
|
||||
return ops, nil
|
||||
}
|
||||
// Again on the local records (their versions changed).
|
||||
scratch := st.Clone()
|
||||
c := &applyCtx{p: x.p, now: x.now}
|
||||
for _, op := range local {
|
||||
if err := applyOp(scratch, op, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
x.warnings = x.warnings[:nwarn]
|
||||
ops, err = x.ptrChangeIn(scratch, rz, ptrName, owner, state, auto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(local, ops...), nil
|
||||
}
|
||||
|
||||
func (x *expander) ptrChangeIn(st *model.State, rz *model.Zone, ptrName, owner string, state int, auto bool) ([]model.Op, error) {
|
||||
var ops []model.Op
|
||||
switch state {
|
||||
case addrNone:
|
||||
for _, r := range st.RRset(rz.ID, ptrName, "PTR") {
|
||||
if strings.EqualFold(r.Data, owner) {
|
||||
ops = append(ops, newOp(OpRecordDelete, r.ID, r.Version, nil, fmt.Sprintf("delete %s PTR %s", r.Name, r.Data)))
|
||||
}
|
||||
}
|
||||
case addrDisabled:
|
||||
for _, r := range st.RRset(rz.ID, ptrName, "PTR") {
|
||||
if strings.EqualFold(r.Data, owner) && !r.Disabled {
|
||||
ops = append(ops, newOp(OpRecordUpdate, r.ID, r.Version, RecordInput{Disabled: model.Ptr(true)}, fmt.Sprintf("disable %s PTR %s", r.Name, r.Data)))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return x.ptrCreateIn(st, rz, ptrName, owner, auto)
|
||||
}
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
// ptrCreateIn returns operations creating the PTR record ptrName -> owner
|
||||
// in reverse zone rz (none when it is there; enabling it when disabled).
|
||||
func (x *expander) ptrCreateIn(rz *model.Zone, ptrName, owner string, auto bool) ([]model.Op, error) {
|
||||
// auto is set when the zone's auto_ptr asked rather than the request: an
|
||||
// address whose PTR record names another host keeps it then.
|
||||
func (x *expander) ptrCreateIn(st *model.State, rz *model.Zone, ptrName, owner string, auto bool) ([]model.Op, error) {
|
||||
var other string
|
||||
for _, r := range x.st.ZoneRecords(rz.ID) {
|
||||
if r.Name == ptrName && r.Type == "PTR" {
|
||||
if strings.EqualFold(r.Data, owner) {
|
||||
if r.Disabled {
|
||||
in := RecordInput{Disabled: model.Ptr(false)}
|
||||
return []model.Op{newOp(OpRecordUpdate, r.ID, r.Version, in, fmt.Sprintf("enable %s PTR %s", r.Name, r.Data))}, nil
|
||||
}
|
||||
return nil, nil // already there
|
||||
}
|
||||
if !r.Disabled {
|
||||
other = r.Data
|
||||
for _, r := range st.RRset(rz.ID, ptrName, "PTR") {
|
||||
if strings.EqualFold(r.Data, owner) {
|
||||
if r.Disabled {
|
||||
in := RecordInput{Disabled: model.Ptr(false)}
|
||||
return []model.Op{newOp(OpRecordUpdate, r.ID, r.Version, in, fmt.Sprintf("enable %s PTR %s", r.Name, r.Data))}, nil
|
||||
}
|
||||
return nil, nil // already there
|
||||
}
|
||||
if !r.Disabled {
|
||||
other = r.Data
|
||||
}
|
||||
}
|
||||
if other != "" {
|
||||
@@ -830,38 +1005,12 @@ func (x *expander) ptrCreateIn(rz *model.Zone, ptrName, owner string, auto bool)
|
||||
return nil, err
|
||||
}
|
||||
in := RecordInput{ZoneID: rz.ID, Name: model.Ptr(ptrName), Type: model.Ptr("PTR"), Data: model.Ptr(owner)}
|
||||
if rz.LinkGroup != 0 {
|
||||
in.SharedID = model.Ptr(int64(0))
|
||||
}
|
||||
return []model.Op{newOp(OpRecordCreate, ids[0], 0, in, fmt.Sprintf("add %s IN PTR %s", ptrName, owner))}, nil
|
||||
}
|
||||
|
||||
// ptrSetDisabled returns operations disabling or enabling the PTR record
|
||||
// of an address record.
|
||||
func (x *expander) ptrSetDisabled(z *model.Zone, owner string, p *rr.Parsed, disabled bool) []model.Op {
|
||||
addr, ok := addrOf(p)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var ops []model.Op
|
||||
for _, vid := range ptrViews(x.st, z) {
|
||||
rz, ptrName := reverseZoneFor(x.st, vid, addr)
|
||||
if rz == nil {
|
||||
continue
|
||||
}
|
||||
ops = append(ops, x.ptrSetDisabledIn(rz, ptrName, owner, disabled)...)
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func (x *expander) ptrSetDisabledIn(rz *model.Zone, ptrName, owner string, disabled bool) []model.Op {
|
||||
var ops []model.Op
|
||||
for _, r := range x.st.ZoneRecords(rz.ID) {
|
||||
if r.Name == ptrName && r.Type == "PTR" && strings.EqualFold(r.Data, owner) && r.Disabled != disabled {
|
||||
in := RecordInput{Disabled: model.Ptr(disabled)}
|
||||
ops = append(ops, newOp(OpRecordUpdate, r.ID, r.Version, in, fmt.Sprintf("%s %s PTR %s", enableWord(disabled), r.Name, r.Data)))
|
||||
}
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func enableWord(disabled bool) string {
|
||||
if disabled {
|
||||
return "disable"
|
||||
@@ -869,23 +1018,6 @@ func enableWord(disabled bool) string {
|
||||
return "enable"
|
||||
}
|
||||
|
||||
func (x *expander) ptrDelete(z *model.Zone, owner string, p *rr.Parsed) []model.Op {
|
||||
addr, ok := addrOf(p)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var ops []model.Op
|
||||
for _, vid := range ptrViews(x.st, z) {
|
||||
rz, ptrName := reverseZoneFor(x.st, vid, addr)
|
||||
if rz == nil {
|
||||
continue
|
||||
}
|
||||
in := RecordInput{ZoneID: rz.ID, Name: model.Ptr(ptrName), Type: model.Ptr("PTR"), Data: model.Ptr(owner), MissingOK: true}
|
||||
ops = append(ops, newOp(OpRecordDelete, 0, 0, in, fmt.Sprintf("delete %s IN PTR %s (if present)", ptrName, owner)))
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
// parseWarnings keeps what parsing noticed (e.g. a name read as absolute).
|
||||
// The stored operation holds the canonical data, so applying it doesn't
|
||||
// notice again.
|
||||
@@ -912,7 +1044,12 @@ func (x *expander) recordCreate(req Request) ([]model.Op, error) {
|
||||
return nil, err
|
||||
}
|
||||
x.parseWarnings(owner, p)
|
||||
ptr, auto := in.PTR, !in.PTR && autoPTR(z, owner, p)
|
||||
if in.PTR {
|
||||
if _, ok := addrOf(p); !ok {
|
||||
return nil, errf(KindBadRequest, "ptr only applies to A and AAAA records")
|
||||
}
|
||||
x.ptrForce(z, owner, p.Type)
|
||||
}
|
||||
in.ZoneID, in.Zone, in.View, in.PTR = z.ID, "", "", false
|
||||
in.Name, in.Type, in.Data = &owner, &p.Type, &p.Data
|
||||
ids, err := x.ids(1)
|
||||
@@ -940,15 +1077,7 @@ func (x *expander) recordCreate(req Request) ([]model.Op, error) {
|
||||
}
|
||||
// local on a zone that isn't linked: its records are local anyway.
|
||||
in.Local = nil
|
||||
ops := []model.Op{newOp(OpRecordCreate, ids[0], 0, in, summary)}
|
||||
if ptr || auto && (in.Disabled == nil || !*in.Disabled) {
|
||||
more, err := x.ptrCreate(z, owner, p, auto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, more...)
|
||||
}
|
||||
return ops, nil
|
||||
return []model.Op{newOp(OpRecordCreate, ids[0], 0, in, summary)}, nil
|
||||
}
|
||||
|
||||
func (x *expander) recordUpdate(req Request) ([]model.Op, error) {
|
||||
@@ -1069,67 +1198,22 @@ func (x *expander) recordUpdate(req Request) ([]model.Op, error) {
|
||||
if string(mustJSON(in)) != "{}" || !ptr {
|
||||
ops = append(ops, newOp(OpRecordUpdate, r.ID, base, in, summary))
|
||||
}
|
||||
disabled := r.Disabled
|
||||
if in.Disabled != nil {
|
||||
disabled = *in.Disabled
|
||||
}
|
||||
moved := p.Data != r.Data || owner != r.Name || p.Type != r.Type
|
||||
if !ptr && z.AutoPTR {
|
||||
// The zone keeps PTR records in step (docs/reverse.md): as if
|
||||
// asked, for whichever of the old and new record is an address.
|
||||
old, err := rr.Parse(z.Name, r.Name, r.Type, r.Data)
|
||||
oldAuto := err == nil && autoPTR(z, r.Name, old)
|
||||
newAuto := autoPTR(z, owner, p)
|
||||
switch {
|
||||
case moved:
|
||||
if oldAuto {
|
||||
ops = append(ops, x.ptrDelete(z, r.Name, old)...)
|
||||
}
|
||||
if newAuto && !disabled {
|
||||
more, err := x.ptrCreate(z, owner, p, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, more...)
|
||||
}
|
||||
case toggled && newAuto && disabled:
|
||||
ops = append(ops, x.ptrSetDisabled(z, owner, p, true)...)
|
||||
case toggled && newAuto:
|
||||
// Enables the PTR record, or adds it when it is missing.
|
||||
more, err := x.ptrCreate(z, owner, p, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, more...)
|
||||
}
|
||||
return ops, nil
|
||||
}
|
||||
if ptr && moved {
|
||||
if ptr {
|
||||
// The PTR records of the old and the new record, whichever is an
|
||||
// address (ptrSync).
|
||||
_, isAddr := addrOf(p)
|
||||
if old, err := rr.Parse(z.Name, r.Name, r.Type, r.Data); err == nil {
|
||||
ops = append(ops, x.ptrDelete(z, r.Name, old)...)
|
||||
}
|
||||
if !disabled {
|
||||
more, err := x.ptrCreate(z, owner, p, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if _, ok := addrOf(old); ok {
|
||||
isAddr = true
|
||||
x.ptrForce(z, r.Name, r.Type)
|
||||
}
|
||||
ops = append(ops, more...)
|
||||
}
|
||||
}
|
||||
if ptr && !moved && toggled {
|
||||
if _, ok := addrOf(p); !ok {
|
||||
if !isAddr {
|
||||
return nil, errf(KindBadRequest, "ptr only applies to A and AAAA records")
|
||||
}
|
||||
ops = append(ops, x.ptrSetDisabled(z, owner, p, disabled)...)
|
||||
}
|
||||
if ptr && !moved && !toggled && !disabled {
|
||||
// Nothing else changes: make sure the PTR record is there (the
|
||||
// reverse zone may be newer than the record).
|
||||
more, err := x.ptrCreate(z, owner, p, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if _, ok := addrOf(p); ok {
|
||||
x.ptrForce(z, owner, p.Type)
|
||||
}
|
||||
ops = append(ops, more...)
|
||||
}
|
||||
return ops, nil
|
||||
}
|
||||
@@ -1152,9 +1236,8 @@ func (x *expander) recordDelete(req Request) ([]model.Op, error) {
|
||||
base = r.Version
|
||||
}
|
||||
ops := []model.Op{newOp(OpRecordDelete, r.ID, base, nil, fmt.Sprintf("delete %s %s %s", r.Name, r.Type, r.Data))}
|
||||
z := x.st.Zones[r.ZoneID]
|
||||
if p, err := rr.Parse(z.Name, r.Name, r.Type, r.Data); err == nil && (in.PTR || autoPTR(z, r.Name, p)) {
|
||||
ops = append(ops, x.ptrDelete(z, r.Name, p)...)
|
||||
if in.PTR {
|
||||
x.ptrForce(x.st.Zones[r.ZoneID], r.Name, r.Type)
|
||||
}
|
||||
return ops, nil
|
||||
}
|
||||
@@ -1190,25 +1273,12 @@ func (x *expander) recordDelete(req Request) ([]model.Op, error) {
|
||||
if in.MissingOK {
|
||||
what += " (if present)"
|
||||
}
|
||||
ptr := in.PTR
|
||||
if in.PTR && parsed != nil {
|
||||
x.ptrForce(z, owner, typ)
|
||||
}
|
||||
in.ZoneID, in.Zone, in.View, in.PTR = z.ID, "", "", false
|
||||
in.Name, in.Type = &owner, &typ
|
||||
ops := []model.Op{newOp(OpRecordDelete, 0, 0, in, "delete "+what)}
|
||||
switch {
|
||||
case ptr && parsed != nil:
|
||||
ops = append(ops, x.ptrDelete(z, owner, parsed)...)
|
||||
case z.AutoPTR:
|
||||
// Every matching record's PTR record goes too.
|
||||
for _, r := range x.st.RRset(z.ID, owner, typ) {
|
||||
if parsed != nil && r.Data != parsed.Data {
|
||||
continue
|
||||
}
|
||||
if p, err := rr.Parse(z.Name, r.Name, r.Type, r.Data); err == nil && autoPTR(z, r.Name, p) {
|
||||
ops = append(ops, x.ptrDelete(z, r.Name, p)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops, nil
|
||||
return []model.Op{newOp(OpRecordDelete, 0, 0, in, "delete "+what)}, nil
|
||||
}
|
||||
|
||||
// recordSetDisabled expands record.disable and record.enable: the record
|
||||
@@ -1342,32 +1412,7 @@ func (x *expander) rrsetReplace(req Request) ([]model.Op, error) {
|
||||
if len(canon) == 0 {
|
||||
summary = fmt.Sprintf("delete all %s records of %s%s", typ, owner, where)
|
||||
}
|
||||
ops := append(pre, newOp(OpRRSetReplace, 0, 0, in, summary))
|
||||
if z.AutoPTR && (typ == "A" || typ == "AAAA") && !strings.HasPrefix(owner, "*.") {
|
||||
// PTR records follow the addresses that go and come.
|
||||
old := map[string]bool{}
|
||||
for _, r := range x.st.RRset(z.ID, owner, typ) {
|
||||
old[r.Data] = true
|
||||
if !slices.Contains(canon, r.Data) {
|
||||
if p, err := rr.Parse(z.Name, r.Name, r.Type, r.Data); err == nil {
|
||||
ops = append(ops, x.ptrDelete(z, r.Name, p)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, d := range canon {
|
||||
if old[d] {
|
||||
continue
|
||||
}
|
||||
if _, p, err := parseRecord(z, owner, typ, d); err == nil {
|
||||
more, err := x.ptrCreate(z, owner, p, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, more...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops, nil
|
||||
return append(pre, newOp(OpRRSetReplace, 0, 0, in, summary)), nil
|
||||
}
|
||||
|
||||
func (x *expander) viewCreate(req Request) ([]model.Op, error) {
|
||||
|
||||
Reference in new issue
Block a user