Files
acme-dns/pkg/nameserver/handler.go
Joona Hoikkala 5a7bc230b8 Refactoring (#325)
* Refactor core

* Re-added tests

* Small fixes

* Add tests for acmetxt cidrslice and util funcs

* Remove the last dangling reference to old logging package

* Refactoring (#327)

* chore: enable more linters and fix linter issues

* ci: enable linter checks on all branches and disable recurring checks

recurring linter checks don't make that much sense. The code & linter checks should not change on their own over night ;)

* chore: update packages

* Revert "chore: update packages"

This reverts commit 30250bf28c4b39e9e5b3af012a4e28ab036bf9af.

* chore: manually upgrade some packages

* Updated dependencies, wrote changelog entry and fixed namespace for release

* Refactoring - improving coverage (#371)

* Increase code coverage in acmedns

* More testing of ReadConfig() and its fallback mechanism

* Found that if someone put a '"' double quote into the filename that we configure zap to log to, it would cause the the JSON created to be invalid. I have replaced the JSON string with proper config

* Better handling of config options for api.TLS - we now error on an invalid value instead of silently failing.

added a basic test for api.setupTLS() (to increase test coverage)

* testing nameserver isOwnChallenge and isAuthoritative methods

* add a unit test for nameserver answerOwnChallenge

* fix linting errors

* bump go and golangci-lint versions in github actions

* Update golangci-lint.yml

Bumping github-actions workflow versions to accommodate some changes in upstream golanci-lint

* Bump Golang version to 1.23 (currently the oldest supported version)

Bump golanglint-ci to 2.0.2 and migrate the config file.

This should resolve the math/rand/v2 issue

* bump golanglint-ci action version

* Fixing up new golanglint-ci warnings and errors

---------

Co-authored-by: Joona Hoikkala <5235109+joohoi@users.noreply.github.com>

* Minor refactoring, error returns and e2e testing suite

* Add a few tests

* Fix linter and umask setting

* Update github actions

* Refine concurrency configuration for GitHub actions

* HTTP timeouts to API, and self-validation mutex to nameserver ops

---------

Co-authored-by: Florian Ritterhoff <32478819+fritterhoff@users.noreply.github.com>
Co-authored-by: Jason Playne <jason@jasonplayne.com>
2026-02-05 16:04:15 +02:00

161 lines
3.5 KiB
Go

package nameserver
import (
"fmt"
"strings"
"github.com/miekg/dns"
)
func (n *Nameserver) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
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 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
}