327 lines
10 KiB
Go
327 lines
10 KiB
Go
// This file is part of the happyDomain (R) project.
|
|
// Copyright (c) 2020-2024 happyDomain
|
|
// Authors: Pierre-Olivier Mercier, et al.
|
|
//
|
|
// This program is offered under a commercial and under the AGPL license.
|
|
// For commercial licensing, contact us at <contact@happydomain.org>.
|
|
//
|
|
// For AGPL licensing:
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package helpers
|
|
|
|
import (
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"git.happydns.org/happyDomain/model"
|
|
)
|
|
|
|
// SplitN splits a string into N sized string chunks.
|
|
// This function is a copy of https://github.com/miekg/dns/blob/master/types.go#L1509
|
|
// awaiting its exportation
|
|
func SplitN(s string, n int) []string {
|
|
if len(s) < n {
|
|
return []string{s}
|
|
}
|
|
sx := []string{}
|
|
p, i := 0, n
|
|
for {
|
|
if i <= len(s) {
|
|
sx = append(sx, s[p:i])
|
|
} else {
|
|
sx = append(sx, s[p:])
|
|
break
|
|
|
|
}
|
|
p, i = p+n, i+n
|
|
}
|
|
|
|
return sx
|
|
}
|
|
|
|
// DomainFQDN normalizes the domain by adding the origin if it is relative (not
|
|
// ended by .).
|
|
func DomainFQDN(subdomain string, origin string) string {
|
|
if len(subdomain) > 0 && subdomain[len(subdomain)-1] == '.' {
|
|
return subdomain
|
|
} else if subdomain == "" || subdomain == "@" {
|
|
return origin
|
|
} else {
|
|
return subdomain + "." + origin
|
|
}
|
|
}
|
|
|
|
// DomainJoin appends each relative domains passed as argument.
|
|
func DomainJoin(domains ...string) (ret string) {
|
|
for _, d := range domains {
|
|
if d == "@" {
|
|
break
|
|
} else if d != "" {
|
|
ret += "." + d
|
|
}
|
|
|
|
if len(ret) > 0 && ret[len(ret)-1] == '.' {
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(ret) >= 1 {
|
|
ret = ret[1:]
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// DomainRelative strips the end of the given FQDN if it is relative to origin.
|
|
func DomainRelative(subdomain string, origin string) string {
|
|
if !strings.HasSuffix(origin, ".") {
|
|
origin += "."
|
|
}
|
|
|
|
if strings.HasSuffix(subdomain, origin) {
|
|
subdomain = strings.TrimSuffix(strings.TrimSuffix(subdomain, origin), ".")
|
|
}
|
|
|
|
if subdomain == "" {
|
|
return "@"
|
|
}
|
|
|
|
return subdomain
|
|
}
|
|
|
|
func NewRecord(domain string, rrtype string, ttl uint32, origin string) happydns.Record {
|
|
rdtype := dns.StringToType[rrtype]
|
|
|
|
rr := dns.TypeToRR[rdtype]()
|
|
|
|
// Fill in the header.
|
|
rr.Header().Name = DomainFQDN(domain, origin)
|
|
rr.Header().Rrtype = rdtype
|
|
rr.Header().Class = dns.ClassINET
|
|
rr.Header().Ttl = ttl
|
|
|
|
return rr
|
|
}
|
|
|
|
func ParseRecord(input, origin string) (happydns.Record, error) {
|
|
zp := dns.NewZoneParser(strings.NewReader(input), origin, "")
|
|
zp.SetDefaultTTL(0)
|
|
zp.SetIncludeAllowed(false)
|
|
|
|
rr, _ := zp.Next()
|
|
if err := zp.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rr.Header().Rrtype == dns.TypeTXT {
|
|
return happydns.NewTXT(rr.(*dns.TXT)), nil
|
|
} else if rr.Header().Rrtype == dns.TypeSPF {
|
|
return happydns.NewSPF(rr.(*dns.SPF)), nil
|
|
}
|
|
|
|
return rr, nil
|
|
}
|
|
|
|
// RRRelative strips the end of the given RR if it is relative to origin.
|
|
func RRRelative(rr happydns.Record, origin string) happydns.Record {
|
|
if !strings.HasSuffix(origin, ".") {
|
|
origin += "."
|
|
}
|
|
|
|
// Make header relative
|
|
if strings.HasSuffix(rr.Header().Name, origin) {
|
|
rr.Header().Name = strings.TrimSuffix(strings.TrimSuffix(rr.Header().Name, origin), ".")
|
|
}
|
|
|
|
return RDataRelative(rr, origin)
|
|
}
|
|
|
|
// RRRelativeSubdomain strips the end of the given RR if it is relative to origin.
|
|
func RRRelativeSubdomain(rr happydns.Record, origin, subdomain string) happydns.Record {
|
|
if !strings.HasSuffix(origin, ".") {
|
|
origin += "."
|
|
}
|
|
|
|
subdomain = DomainFQDN(subdomain, origin)
|
|
|
|
// Make header relative
|
|
if strings.HasSuffix(rr.Header().Name, subdomain) {
|
|
rr.Header().Name = strings.TrimSuffix(strings.TrimSuffix(rr.Header().Name, subdomain), ".")
|
|
}
|
|
|
|
return RDataRelative(rr, origin)
|
|
}
|
|
|
|
// RDataRelative strips the end of the given RData if it is relative to origin.
|
|
func RDataRelative(rr happydns.Record, origin string) happydns.Record {
|
|
// Make RData relative
|
|
if ns, ok := rr.(*dns.NS); ok {
|
|
ns.Ns = DomainRelative(ns.Ns, origin)
|
|
} else if md, ok := rr.(*dns.MD); ok {
|
|
md.Md = DomainRelative(md.Md, origin)
|
|
} else if mf, ok := rr.(*dns.MF); ok {
|
|
mf.Mf = DomainRelative(mf.Mf, origin)
|
|
} else if cname, ok := rr.(*dns.CNAME); ok {
|
|
cname.Target = DomainRelative(cname.Target, origin)
|
|
} else if soa, ok := rr.(*dns.SOA); ok {
|
|
soa.Ns = DomainRelative(soa.Ns, origin)
|
|
soa.Mbox = DomainRelative(soa.Mbox, origin)
|
|
} else if mb, ok := rr.(*dns.MB); ok {
|
|
mb.Mb = DomainRelative(mb.Mb, origin)
|
|
} else if mg, ok := rr.(*dns.MG); ok {
|
|
mg.Mg = DomainRelative(mg.Mg, origin)
|
|
} else if mr, ok := rr.(*dns.MR); ok {
|
|
mr.Mr = DomainRelative(mr.Mr, origin)
|
|
} else if ptr, ok := rr.(*dns.PTR); ok {
|
|
ptr.Ptr = DomainRelative(ptr.Ptr, origin)
|
|
} else if minfo, ok := rr.(*dns.MINFO); ok {
|
|
minfo.Rmail = DomainRelative(minfo.Rmail, origin)
|
|
minfo.Email = DomainRelative(minfo.Email, origin)
|
|
} else if mx, ok := rr.(*dns.MX); ok {
|
|
mx.Mx = DomainRelative(mx.Mx, origin)
|
|
} else if rp, ok := rr.(*dns.RP); ok {
|
|
rp.Mbox = DomainRelative(rp.Mbox, origin)
|
|
rp.Txt = DomainRelative(rp.Txt, origin)
|
|
} else if afsdb, ok := rr.(*dns.AFSDB); ok {
|
|
afsdb.Hostname = DomainRelative(afsdb.Hostname, origin)
|
|
} else if rt, ok := rr.(*dns.RT); ok {
|
|
rt.Host = DomainRelative(rt.Host, origin)
|
|
} else if ptr, ok := rr.(*dns.NSAPPTR); ok {
|
|
ptr.Ptr = DomainRelative(ptr.Ptr, origin)
|
|
} else if sig, ok := rr.(*dns.SIG); ok {
|
|
sig.SignerName = DomainRelative(sig.SignerName, origin)
|
|
} else if px, ok := rr.(*dns.PX); ok {
|
|
px.Map822 = DomainRelative(px.Map822, origin)
|
|
px.Mapx400 = DomainRelative(px.Mapx400, origin)
|
|
} else if nxt, ok := rr.(*dns.NXT); ok {
|
|
nxt.NextDomain = DomainRelative(nxt.NextDomain, origin)
|
|
} else if srv, ok := rr.(*dns.SRV); ok {
|
|
srv.Target = DomainRelative(srv.Target, origin)
|
|
} else if ptr, ok := rr.(*dns.NAPTR); ok {
|
|
ptr.Replacement = DomainRelative(ptr.Replacement, origin)
|
|
} else if kx, ok := rr.(*dns.KX); ok {
|
|
kx.Exchanger = DomainRelative(kx.Exchanger, origin)
|
|
} else if dname, ok := rr.(*dns.DNAME); ok {
|
|
dname.Target = DomainRelative(dname.Target, origin)
|
|
} else if sig, ok := rr.(*dns.RRSIG); ok {
|
|
sig.SignerName = DomainRelative(sig.SignerName, origin)
|
|
} else if nxt, ok := rr.(*dns.NSEC); ok {
|
|
nxt.NextDomain = DomainRelative(nxt.NextDomain, origin)
|
|
} else if hip, ok := rr.(*dns.HIP); ok {
|
|
for i := range hip.RendezvousServers {
|
|
hip.RendezvousServers[i] = DomainRelative(hip.RendezvousServers[i], origin)
|
|
}
|
|
} else if talink, ok := rr.(*dns.TALINK); ok {
|
|
talink.PreviousName = DomainRelative(talink.PreviousName, origin)
|
|
talink.NextName = DomainRelative(talink.NextName, origin)
|
|
} else if lp, ok := rr.(*dns.LP); ok {
|
|
lp.Fqdn = DomainRelative(lp.Fqdn, origin)
|
|
}
|
|
|
|
return rr
|
|
}
|
|
|
|
// RRAbsolute transforms relative RR to absolute RR in place.
|
|
func RRAbsolute(rr happydns.Record, origin string) happydns.Record {
|
|
if origin != "" {
|
|
if !strings.HasSuffix(origin, ".") {
|
|
origin += "."
|
|
}
|
|
|
|
// Make header absolute
|
|
rr.Header().Name = DomainFQDN(rr.Header().Name, origin)
|
|
}
|
|
|
|
// Make RData absolute
|
|
if ns, ok := rr.(*dns.NS); ok {
|
|
ns.Ns = DomainFQDN(ns.Ns, origin)
|
|
} else if md, ok := rr.(*dns.MD); ok {
|
|
md.Md = DomainFQDN(md.Md, origin)
|
|
} else if mf, ok := rr.(*dns.MF); ok {
|
|
mf.Mf = DomainFQDN(mf.Mf, origin)
|
|
} else if cname, ok := rr.(*dns.CNAME); ok {
|
|
cname.Target = DomainFQDN(cname.Target, origin)
|
|
} else if soa, ok := rr.(*dns.SOA); ok {
|
|
soa.Ns = DomainFQDN(soa.Ns, origin)
|
|
soa.Mbox = DomainFQDN(soa.Mbox, origin)
|
|
} else if mb, ok := rr.(*dns.MB); ok {
|
|
mb.Mb = DomainFQDN(mb.Mb, origin)
|
|
} else if mg, ok := rr.(*dns.MG); ok {
|
|
mg.Mg = DomainFQDN(mg.Mg, origin)
|
|
} else if mr, ok := rr.(*dns.MR); ok {
|
|
mr.Mr = DomainFQDN(mr.Mr, origin)
|
|
} else if ptr, ok := rr.(*dns.PTR); ok {
|
|
ptr.Ptr = DomainFQDN(ptr.Ptr, origin)
|
|
} else if minfo, ok := rr.(*dns.MINFO); ok {
|
|
minfo.Rmail = DomainFQDN(minfo.Rmail, origin)
|
|
minfo.Email = DomainFQDN(minfo.Email, origin)
|
|
} else if mx, ok := rr.(*dns.MX); ok {
|
|
mx.Mx = DomainFQDN(mx.Mx, origin)
|
|
} else if rp, ok := rr.(*dns.RP); ok {
|
|
rp.Mbox = DomainFQDN(rp.Mbox, origin)
|
|
rp.Txt = DomainFQDN(rp.Txt, origin)
|
|
} else if afsdb, ok := rr.(*dns.AFSDB); ok {
|
|
afsdb.Hostname = DomainFQDN(afsdb.Hostname, origin)
|
|
} else if rt, ok := rr.(*dns.RT); ok {
|
|
rt.Host = DomainFQDN(rt.Host, origin)
|
|
} else if ptr, ok := rr.(*dns.NSAPPTR); ok {
|
|
ptr.Ptr = DomainFQDN(ptr.Ptr, origin)
|
|
} else if sig, ok := rr.(*dns.SIG); ok {
|
|
sig.SignerName = DomainFQDN(sig.SignerName, origin)
|
|
} else if px, ok := rr.(*dns.PX); ok {
|
|
px.Map822 = DomainFQDN(px.Map822, origin)
|
|
px.Mapx400 = DomainFQDN(px.Mapx400, origin)
|
|
} else if nxt, ok := rr.(*dns.NXT); ok {
|
|
nxt.NextDomain = DomainFQDN(nxt.NextDomain, origin)
|
|
} else if srv, ok := rr.(*dns.SRV); ok {
|
|
srv.Target = DomainFQDN(srv.Target, origin)
|
|
} else if ptr, ok := rr.(*dns.NAPTR); ok {
|
|
ptr.Replacement = DomainFQDN(ptr.Replacement, origin)
|
|
} else if kx, ok := rr.(*dns.KX); ok {
|
|
kx.Exchanger = DomainFQDN(kx.Exchanger, origin)
|
|
} else if dname, ok := rr.(*dns.DNAME); ok {
|
|
dname.Target = DomainFQDN(dname.Target, origin)
|
|
} else if sig, ok := rr.(*dns.RRSIG); ok {
|
|
sig.SignerName = DomainFQDN(sig.SignerName, origin)
|
|
} else if nxt, ok := rr.(*dns.NSEC); ok {
|
|
nxt.NextDomain = DomainFQDN(nxt.NextDomain, origin)
|
|
} else if hip, ok := rr.(*dns.HIP); ok {
|
|
for i := range hip.RendezvousServers {
|
|
hip.RendezvousServers[i] = DomainFQDN(hip.RendezvousServers[i], origin)
|
|
}
|
|
} else if talink, ok := rr.(*dns.TALINK); ok {
|
|
talink.PreviousName = DomainFQDN(talink.PreviousName, origin)
|
|
talink.NextName = DomainFQDN(talink.NextName, origin)
|
|
} else if lp, ok := rr.(*dns.LP); ok {
|
|
lp.Fqdn = DomainFQDN(lp.Fqdn, origin)
|
|
}
|
|
|
|
return rr
|
|
}
|
|
|
|
func CopyRecord(rr happydns.Record) happydns.Record {
|
|
if dnsrr, ok := rr.(dns.RR); ok {
|
|
return dns.Copy(dnsrr)
|
|
}
|
|
|
|
if copiablerr, ok := rr.(happydns.CopiableRecord); ok {
|
|
return copiablerr.Copy()
|
|
}
|
|
|
|
log.Fatalf("Type %T doesn't implement Copy method", rr)
|
|
return nil
|
|
}
|