mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-04 21:47:40 +03:00
Allow to override DNS resolver address via the -debug.dnsoverride flag and SMTP port via -debug.smtpport. All flags are not available unless maddy is built using the 'debugflags' tag.
195 lines
4.2 KiB
Go
195 lines
4.2 KiB
Go
package dns
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// ExtResolver is a convenience wrapper for miekg/dns library that provides
|
|
// access to certain low-level functionality (notably, AD flag in responses,
|
|
// indicating whether DNSSEC verification was performed by the server).
|
|
type ExtResolver struct {
|
|
cl *dns.Client
|
|
Cfg *dns.ClientConfig
|
|
}
|
|
|
|
func (e ExtResolver) exchange(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
|
var resp *dns.Msg
|
|
var lastErr error
|
|
for _, srv := range e.Cfg.Servers {
|
|
resp, _, lastErr = e.cl.ExchangeContext(ctx, msg, net.JoinHostPort(srv, e.Cfg.Port))
|
|
if lastErr == nil {
|
|
break
|
|
}
|
|
}
|
|
return resp, lastErr
|
|
}
|
|
|
|
func (e ExtResolver) AuthLookupAddr(ctx context.Context, addr string) (ad bool, names []string, err error) {
|
|
revAddr, err := dns.ReverseAddr(addr)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
msg := new(dns.Msg)
|
|
msg.SetQuestion(revAddr, dns.TypePTR)
|
|
msg.SetEdns0(4096, false)
|
|
msg.AuthenticatedData = true
|
|
|
|
resp, err := e.exchange(ctx, msg)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
ad = resp.AuthenticatedData
|
|
names = make([]string, 0, len(resp.Answer))
|
|
for _, rr := range resp.Answer {
|
|
ptrRR, ok := rr.(*dns.PTR)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
names = append(names, ptrRR.Ptr)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (e ExtResolver) AuthLookupHost(ctx context.Context, host string) (ad bool, addrs []string, err error) {
|
|
ad, addrParsed, err := e.AuthLookupIPAddr(ctx, host)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
addrs = make([]string, 0, len(addrParsed))
|
|
for _, addr := range addrParsed {
|
|
addrs = append(addrs, addr.String())
|
|
}
|
|
return ad, addrs, nil
|
|
}
|
|
|
|
func (e ExtResolver) AuthLookupMX(ctx context.Context, name string) (ad bool, mxs []*net.MX, err error) {
|
|
msg := new(dns.Msg)
|
|
msg.SetQuestion(dns.Fqdn(name), dns.TypeMX)
|
|
msg.SetEdns0(4096, false)
|
|
msg.AuthenticatedData = true
|
|
|
|
resp, err := e.exchange(ctx, msg)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
ad = resp.AuthenticatedData
|
|
mxs = make([]*net.MX, 0, len(resp.Answer))
|
|
for _, rr := range resp.Answer {
|
|
mxRR, ok := rr.(*dns.MX)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
mxs = append(mxs, &net.MX{
|
|
Host: mxRR.Mx,
|
|
Pref: mxRR.Preference,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (e ExtResolver) AuthLookupTXT(ctx context.Context, name string) (ad bool, recs []string, err error) {
|
|
msg := new(dns.Msg)
|
|
msg.SetQuestion(dns.Fqdn(name), dns.TypeTXT)
|
|
msg.SetEdns0(4096, false)
|
|
msg.AuthenticatedData = true
|
|
|
|
resp, err := e.exchange(ctx, msg)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
ad = resp.AuthenticatedData
|
|
recs = make([]string, 0, len(resp.Answer))
|
|
for _, rr := range resp.Answer {
|
|
txtRR, ok := rr.(*dns.TXT)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
recs = append(recs, strings.Join(txtRR.Txt, ""))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (e ExtResolver) AuthLookupIPAddr(ctx context.Context, host string) (ad bool, addrs []net.IPAddr, err error) {
|
|
// First, query IPv6.
|
|
msg := new(dns.Msg)
|
|
msg.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
|
|
msg.SetEdns0(4096, false)
|
|
msg.AuthenticatedData = true
|
|
|
|
resp, err := e.exchange(ctx, msg)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
ad = resp.AuthenticatedData
|
|
addrs = make([]net.IPAddr, 0, len(resp.Answer))
|
|
for _, rr := range resp.Answer {
|
|
aaaaRR, ok := rr.(*dns.AAAA)
|
|
if !ok {
|
|
continue
|
|
}
|
|
addrs = append(addrs, net.IPAddr{IP: aaaaRR.AAAA})
|
|
}
|
|
|
|
// Then repeat query with IPv4.
|
|
msg = new(dns.Msg)
|
|
msg.SetQuestion(dns.Fqdn(host), dns.TypeA)
|
|
msg.SetEdns0(4096, false)
|
|
msg.AuthenticatedData = true
|
|
|
|
resp, err = e.exchange(ctx, msg)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
// Both queries should be authenticated.
|
|
ad = ad && resp.AuthenticatedData
|
|
|
|
for _, rr := range resp.Answer {
|
|
aRR, ok := rr.(*dns.A)
|
|
if !ok {
|
|
continue
|
|
}
|
|
addrs = append(addrs, net.IPAddr{IP: aRR.A})
|
|
}
|
|
|
|
return ad, addrs, err
|
|
}
|
|
|
|
func NewExtResolver() (*ExtResolver, error) {
|
|
cfg, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if overrideServ != "" && overrideServ != "system-default" {
|
|
host, port, err := net.SplitHostPort(overrideServ)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
cfg.Servers = []string{host}
|
|
cfg.Port = port
|
|
}
|
|
|
|
cl := new(dns.Client)
|
|
cl.Dialer = &net.Dialer{
|
|
Timeout: time.Duration(cfg.Timeout) * time.Second,
|
|
}
|
|
return &ExtResolver{
|
|
cl: cl,
|
|
Cfg: cfg,
|
|
}, nil
|
|
}
|