aboutsummaryrefslogtreecommitdiff
path: root/plugin/normalize.go
blob: 10a60a806229889e65262d9747193a85e90eaa1e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package plugin

import (
	"fmt"
	"net"
	"strconv"
	"strings"

	"github.com/coredns/coredns/plugin/pkg/cidr"
	"github.com/coredns/coredns/plugin/pkg/parse"

	"github.com/miekg/dns"
)

// See core/dnsserver/address.go - we should unify these two impls.

// Zones represents a lists of zone names.
type Zones []string

// Matches checks if qname is a subdomain of any of the zones in z.  The match
// will return the most specific zones that matches. The empty string
// signals a not found condition.
func (z Zones) Matches(qname string) string {
	zone := ""
	for _, zname := range z {
		if dns.IsSubDomain(zname, qname) {
			// We want the *longest* matching zone, otherwise we may end up in a parent
			if len(zname) > len(zone) {
				zone = zname
			}
		}
	}
	return zone
}

// Normalize fully qualifies all zones in z. The zones in Z must be domain names, without
// a port or protocol prefix.
func (z Zones) Normalize() {
	for i := range z {
		z[i] = Name(z[i]).Normalize()
	}
}

// Name represents a domain name.
type Name string

// Matches checks to see if other is a subdomain (or the same domain) of n.
// This method assures that names can be easily and consistently matched.
func (n Name) Matches(child string) bool {
	if dns.Name(n) == dns.Name(child) {
		return true
	}
	return dns.IsSubDomain(string(n), child)
}

// Normalize lowercases and makes n fully qualified.
func (n Name) Normalize() string { return strings.ToLower(dns.Fqdn(string(n))) }

type (
	// Host represents a host from the Corefile, may contain port.
	Host string
)

// Normalize will return the host portion of host, stripping
// of any port or transport. The host will also be fully qualified and lowercased.
// An empty slice is returned on failure
func (h Host) Normalize() []string {
	// The error can be ignored here, because this function should only be called after the corefile has already been vetted.
	s := string(h)
	_, s = parse.Transport(s)

	hosts, _, err := SplitHostPort(s)
	if err != nil {
		return nil
	}
	for i := range hosts {
		hosts[i] = Name(hosts[i]).Normalize()

	}
	return hosts
}

// SplitHostPort splits s up in a host(s) and port portion, taking reverse address notation into account.
// String the string s should *not* be prefixed with any protocols, i.e. dns://. SplitHostPort can return
// multiple hosts when a reverse notation on a non-octet boundary is given.
func SplitHostPort(s string) (hosts []string, port string, err error) {
	// If there is: :[0-9]+ on the end we assume this is the port. This works for (ascii) domain
	// names and our reverse syntax, which always needs a /mask *before* the port.
	// So from the back, find first colon, and then check if it's a number.
	colon := strings.LastIndex(s, ":")
	if colon == len(s)-1 {
		return nil, "", fmt.Errorf("expecting data after last colon: %q", s)
	}
	if colon != -1 {
		if p, err := strconv.Atoi(s[colon+1:]); err == nil {
			port = strconv.Itoa(p)
			s = s[:colon]
		}
	}

	// TODO(miek): this should take escaping into account.
	if len(s) > 255 {
		return nil, "", fmt.Errorf("specified zone is too long: %d > 255", len(s))
	}

	if _, ok := dns.IsDomainName(s); !ok {
		return nil, "", fmt.Errorf("zone is not a valid domain name: %s", s)
	}

	// Check if it parses as a reverse zone, if so we use that. Must be fully specified IP and mask.
	_, n, err := net.ParseCIDR(s)
	if err != nil {
		return []string{s}, port, nil
	}

	// now check if multiple hosts must be returned.
	nets := cidr.Class(n)
	hosts = cidr.Reverse(nets)
	return hosts, port, nil
}

// OriginsFromArgsOrServerBlock returns the normalized args if that slice
// is not empty, otherwise the serverblock slice is returned (in a newly copied slice).
func OriginsFromArgsOrServerBlock(args, serverblock []string) []string {
	if len(args) == 0 {
		s := make([]string, len(serverblock))
		copy(s, serverblock)
		for i := range s {
			s[i] = Host(s[i]).Normalize()[0] // expansion of these already happened in dnsserver/registrer.go
		}
		return s
	}
	s := []string{}
	for i := range args {
		sx := Host(args[i]).Normalize()
		if len(sx) == 0 {
			continue // silently ignores errors.
		}
		s = append(s, sx...)
	}

	return s
}