/*
* Copyright (C) 2024 Jonni Liljamo <jonni@liljamo.com>
*
* This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
* more information.
*/
package dns
import (
"log/slog"
"net"
"strings"
"git.src.quest/~liljamo/felu/internal/config"
"git.src.quest/~liljamo/felu/internal/db"
"github.com/miekg/dns"
)
func parseQuery(m *dns.Msg, r *dns.Msg) {
for _, q := range m.Question {
slog.Info("Got Query",
slog.Any("id", r.Id),
slog.String("type", dns.TypeToString[q.Qtype]),
slog.String("qname", q.Name),
)
switch q.Qtype {
case dns.TypeA:
handleARecord(&q, m, r)
case dns.TypeCNAME:
// NOTE: This is stubbed like this to make things like lego not shit themselves if they get NOTIMP.
m.SetRcode(r, dns.RcodeNameError)
case dns.TypeNS:
handleNSRecord(&q, m, r)
case dns.TypeSOA:
handleSOARecord(&q, m, r)
default:
m.SetRcode(r, dns.RcodeNotImplemented)
}
slog.Info("Responding to Query",
slog.Any("id", r.Id),
slog.String("rcode", dns.RcodeToString[m.Rcode]),
)
}
}
func handleARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
if index := strings.IndexByte(q.Name, '.'); index >= 0 {
aRecord, err := db.FetchDomainARecord(strings.ToLower(q.Name[:index]))
if err != nil {
m.SetRcode(r, dns.RcodeNameError)
} else {
m.Answer = append(m.Answer, &dns.A{
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},
A: net.ParseIP(aRecord),
})
}
} else {
m.SetRcode(r, dns.RcodeNameError)
}
}
func handleNSRecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
ns := &dns.NS{
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86400},
Ns: dns.Fqdn(config.FeluConfig.DDNSDomain),
}
// "Root" DDNS domain NS.
if q.Name == dns.Fqdn(config.FeluConfig.DDNSDomain) {
m.Answer = append(m.Answer, ns)
return
}
if index := strings.IndexByte(q.Name, '.'); index >= 0 {
// FIXME: other way of checking that the domain exists
_, err := db.FetchDomainARecord(strings.ToLower(q.Name[:index]))
if err != nil {
m.SetRcode(r, dns.RcodeNameError)
} else {
m.Answer = append(m.Answer, ns)
}
} else {
m.SetRcode(r, dns.RcodeNameError)
}
}
func handleSOARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) {
soa := &dns.SOA{
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0},
Ns: dns.Fqdn(config.FeluConfig.DDNSDomain),
Mbox: dns.Fqdn(config.FeluConfig.SOAEmail),
Serial: 2024100301,
Refresh: 86400,
Retry: 7200,
Expire: 3600000,
Minttl: 172800,
}
// "Root" DDNS domain SOA.
if q.Name == dns.Fqdn(config.FeluConfig.DDNSDomain) {
m.Answer = append(m.Answer, soa)
return
}
if index := strings.IndexByte(q.Name, '.'); index >= 0 {
// FIXME: other way of checking that the domain exists
_, err := db.FetchDomainARecord(strings.ToLower(q.Name[:index]))
if err != nil {
m.SetRcode(r, dns.RcodeNameError)
} else {
m.Answer = append(m.Answer, soa)
}
} else {
m.SetRcode(r, dns.RcodeNameError)
}
}