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) }