<commit_before>// Copyright © 2017 Pennock Tech, LLC.
// All rights reserved, except as granted under license.
// Licensed per file LICENSE.txt

package main

import (
	"errors"
	"fmt"
	"net"
	"os"
	"regexp"
	"strconv"
	"sync"

	"go.pennock.tech/smtpdane/internal/errorlist"

	"github.com/miekg/dns"
)

const EnvKeyDNSResolver = "DNS_RESOLVER"

var dnsSettings struct {
	sync.RWMutex
	conf   *dns.ClientConfig
	client *dns.Client
}

func initDNS() (*dns.ClientConfig, *dns.Client, error) {
	dnsSettings.RLock()
	if dnsSettings.client != nil {
		defer dnsSettings.RUnlock()
		return dnsSettings.conf, dnsSettings.client, nil
	}
	dnsSettings.RUnlock()
	dnsSettings.Lock()
	defer dnsSettings.Unlock()
	if dnsSettings.client != nil {
		return dnsSettings.conf, dnsSettings.client, nil
	}

	var (
		conf *dns.ClientConfig
		err  error
	)
	if os.Getenv(EnvKeyDNSResolver) == "" {
		conf, err = dns.ClientConfigFromFile("/etc/resolv.conf")
		if err != nil {
			return nil, nil, err
		}
	} else {
		// we now use the config always, for things like timeouts,
		// so construct a skeletal one
		conf = &dns.ClientConfig{
			Attempts: 3,
		}
	}

	c := new(dns.Client)

	dnsSettings.conf = conf
	dnsSettings.client = c

	return dnsSettings.conf, dnsSettings.client, nil
}

func resolversFromList(input []string, defDNSPort string) []string {
	r := make([]string, len(input))
	for i := range input {
		r[i] = HostPortWithDefaultPort(input[i], defDNSPort)
	}
	return r
}

var resolverSplitRE *regexp.Regexp

func init() {
	resolverSplitRE = regexp.MustCompile(`[,\s]+`)
}

func resolversFromString(input string) []string {
	return resolversFromList(resolverSplitRE.Split(input, -1), "53")
}

