1package dns 2 3import ( 4 "bufio" 5 "io" 6 "os" 7 "strconv" 8 "strings" 9) 10 11// ClientConfig wraps the contents of the /etc/resolv.conf file. 12type 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. 23func 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 33func 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 = make([]string, len(f)-1) 72 for i := 0; i < len(c.Search); i++ { 73 c.Search[i] = f[i+1] 74 } 75 76 case "options": // magic options 77 for i := 1; i < len(f); i++ { 78 s := f[i] 79 switch { 80 case len(s) >= 6 && s[:6] == "ndots:": 81 n, _ := strconv.Atoi(s[6:]) 82 if n < 0 { 83 n = 0 84 } else if n > 15 { 85 n = 15 86 } 87 c.Ndots = n 88 case len(s) >= 8 && s[:8] == "timeout:": 89 n, _ := strconv.Atoi(s[8:]) 90 if n < 1 { 91 n = 1 92 } 93 c.Timeout = n 94 case len(s) >= 9 && s[:9] == "attempts:": 95 n, _ := strconv.Atoi(s[9:]) 96 if n < 1 { 97 n = 1 98 } 99 c.Attempts = n 100 case s == "rotate": 101 /* not imp */ 102 } 103 } 104 } 105 } 106 return c, nil 107} 108 109// NameList returns all of the names that should be queried based on the 110// config. It is based off of go's net/dns name building, but it does not 111// check the length of the resulting names. 112func (c *ClientConfig) NameList(name string) []string { 113 // if this domain is already fully qualified, no append needed. 114 if IsFqdn(name) { 115 return []string{name} 116 } 117 118 // Check to see if the name has more labels than Ndots. Do this before making 119 // the domain fully qualified. 120 hasNdots := CountLabel(name) > c.Ndots 121 // Make the domain fully qualified. 122 name = Fqdn(name) 123 124 // Make a list of names based off search. 125 names := []string{} 126 127 // If name has enough dots, try that first. 128 if hasNdots { 129 names = append(names, name) 130 } 131 for _, s := range c.Search { 132 names = append(names, Fqdn(name+s)) 133 } 134 // If we didn't have enough dots, try after suffixes. 135 if !hasNdots { 136 names = append(names, name) 137 } 138 return names 139} 140