/* * Copyright (C) 2024 Jonni Liljamo * * 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.TypeAAAA: // NOTE: Stub m.SetRcode(r, dns.RcodeNameError) case dns.TypeCAA: handleCAARecord(&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) case dns.TypeTXT: handleTXTRecord(&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) { qName := strings.ToLower(q.Name) // TODO: This is probably not a great way to handle this, but it is what it is. For now. // "Root" Domain A. if qName == config.FeluConfig.Domain { 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(config.FeluConfig.IPv4), }) return } if index := strings.IndexByte(qName, '.'); index >= 0 { aRecord, err := db.FetchDomainARecord(qName[: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 handleCAARecord(q *dns.Question, m *dns.Msg, r *dns.Msg) { qName := strings.ToLower(q.Name) // FIXME: Figure out how CAA actually works I guess, currently this is just // a carbron copy of handleNSRecord ns := &dns.NS{ Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86400}, Ns: config.FeluConfig.Domain, } // "Root" Domain NS. if qName == config.FeluConfig.Domain { m.Answer = append(m.Answer, ns) return } if index := strings.IndexByte(qName, '.'); index >= 0 { // FIXME: other way of checking that the domain exists _, err := db.FetchDomainARecord(qName[:index]) if err != nil { m.SetRcode(r, dns.RcodeNameError) } else { m.Answer = append(m.Answer, ns) } } else { m.SetRcode(r, dns.RcodeNameError) } } func handleNSRecord(q *dns.Question, m *dns.Msg, r *dns.Msg) { qName := strings.ToLower(q.Name) ns := &dns.NS{ Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86400}, Ns: config.FeluConfig.Domain, } // "Root" Domain NS. if qName == config.FeluConfig.Domain { m.Answer = append(m.Answer, ns) return } if index := strings.IndexByte(qName, '.'); index >= 0 { // FIXME: other way of checking that the domain exists _, err := db.FetchDomainARecord(qName[: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) { qName := strings.ToLower(q.Name) soa := &dns.SOA{ Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 7200}, Ns: config.FeluConfig.Domain, Mbox: config.FeluConfig.SOAEmail, Serial: 2024100301, Refresh: 86400, Retry: 7200, Expire: 3600000, Minttl: 86400, } // "Root" Domain SOA. if qName == config.FeluConfig.Domain { m.Answer = append(m.Answer, soa) return } if index := strings.IndexByte(qName, '.'); index >= 0 { // FIXME: other way of checking that the domain exists _, err := db.FetchDomainARecord(qName[:index]) if err != nil { m.SetRcode(r, dns.RcodeNameError) } else { m.Answer = append(m.Answer, soa) } } else { m.SetRcode(r, dns.RcodeNameError) } } func handleTXTRecord(q *dns.Question, m *dns.Msg, r *dns.Msg) { qName := strings.ToLower(q.Name) // NOTE: This handles only _acme-challenge queries expectedPrefix := "_acme-challenge." if !strings.HasPrefix(qName, expectedPrefix) { m.SetRcode(r, dns.RcodeNameError) return } qNameWithoutPrefix, ok := strings.CutPrefix(qName, expectedPrefix) if !ok { m.SetRcode(r, dns.RcodeFormatError) return } var ddnsDomain string if index := strings.IndexByte(qNameWithoutPrefix, '.'); index >= 0 { ddnsDomain = qNameWithoutPrefix[:index] } else { m.SetRcode(r, dns.RcodeNameError) return } txtRecord, err := db.FetchDomainAcmeChallenge(ddnsDomain) if err != nil { m.SetRcode(r, dns.RcodeNameError) } else { m.Answer = append(m.Answer, &dns.TXT{ Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{txtRecord}, }) } }