// FIXME: This is not doing DNS validation locally.
// It's resolving DNS, delegating trust in validation to the resolver by
// trusting the AD bit.
// I want to get this working without needing a validating resolver.
// This should be a standalone monitoring tool.
func resolveRRSecure(
	// the cbfunc is called the the confirmed RR type and the rr and the rrname;
	// it should return an item to be added to the resolveRRSecure return list,
	// and an error; non-nil error inhibits appending to the list.
	cbfunc func(typ uint16, rr dns.RR, rrname string) (interface{}, error),
	rrname string,
	typlist ...uint16,
) ([]interface{}, error) {
	config, c, err := initDNS()
	if err != nil {
		return nil, err
	}

	var resolvers []string
	if r := os.Getenv(EnvKeyDNSResolver); r != "" {
		resolvers = resolversFromString(r)
	} else {
		resolvers = resolversFromList(config.Servers, config.Port)
	}

	resultList := make([]interface{}, 0, 20)
	errList := errorlist.New()

	m := new(dns.Msg)
	m.SetEdns0(dns.DefaultMsgSize, true)

	// why is this uint16 ipv dns.Type ?  Infelicity stuck in API?
DNS_RRTYPE_LOOP:
	for _, typ := range typlist {
		m.SetQuestion(rrname, typ)

		var (
			r   *dns.Msg
			err error
		)

	DNS_RESOLVER_LOOP:
		for _, resolver := range resolvers {
			c.Net = "udp"
		RETRY_DNS_LOOKUP:
			for i := 0; i < config.Attempts; i++ {
				r, _, err = c.Exchange(m, resolver)
				if err != nil {
					if nerr, ok := err.(net.Error); ok && nerr.Timeout() && i < config.Attempts {
						continue
					}
					errList.Add(err)
					r = nil
					continue DNS_RESOLVER_LOOP
				}
			}

			if r.Rcode != dns.RcodeSuccess {
				failure, known := dns.RcodeToString[r.Rcode]
				if !known {
					failure = fmt.Sprintf("Rcode<%d> (unknown)", r.Rcode)
				}
				errList.AddErrorf("DNS lookup non-successful [resolver %v]: %v", resolver, failure)
				rcode := r.Rcode
				r = nil
				// There are enough broken server implementations when it comes
				// to AD and unknown types (often including AAAA) that we
				// currently only consider NXDOMAIN definitive.
				// We can expand upon this as needed.
				switch rcode {
				case dns.RcodeNameError:
					continue DNS_RRTYPE_LOOP
				default:
					continue DNS_RESOLVER_LOOP
				}
			}

			// Check for truncation first, in case some bad servers truncate
			// the DNSSEC data needed to be AD.
			if r.Truncated {
				c.Net = "tcp"
				goto RETRY_DNS_LOOKUP
			}<commit_msg>nil check: DNS resilience exposed nil deref<commit_after>// Copyright © 2017 Pennock Tech, LLC.
// All rights reserved, except as granted under license.
// Licensed per file LICENSE.txt

package main

import (
	"errors"
	"fmt"
	"net"
	"os"
	"regexp"
	"strconv"
	"sync"

	"go.pennock.tech/smtpdane/internal/errorlist"

	"github.com/miekg/dns"
)

const EnvKeyDNSResolver = "DNS_RESOLVER"

var dnsSettings struct {
	sync.RWMutex
	conf   *dns.ClientConfig
	client *dns.Client
}

func initDNS() (*dns.ClientConfig, *dns.Client, error) {
	dnsSettings.RLock()
	if dnsSettings.client != nil {
		defer dnsSettings.RUnlock()
		return dnsSettings.conf, dnsSettings.client, nil
	}
	dnsSettings.RUnlock()
	dnsSettings.Lock()
	defer dnsSettings.Unlock()
	if dnsSettings.client != nil {
		return dnsSettings.conf, dnsSettings.client, nil
	}

	var (
		conf *dns.ClientConfig
		err  error
	)
	if os.Getenv(EnvKeyDNSResolver) == "" {
		conf, err = dns.ClientConfigFromFile("/etc/resolv.conf")
		if err != nil {
			return nil, nil, err
		}
	} else {
		// we now use the config always, for things like timeouts,
		// so construct a skeletal one
		conf = &dns.ClientConfig{
			Attempts: 3,
		}
	}

	c := new(dns.Client)

	dnsSettings.conf = conf
	dnsSettings.client = c

	return dnsSettings.conf, dnsSettings.client, nil
}

func resolversFromList(input []string, defDNSPort string) []string {
	r := make([]string, len(input))
	for i := range input {
		r[i] = HostPortWithDefaultPort(input[i], defDNSPort)
	}
	return r
}

var resolverSplitRE *regexp.Regexp

func init() {
	resolverSplitRE = regexp.MustCompile(`[,\s]+`)
}

func resolversFromString(input string) []string {
	return resolversFromList(resolverSplitRE.Split(input, -1), "53")
}

// FIXME: This is not doing DNS validation locally.
// It's resolving DNS, delegating trust in validation to the resolver by
// trusting the AD bit.
// I want to get this working without needing a validating resolver.
// This should be a standalone monitoring tool.
func resolveRRSecure(
	// the cbfunc is called the the confirmed RR type and the rr and the rrname;
	// it should return an item to be added to the resolveRRSecure return list,
	// and an error; non-nil error inhibits appending to the list.
	cbfunc func(typ uint16, rr dns.RR, rrname string) (interface{}, error),
	rrname string,
	typlist ...uint16,
) ([]interface{}, error) {
	config, c, err := initDNS()
	if err != nil {
		return nil, err
	}

	var resolvers []string
	if r := os.Getenv(EnvKeyDNSResolver); r != "" {
		resolvers = resolversFromString(r)
	} else {
		resolvers = resolversFromList(config.Servers, config.Port)
	}

	resultList := make([]interface{}, 0, 20)
	errList := errorlist.New()

	m := new(dns.Msg)
	m.SetEdns0(dns.DefaultMsgSize, true)

	// why is this uint16 ipv dns.Type ?  Infelicity stuck in API?
DNS_RRTYPE_LOOP:
	for _, typ := range typlist {
		m.SetQuestion(rrname, typ)

		var (
			r   *dns.Msg
			err error
		)

	DNS_RESOLVER_LOOP:
		for _, resolver := range resolvers {
			c.Net = "udp"
		RETRY_DNS_LOOKUP:
			for i := 0; i < config.Attempts; i++ {
				r, _, err = c.Exchange(m, resolver)
				if err != nil {
					if nerr, ok := err.(net.Error); ok && nerr.Timeout() && i < config.Attempts {
						continue
					}
					errList.Add(err)
					r = nil
					continue DNS_RESOLVER_LOOP
				}
			}

			if r == nil {
				continue
			}
			if r.Rcode != dns.RcodeSuccess {
				failure, known := dns.RcodeToString[r.Rcode]
				if !known {
					failure = fmt.Sprintf("Rcode<%d> (unknown)", r.Rcode)
				}
				errList.AddErrorf("DNS lookup non-successful [resolver %v]: %v", resolver, failure)
				rcode := r.Rcode
				r = nil
				// There are enough broken server implementations when it comes
				// to AD and unknown types (often including AAAA) that we
				// currently only consider NXDOMAIN definitive.
				// We can expand upon this as needed.
				switch rcode {
				case dns.RcodeNameError:
					continue DNS_RRTYPE_LOOP
				default:
					continue DNS_RESOLVER_LOOP
				}
			}

			// Check for truncation first, in case some bad servers truncate
			// the DNSSEC data needed to be AD.
			if r.Truncated {
				c.Net = "tcp"
				goto RETRY_DNS_LOOKUP
			}
{'fraction_non_alphanumeric': 0.11694279840367638, 'fraction_numerical': 0.004595477083081389, 'mean_word_length': 3.280538302277433, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3563985', 'n_tokens_mistral': 3326, 'n_tokens_neox': 2918, 'n_words': 852}