package engine import ( "encoding/json" "time" "jarl/internal/model" ) // Operation kinds. const ( OpZoneCreate = "zone.create" OpZoneUpdate = "zone.update" OpZoneDelete = "zone.delete" OpRecordCreate = "record.create" OpRecordUpdate = "record.update" OpRecordDelete = "record.delete" OpRRSetReplace = "rrset.replace" OpViewCreate = "view.create" OpViewUpdate = "view.update" OpViewDelete = "view.delete" OpViewReorder = "view.reorder" OpACLCreate = "acl.create" OpACLUpdate = "acl.update" OpACLDelete = "acl.delete" OpTSIGCreate = "tsig.create" OpTSIGUpdate = "tsig.update" OpTSIGDelete = "tsig.delete" OpOptionCreate = "option.create" OpOptionUpdate = "option.update" OpOptionDelete = "option.delete" OpSettingsUpdate = "settings.update" OpBlocklistCreate = "blocklist.create" OpBlocklistUpdate = "blocklist.update" OpBlocklistDelete = "blocklist.delete" OpBlockRuleCreate = "blockrule.create" OpBlockRuleUpdate = "blockrule.update" OpBlockRuleDelete = "blockrule.delete" OpBlockingPause = "blocking.pause" OpBlockStepCreate = "blockstep.create" OpBlockStepUpdate = "blockstep.update" OpBlockStepDelete = "blockstep.delete" OpClientGroupCreate = "clientgroup.create" OpClientGroupUpdate = "clientgroup.update" OpClientGroupDelete = "clientgroup.delete" // Requests only: disabling and enabling records, by ID or by match // (zone, name, type and optionally data), become record.update // operations. OpRecordDisable = "record.disable" OpRecordEnable = "record.enable" // Acknowledging an exposure finding (docs/exposure.md), and taking it // back, become view.update or settings.update operations. OpExposureAck = "exposure.ack" OpExposureUnack = "exposure.unack" // Linked zones (docs/views.md#linked-zones): requests that expand // into zone and record updates. OpZoneLink = "zone.link" OpZoneUnlink = "zone.unlink" // Filling a reverse zone with the PTR records of its view's A and // AAAA records (docs/reverse.md) becomes record.create operations. OpZoneFillPTR = "zone.fill_ptr" // Dismissing a reverse zone suggestion, and taking that back, become // view.update or settings.update operations. OpReverseDismiss = "reverse.dismiss" OpReverseUndismiss = "reverse.undismiss" ) // DismissInput dismisses a reverse zone suggestion: a network in a view // (by ID or name), or the server's (neither given). type DismissInput struct { ViewID int64 `json:"view_id,omitempty"` View string `json:"view,omitempty"` Prefix string `json:"prefix"` } // AckInput acknowledges (or un-acknowledges) an exposure finding of a view // (by ID or name), or of the server (neither given). type AckInput struct { ViewID int64 `json:"view_id,omitempty"` View string `json:"view,omitempty"` Code string `json:"code"` Reason string `json:"reason,omitempty"` } // OpKinds lists all operation kinds. var OpKinds = []string{OpZoneCreate, OpZoneUpdate, OpZoneDelete, OpRecordCreate, OpRecordUpdate, OpRecordDelete, OpRRSetReplace, OpViewCreate, OpViewUpdate, OpViewDelete, OpViewReorder, OpACLCreate, OpACLUpdate, OpACLDelete, OpTSIGCreate, OpTSIGUpdate, OpTSIGDelete, OpOptionCreate, OpOptionUpdate, OpOptionDelete, OpSettingsUpdate, OpBlocklistCreate, OpBlocklistUpdate, OpBlocklistDelete, OpBlockRuleCreate, OpBlockRuleUpdate, OpBlockRuleDelete, OpBlockingPause, OpBlockStepCreate, OpBlockStepUpdate, OpBlockStepDelete, OpClientGroupCreate, OpClientGroupUpdate, OpClientGroupDelete} // Request is an operation as submitted by a client. Requests are expanded // into one or more stored operations (for example an A record with "ptr" // also creates the matching PTR record). type Request struct { Op string `json:"op"` // ID is the object to update or delete. ID int64 `json:"id,omitempty"` // BaseVersion, if set, makes the request fail when the object's // version differs (optimistic locking). Otherwise the version at // staging time is recorded. BaseVersion int `json:"base_version,omitempty"` Data json.RawMessage `json:"data,omitempty"` } // NewRequest builds a request with data marshaled from an input struct. func NewRequest(op string, id int64, data any) Request { r := Request{Op: op, ID: id} if data != nil { b, err := json.Marshal(data) if err != nil { panic(err) } r.Data = b } return r } // SOAInput sets SOA fields of a primary zone. type SOAInput struct { MName *string `json:"mname,omitempty"` RName *string `json:"rname,omitempty"` // mailbox name or email address Serial *uint32 `json:"serial,omitempty"` Refresh *uint32 `json:"refresh,omitempty"` Retry *uint32 `json:"retry,omitempty"` Expire *uint32 `json:"expire,omitempty"` Minimum *uint32 `json:"minimum,omitempty"` } // ZoneInput creates or updates a zone. type ZoneInput struct { // Name is the zone name. For create, a CIDR prefix such as // "192.168.1.0/24" creates the matching reverse zone. Name *string `json:"name,omitempty"` ViewID *int64 `json:"view_id,omitempty"` View *string `json:"view,omitempty"` // view name, alternative to view_id Type *string `json:"type,omitempty"` InView *string `json:"in_view,omitempty"` File *string `json:"file,omitempty"` SOA *SOAInput `json:"soa,omitempty"` DefaultTTL *uint32 `json:"default_ttl,omitempty"` SerialPolicy *string `json:"serial_policy,omitempty"` // DNSSECPolicy turns signing on ("default" or a custom policy's // name), starts turning it off ("insecure") or, once that has // finished, off (""). DNSSECPolicy *string `json:"dnssec_policy,omitempty"` Comment *string `json:"comment,omitempty"` // Disabled keeps the zone in Jarl but out of named's configuration. Disabled *bool `json:"disabled,omitempty"` // LinkGroup is the zone's link group (0: not linked). Set by zone.link // and zone.unlink; requests use those. LinkGroup *int64 `json:"link_group,omitempty"` // LinkWith (create only) links the new zone with the zone of the same // name in this view, copying its shared records (or all of its // records, when it isn't linked yet). LinkWith string `json:"link_with,omitempty"` // AutoPTR keeps the PTR records of the zone's A and AAAA records in // step (docs/reverse.md). AutoPTR *bool `json:"auto_ptr,omitempty"` // Fill (create only, reverse zones) adds a PTR record for every // address of the view's A and AAAA records in the zone's range. Fill bool `json:"fill,omitempty"` // Create only: name servers for the apex NS records (default: the // configured default name servers), additional records, and a zone to // copy records from. Nameservers []string `json:"nameservers,omitempty"` Records []RecordInput `json:"records,omitempty"` CloneFrom int64 `json:"clone_from,omitempty"` // Options (create only) are zone options to set, e.g. primaries for a // secondary zone or forwarders for a forward zone. Options []ZoneOption `json:"options,omitempty"` } // ZoneOption is an option given when creating a zone. type ZoneOption struct { Name string `json:"name"` Value string `json:"value"` Comment string `json:"comment,omitempty"` } // RecordInput creates, updates or (by match) deletes a record. type RecordInput struct { ZoneID int64 `json:"zone_id,omitempty"` Zone string `json:"zone,omitempty"` // zone name, alternative to zone_id View string `json:"view,omitempty"` // view of Zone Name *string `json:"name,omitempty"` Type *string `json:"type,omitempty"` TTL model.Opt[uint32] `json:"ttl,omitzero"` // null: use the zone default Data *string `json:"data,omitempty"` Comment *string `json:"comment,omitempty"` // Disabled keeps the record in Jarl but out of the zone. Disabled *bool `json:"disabled,omitempty"` // PTR also creates (or deletes, disables or enables) the matching PTR // record in the reverse zone, for A and AAAA records. On an update that // changes nothing else it adds the PTR record if it is missing. In a // linked zone, every linked view's reverse zone counts. PTR bool `json:"ptr,omitempty"` // MissingOK makes a delete-by-match succeed when nothing matches. MissingOK bool `json:"missing_ok,omitempty"` // Local (requests, linked zones): true keeps the record in this view // only (an override: the whole name and type becomes local here); // false shares it with the linked zones. Unset: like the records of // the same name and type, shared when there are none. Local *bool `json:"local,omitempty"` // SharedID (stored operations) ties the copies of a shared record // together; 0 makes it local. SharedID *int64 `json:"shared_id,omitempty"` } // LinkInput links zones of the same name in several views, or (unlink) // takes one out of its link. type LinkInput struct { ZoneID int64 `json:"zone_id,omitempty"` Zone string `json:"zone,omitempty"` View string `json:"view,omitempty"` // With are the views whose zone of the same name to link with (link // only; default: every view that has one). With []string `json:"with,omitempty"` } // RRSetInput replaces all records of one name and type. type RRSetInput struct { ZoneID int64 `json:"zone_id,omitempty"` Zone string `json:"zone,omitempty"` View string `json:"view,omitempty"` Name string `json:"name"` Type string `json:"type"` TTL model.Opt[uint32] `json:"ttl,omitzero"` Data []string `json:"data"` Comment *string `json:"comment,omitempty"` // NewIDs are allocated for records that need to be created. NewIDs []int64 `json:"new_ids,omitempty"` // Local (requests): like RecordInput.Local, for the whole set. Local *bool `json:"local,omitempty"` // Shared (stored operations, linked zones): the records are shared // (true) or local (false). Shared *bool `json:"shared,omitempty"` } // ViewInput creates or updates a view. type ViewInput struct { Name *string `json:"name,omitempty"` MatchClients *[]string `json:"match_clients,omitempty"` MatchDestinations *[]string `json:"match_destinations,omitempty"` MatchRecursiveOnly model.Opt[bool] `json:"match_recursive_only,omitzero"` Comment *string `json:"comment,omitempty"` Position *int `json:"position,omitempty"` // AdoptZones (create) moves all zones that are not in a view into the // new view; named requires every zone to be in a view once views // are used. AdoptZones bool `json:"adopt_zones,omitempty"` // Cascade (delete) also deletes the view's zones. Cascade bool `json:"cascade,omitempty"` // Exposure is "auto" (or ""), "public" or "private" (docs/exposure.md). Exposure *string `json:"exposure,omitempty"` Acknowledged *[]model.Acknowledgement `json:"acknowledged,omitempty"` // ReverseSuggestions turns the reverse zone suggestions for the // view's addresses on or off (docs/reverse.md). ReverseSuggestions *bool `json:"reverse_suggestions,omitempty"` // DismissedReverse are the networks whose suggestion was dismissed. DismissedReverse *[]string `json:"dismissed_reverse,omitempty"` } // ReorderInput sets the order of views. type ReorderInput struct { IDs []int64 `json:"ids"` } // ACLInput creates or updates an ACL. type ACLInput struct { Name *string `json:"name,omitempty"` Elements *[]string `json:"elements,omitempty"` Comment *string `json:"comment,omitempty"` } // TSIGInput creates or updates a TSIG key. A missing secret on create is // generated. type TSIGInput struct { Name *string `json:"name,omitempty"` Algorithm *string `json:"algorithm,omitempty"` Secret *string `json:"secret,omitempty"` Comment *string `json:"comment,omitempty"` } // OptionInput creates or updates an option. type OptionInput struct { Scope *string `json:"scope,omitempty"` // options, view, zone, toplevel ScopeID *int64 `json:"scope_id,omitempty"` // view or zone ID Name *string `json:"name,omitempty"` Value *string `json:"value,omitempty"` Comment *string `json:"comment,omitempty"` Position *int `json:"position,omitempty"` } // SettingsInput updates Jarl's settings. type SettingsInput struct { DefaultTTL *uint32 `json:"default_ttl,omitempty"` Nameservers *[]string `json:"nameservers,omitempty"` RName *string `json:"rname,omitempty"` Refresh *uint32 `json:"refresh,omitempty"` Retry *uint32 `json:"retry,omitempty"` Expire *uint32 `json:"expire,omitempty"` NegativeTTL *uint32 `json:"negative_ttl,omitempty"` SerialPolicy *string `json:"serial_policy,omitempty"` // Exposure and Acknowledged are the server's (docs/exposure.md). Exposure *string `json:"exposure,omitempty"` Acknowledged *[]model.Acknowledgement `json:"acknowledged,omitempty"` // DismissedReverse are dismissed reverse zone suggestions (without // views). DismissedReverse *[]string `json:"dismissed_reverse,omitempty"` } // DeleteInput carries options for delete operations. type DeleteInput struct { Cascade bool `json:"cascade,omitempty"` } // BlocklistInput creates or updates a blocklist. Views are given by name // (Views) or ID (ViewIDs); an empty list means every view that offers // recursion. type BlocklistInput struct { Name *string `json:"name,omitempty"` URL *string `json:"url,omitempty"` Views *[]string `json:"views,omitempty"` ViewIDs *[]int64 `json:"view_ids,omitempty"` Disabled *bool `json:"disabled,omitempty"` Comment *string `json:"comment,omitempty"` // Step is the step the list starts at, by name ("normal" or a step // above it) or ID (StepID; 0 is normal). Step *string `json:"step,omitempty"` StepID *int64 `json:"step_id,omitempty"` } // BlockStepInput creates or updates a blocking step above Normal. // Position orders the steps (new steps go on top). type BlockStepInput struct { Name *string `json:"name,omitempty"` Position *int `json:"position,omitempty"` Comment *string `json:"comment,omitempty"` } // ClientGroupInput creates or updates a client group. Members are // addresses, prefixes or ACL names; the step is given by name ("off", // "normal" or a step) or ID; views as for BlocklistInput. type ClientGroupInput struct { Name *string `json:"name,omitempty"` Members *[]string `json:"members,omitempty"` Step *string `json:"step,omitempty"` StepID *int64 `json:"step_id,omitempty"` Views *[]string `json:"views,omitempty"` ViewIDs *[]int64 `json:"view_ids,omitempty"` Comment *string `json:"comment,omitempty"` } // BlockRuleInput creates or updates a block or allow rule. Views as for // BlocklistInput. type BlockRuleInput struct { Name *string `json:"name,omitempty"` Action *string `json:"action,omitempty"` // block (default) or allow Exact *bool `json:"exact,omitempty"` // only the name, not the names below it Views *[]string `json:"views,omitempty"` ViewIDs *[]int64 `json:"view_ids,omitempty"` Comment *string `json:"comment,omitempty"` } // BlockingPauseInput pauses blocking until a time, or resumes it (null). type BlockingPauseInput struct { Until *time.Time `json:"until"` // Indefinitely pauses until someone resumes blocking (Until is nil). Indefinitely bool `json:"indefinitely,omitempty"` } // MaxBlockingPause is the longest pause: blocking shouldn't stay off by // mistake (disable lists to stop using them). const MaxBlockingPause = 24 * time.Hour