Mail reverse DNS check, what Jarl adds to named.conf, lists one per line

jarld checks the reverse DNS of the public zones' MX and SPF addresses as the internet sees it, every 6 hours (Exposure page, jarl exposure mail, jarl_mail_reverse_dns_ok). The Server options page, jarl option generated and GET /options/generated list what Jarl writes into named.conf by itself; stats.rate_limit_log turns off the query-errors logging. Option lists of three or more elements are shown one per line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-12 21:02:11 +01:00
1 parent d33f573782
commit c27a6c10bd
30 files changed
+1095 -16

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
package server
import (
"context"
"net/http"
"time"
"jarl/internal/auth"
"jarl/internal/mailcheck"
)
// mailCheckEvery is how often jarld checks the reverse DNS of the
// addresses that send and receive mail (docs/exposure.md#mail).
const mailCheckEvery = 6 * time.Hour
// checkMail checks the reverse DNS of the public zones' mail addresses and
// keeps the report; it logs addresses that became wrong.
func (s *Server) checkMail(ctx context.Context) *mailcheck.Report {
st, err := s.store.LoadState(ctx)
if err != nil {
s.log.Warn("mail reverse DNS check: loading state", "err", err)
return nil
}
public := map[int64]bool{}
for _, x := range s.eng.Exposures(st) {
public[x.ViewID] = x.Public
}
if len(st.Views) == 0 {
public[0] = true
}
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
rep := mailcheck.Check(ctx, st, func(id int64) bool { return public[id] }, mailcheck.Resolver{Servers: s.cfg.Resolvers})
s.mail.Lock()
prev := s.mail.r
s.mail.r = rep
s.mail.Unlock()
was := map[string]bool{}
if prev != nil {
for _, r := range prev.Results {
was[r.Zone+"|"+r.Address] = r.OK
}
}
for _, r := range rep.Results {
if ok, seen := was[r.Zone+"|"+r.Address]; !r.OK && (ok || !seen) {
s.log.Warn("mail reverse DNS", "zone", r.Zone, "address", r.Address, "problem", r.Message)
}
}
return rep
}
func (s *Server) mailReport() *mailcheck.Report {
s.mail.Lock()
defer s.mail.Unlock()
return s.mail.r
}
// mailCheckNow: POST /exposure/mail checks now and returns the report.
func (s *Server) mailCheckNow(w http.ResponseWriter, r *http.Request, p *auth.Principal) error {
if err := require(p, auth.Action{Resource: auth.ResOptions}); err != nil {
return err
}
rep := s.checkMail(r.Context())
if rep == nil {
rep = &mailcheck.Report{Checked: time.Now(), Results: []mailcheck.Result{}}
}
writeJSON(w, http.StatusOK, rep)
return nil
}
// mailLoop checks a minute after the start, then every mailCheckEvery.
func (s *Server) mailLoop(ctx context.Context) {
wait := time.Minute
for {
select {
case <-ctx.Done():
return
case <-time.After(wait):
s.checkMail(ctx)
wait = mailCheckEvery
}
}
}