pancheri/zone_config.go

175 lines
4.7 KiB
Go
Raw Normal View History

package pancheri
import (
"crypto/sha256"
"errors"
"fmt"
"gopkg.in/yaml.v2"
"net"
"os"
2023-10-02 17:48:31 +00:00
"strings"
)
type ZoneConfig struct {
Zone struct {
Root string `yaml:"root"`
Records []CfgRecord `yaml:"records"`
} `yaml:"zone"`
}
type CfgRecord struct {
RecordType string `yaml:"type"`
Domains []string `yaml:"domains"`
Ipv4 net.IP `yaml:"ipv4"`
Ipv6 net.IP `yaml:"ipv6"`
Target string `yaml:"target"`
Content []string `yaml:"content"`
TTL uint `yaml:"ttl"`
}
func LoadZone(path string) (*Zone, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
var cfg ZoneConfig
decoder := yaml.NewDecoder(f)
err = decoder.Decode(&cfg)
if err != nil {
return nil, err
}
err = f.Close()
if err != nil {
return nil, err
}
reduced, err := yaml.Marshal(cfg)
if err != nil {
return nil, err
}
// ReducedHash is a hash of the zonefile after it has been re-marshaled by libyaml.
// This is intended to prevent pointless changes of the serial when only minor formatting changes have been made
// ex. adding an extra space somewhere won't trigger a new serial
reducedHash := fmt.Sprintf("%x", sha256.Sum256(reduced))
// The serial is the first few bytes of this converted to an integer
// validate and convert
zone := Zone{
Root: cfg.Zone.Root,
ReducedHash: reducedHash,
Zonefile: path,
ARecords: make(map[string]RecordA),
AAAARecords: make(map[string]RecordAAAA),
CNAMERecords: make(map[string]RecordCNAME),
TXTRecords: make(map[string]RecordTXT),
}
for _, record := range cfg.Zone.Records {
if record.RecordType == RuleTypeA {
// req.d fields: in, ip, ttl
if len(record.Domains) == 0 {
return nil, errors.New("A record must contain at least one domain")
}
if record.Ipv4 == nil {
return nil, errors.New("A record must contain ipv4 address")
}
if record.TTL == 0 {
return nil, errors.New("A record TTL cannot be 0 or empty")
}
for _, domain := range record.Domains {
2023-10-02 17:48:31 +00:00
if !strings.HasSuffix(domain, ".") {
domain = domain + "." + cfg.Zone.Root + "."
}
if _, ok := zone.ARecords[domain]; ok {
return nil, errors.New("duplicate A record " + domain)
}
zone.ARecords[domain] = RecordA{
In: domain,
Ip: record.Ipv4.To4(),
TTL: record.TTL,
}
}
} else if record.RecordType == RuleTypeAAAA {
// req.d fields: in, ip, ttl
if len(record.Domains) == 0 {
return nil, errors.New("AAAA record must contain at least one domain")
}
if record.Ipv6 == nil {
return nil, errors.New("AAAA record must contain ipv6 address")
}
if record.TTL == 0 {
return nil, errors.New("AAAA record TTL cannot be 0 or empty")
}
for _, domain := range record.Domains {
2023-10-02 17:48:31 +00:00
if !strings.HasSuffix(domain, ".") {
domain = domain + "." + cfg.Zone.Root + "."
}
if _, ok := zone.AAAARecords[domain]; ok {
return nil, errors.New("duplicate AAAA record " + domain)
}
zone.AAAARecords[domain] = RecordAAAA{
In: domain,
Ip: record.Ipv6,
TTL: record.TTL,
}
}
} else if record.RecordType == RuleTypeCNAME {
// req.d fields: in, ip, ttl
if len(record.Domains) == 0 {
return nil, errors.New("CNAME record must contain at least one domain")
}
if len(record.Target) == 0 {
return nil, errors.New("CNAME record must contain target")
}
if record.TTL == 0 {
return nil, errors.New("CNAME record TTL cannot be 0 or empty")
}
if !strings.HasSuffix(record.Target, ".") {
record.Target = record.Target + "." + cfg.Zone.Root + "."
}
for _, domain := range record.Domains {
2023-10-02 17:48:31 +00:00
if !strings.HasSuffix(domain, ".") {
domain = domain + "." + cfg.Zone.Root + "."
}
if _, ok := zone.CNAMERecords[domain]; ok {
return nil, errors.New("duplicate CNAME record " + domain)
}
zone.CNAMERecords[domain] = RecordCNAME{
In: domain,
Target: record.Target,
TTL: record.TTL,
}
}
} else if record.RecordType == RuleTypeTXT {
// req.d fields: in, content, ttl
if len(record.Domains) == 0 {
return nil, errors.New("TXT record must contain at least one domain")
}
if len(record.Content) == 0 {
return nil, errors.New("TXT record must contain content")
}
if record.TTL == 0 {
return nil, errors.New("TXT record TTL cannot be 0 or empty")
}
for _, domain := range record.Domains {
2023-10-02 17:48:31 +00:00
if !strings.HasSuffix(domain, ".") {
domain = domain + "." + cfg.Zone.Root + "."
}
if _, ok := zone.TXTRecords[domain]; ok {
return nil, errors.New("duplicate TXT record " + domain)
}
zone.TXTRecords[domain] = RecordTXT{
In: domain,
Content: record.Content,
TTL: record.TTL,
}
}
}
}
return &zone, nil
}