1 package dns
2 3 import (
4 "bufio"
5 "io"
6 "os"
7 "strconv"
8 "strings"
9 )
10 11 // ClientConfig wraps the contents of the /etc/resolv.conf file.
12 type ClientConfig struct {
13 Servers []string // servers to use
14 Search []string // suffixes to append to local name
15 Port string // what port to use
16 Ndots int // number of dots in name to trigger absolute lookup
17 Timeout int // seconds before giving up on packet
18 Attempts int // lost packets before giving up on server, not used in the package dns
19 }
20 21 // ClientConfigFromFile parses a resolv.conf(5) like file and returns
22 // a *ClientConfig.
23 func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) {
24 file, err := os.Open(resolvconf)
25 if err != nil {
26 return nil, err
27 }
28 defer file.Close()
29 return ClientConfigFromReader(file)
30 }
31 32 // ClientConfigFromReader works like ClientConfigFromFile but takes an io.Reader as argument
33 func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
34 c := new(ClientConfig)
35 scanner := bufio.NewScanner(resolvconf)
36 c.Servers = make([]string, 0)
37 c.Search = make([]string, 0)
38 c.Port = "53"
39 c.Ndots = 1
40 c.Timeout = 5
41 c.Attempts = 2
42 43 for scanner.Scan() {
44 if err := scanner.Err(); err != nil {
45 return nil, err
46 }
47 line := scanner.Text()
48 f := strings.Fields(line)
49 if len(f) < 1 {
50 continue
51 }
52 switch f[0] {
53 case "nameserver": // add one name server
54 if len(f) > 1 {
55 // One more check: make sure server name is
56 // just an IP address. Otherwise we need DNS
57 // to look it up.
58 name := f[1]
59 c.Servers = append(c.Servers, name)
60 }
61 62 case "domain": // set search path to just this domain
63 if len(f) > 1 {
64 c.Search = make([]string, 1)
65 c.Search[0] = f[1]
66 } else {
67 c.Search = make([]string, 0)
68 }
69 70 case "search": // set search path to given servers
71 c.Search = cloneSlice(f[1:])
72 73 case "options": // magic options
74 for _, s := range f[1:] {
75 switch {
76 case len(s) >= 6 && s[:6] == "ndots:":
77 n, _ := strconv.Atoi(s[6:])
78 if n < 0 {
79 n = 0
80 } else if n > 15 {
81 n = 15
82 }
83 c.Ndots = n
84 case len(s) >= 8 && s[:8] == "timeout:":
85 n, _ := strconv.Atoi(s[8:])
86 if n < 1 {
87 n = 1
88 }
89 c.Timeout = n
90 case len(s) >= 9 && s[:9] == "attempts:":
91 n, _ := strconv.Atoi(s[9:])
92 if n < 1 {
93 n = 1
94 }
95 c.Attempts = n
96 case s == "rotate":
97 /* not imp */
98 }
99 }
100 }
101 }
102 return c, nil
103 }
104 105 // NameList returns all of the names that should be queried based on the
106 // config. It is based off of go's net/dns name building, but it does not
107 // check the length of the resulting names.
108 func (c *ClientConfig) NameList(name string) []string {
109 // if this domain is already fully qualified, no append needed.
110 if IsFqdn(name) {
111 return []string{name}
112 }
113 114 // Check to see if the name has more labels than Ndots. Do this before making
115 // the domain fully qualified.
116 hasNdots := CountLabel(name) > c.Ndots
117 // Make the domain fully qualified.
118 name = Fqdn(name)
119 120 // Make a list of names based off search.
121 names := []string{}
122 123 // If name has enough dots, try that first.
124 if hasNdots {
125 names = append(names, name)
126 }
127 for _, s := range c.Search {
128 names = append(names, Fqdn(name+s))
129 }
130 // If we didn't have enough dots, try after suffixes.
131 if !hasNdots {
132 names = append(names, name)
133 }
134 return names
135 }
136