274 lines
5.7 KiB
Go
274 lines
5.7 KiB
Go
package nameserver
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func (n *Nameserver) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|
|
|
if len(r.Question) == 1 {
|
|
q := r.Question[0]
|
|
if q.Qtype == dns.TypeAXFR || q.Qtype == dns.TypeIXFR { // Get remote IP
|
|
remoteIP, _, err := net.SplitHostPort(w.RemoteAddr().String())
|
|
if err != nil {
|
|
n.Logger.Errorw("Failed to parse remote address", "err", err)
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
m.Rcode = dns.RcodeRefused
|
|
_ = w.WriteMsg(m)
|
|
return
|
|
}
|
|
|
|
// Check if remote IP is in slave list
|
|
allowed := false
|
|
for _, slave := range n.Config.General.SlaveHosts {
|
|
if remoteIP == slave {
|
|
allowed = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !allowed {
|
|
n.Logger.Warnw("AXFR/IXFR request denied", "remote", remoteIP)
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
m.Rcode = dns.RcodeRefused
|
|
_ = w.WriteMsg(m)
|
|
return
|
|
}
|
|
n.handleAXFR(w, r)
|
|
return
|
|
}
|
|
}
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
// handle edns0
|
|
opt := r.IsEdns0()
|
|
if opt != nil {
|
|
if opt.Version() != 0 {
|
|
// Only EDNS0 is standardized
|
|
m.Rcode = dns.RcodeBadVers
|
|
m.SetEdns0(512, false)
|
|
} else {
|
|
// We can safely do this as we know that we're not setting other OPT RRs within acme-dns.
|
|
m.SetEdns0(512, false)
|
|
if r.Opcode == dns.OpcodeQuery {
|
|
n.readQuery(m)
|
|
}
|
|
}
|
|
} else {
|
|
if r.Opcode == dns.OpcodeQuery {
|
|
n.readQuery(m)
|
|
}
|
|
}
|
|
_ = w.WriteMsg(m)
|
|
}
|
|
|
|
func (n *Nameserver) readQuery(m *dns.Msg) {
|
|
var authoritative = false
|
|
for _, que := range m.Question {
|
|
if rr, rc, auth, err := n.answer(que); err == nil {
|
|
if auth {
|
|
authoritative = auth
|
|
}
|
|
m.Rcode = rc
|
|
m.Answer = append(m.Answer, rr...)
|
|
}
|
|
}
|
|
m.Authoritative = authoritative
|
|
if authoritative {
|
|
if m.Rcode == dns.RcodeNameError {
|
|
m.Ns = append(m.Ns, n.SOA)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *Nameserver) answer(q dns.Question) ([]dns.RR, int, bool, error) {
|
|
var rcode int
|
|
var err error
|
|
var txtRRs []dns.RR
|
|
loweredName := strings.ToLower(q.Name)
|
|
var authoritative = n.isAuthoritative(loweredName)
|
|
if !n.isOwnChallenge(loweredName) && !n.answeringForDomain(loweredName) {
|
|
rcode = dns.RcodeNameError
|
|
}
|
|
r, _ := n.getRecord(loweredName, q.Qtype)
|
|
if q.Qtype == dns.TypeTXT {
|
|
if n.isOwnChallenge(loweredName) {
|
|
txtRRs, err = n.answerOwnChallenge(q)
|
|
} else {
|
|
txtRRs, err = n.answerTXT(q)
|
|
}
|
|
if err == nil {
|
|
r = append(r, txtRRs...)
|
|
}
|
|
}
|
|
if q.Qtype == dns.TypeSOA {
|
|
r = append(r, n.SOA)
|
|
}
|
|
if len(r) > 0 {
|
|
// Make sure that we return NOERROR if there were dynamic records for the domain
|
|
rcode = dns.RcodeSuccess
|
|
}
|
|
n.Logger.Debugw("Answering question for domain",
|
|
"qtype", dns.TypeToString[q.Qtype],
|
|
"domain", q.Name,
|
|
"rcode", dns.RcodeToString[rcode])
|
|
return r, rcode, authoritative, nil
|
|
}
|
|
|
|
func (n *Nameserver) answerTXT(q dns.Question) ([]dns.RR, error) {
|
|
var ra []dns.RR
|
|
subdomain := sanitizeDomainQuestion(q.Name)
|
|
atxt, err := n.DB.GetTXTForDomain(subdomain)
|
|
if err != nil {
|
|
n.Logger.Errorw("Error while trying to get record",
|
|
"error", err.Error())
|
|
return ra, err
|
|
}
|
|
for _, v := range atxt {
|
|
if len(v) > 0 {
|
|
r := new(dns.TXT)
|
|
r.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}
|
|
r.Txt = append(r.Txt, v)
|
|
ra = append(ra, r)
|
|
}
|
|
}
|
|
return ra, nil
|
|
}
|
|
|
|
func (n *Nameserver) isAuthoritative(name string) bool {
|
|
if n.answeringForDomain(name) {
|
|
return true
|
|
}
|
|
off := 0
|
|
for {
|
|
i, next := dns.NextLabel(name, off)
|
|
if next {
|
|
return false
|
|
}
|
|
off = i
|
|
if n.answeringForDomain(name[off:]) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *Nameserver) isOwnChallenge(name string) bool {
|
|
if strings.HasPrefix(name, "_acme-challenge.") {
|
|
domain := name[16:]
|
|
if domain == n.OwnDomain {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// answeringForDomain checks if we have any records for a domain
|
|
func (n *Nameserver) answeringForDomain(name string) bool {
|
|
if n.OwnDomain == name {
|
|
return true
|
|
}
|
|
_, ok := n.Domains[name]
|
|
return ok
|
|
}
|
|
|
|
func (n *Nameserver) getRecord(name string, qtype uint16) ([]dns.RR, error) {
|
|
var rr []dns.RR
|
|
var cnames []dns.RR
|
|
domain, ok := n.Domains[name]
|
|
if !ok {
|
|
return rr, fmt.Errorf("no records for domain %s", name)
|
|
}
|
|
for _, ri := range domain.Records {
|
|
if ri.Header().Rrtype == qtype {
|
|
rr = append(rr, ri)
|
|
}
|
|
if ri.Header().Rrtype == dns.TypeCNAME {
|
|
cnames = append(cnames, ri)
|
|
}
|
|
}
|
|
if len(rr) == 0 {
|
|
return cnames, nil
|
|
}
|
|
return rr, nil
|
|
}
|
|
|
|
func (n *Nameserver) handleAXFR(w dns.ResponseWriter, r *dns.Msg) {
|
|
|
|
if len(r.Question) == 0 {
|
|
return
|
|
}
|
|
|
|
zone := dns.Fqdn(r.Question[0].Name)
|
|
|
|
records, ok := n.Domains[zone]
|
|
if !ok {
|
|
m := new(dns.Msg)
|
|
m.SetRcode(r, dns.RcodeNameError)
|
|
_ = w.WriteMsg(m)
|
|
return
|
|
}
|
|
|
|
// AXFR muss über Transfer laufen
|
|
tr := new(dns.Transfer)
|
|
|
|
c := make(chan *dns.Envelope)
|
|
|
|
go func() {
|
|
defer close(c)
|
|
|
|
var rr []dns.RR
|
|
|
|
// Start SOA
|
|
rr = append(rr, n.SOA)
|
|
|
|
// NS
|
|
rr = append(rr, records.NS...)
|
|
|
|
// Andere Records
|
|
// rr = append(rr, filterSOA(records.Records)...)
|
|
rr = append(rr, records.Records...)
|
|
|
|
// TXT Records nur für diese Zone!
|
|
txtRecords, err := n.DB.GetTXTForAllDomains()
|
|
if err == nil {
|
|
for _, rec := range txtRecords {
|
|
if rec.Value == "" {
|
|
continue
|
|
}
|
|
|
|
fqdn := dns.Fqdn(rec.Subdomain + "." + zone)
|
|
|
|
txtRR := &dns.TXT{
|
|
Hdr: dns.RR_Header{
|
|
Name: fqdn,
|
|
Rrtype: dns.TypeTXT,
|
|
Class: dns.ClassINET,
|
|
Ttl: 1,
|
|
},
|
|
Txt: []string{rec.Value},
|
|
}
|
|
|
|
rr = append(rr, txtRR)
|
|
|
|
n.Logger.Debugw("handleAXFR TXT Record", "subdomain", rec.Subdomain, "value", rec.Value, "fqdn", fqdn)
|
|
rr = append(rr, txtRR)
|
|
}
|
|
} else {
|
|
n.Logger.Errorw("Failed to get TXT records for AXFR", "error", err)
|
|
}
|
|
|
|
// End SOA
|
|
rr = append(rr, n.SOA)
|
|
|
|
c <- &dns.Envelope{RR: rr}
|
|
}()
|
|
|
|
_ = tr.Out(w, r, c)
|
|
}
|